@nmmty/lazycanvas 1.0.0-dev.4 → 1.0.0-dev.7
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/core/Scene.d.ts +4 -6
- package/dist/core/Scene.js +6 -22
- package/dist/jsx-runtime.js +1 -1
- package/dist/structures/LazyCanvas.d.ts +2 -0
- package/dist/structures/LazyCanvas.js +2 -0
- package/dist/structures/components/BaseLayer.d.ts +40 -28
- package/dist/structures/components/BaseLayer.js +45 -12
- package/dist/structures/components/BezierLayer.d.ts +10 -2
- package/dist/structures/components/BezierLayer.js +3 -3
- package/dist/structures/components/Div.d.ts +10 -7
- package/dist/structures/components/Div.js +18 -3
- package/dist/structures/components/ImageLayer.js +2 -2
- package/dist/structures/components/LineLayer.d.ts +10 -2
- package/dist/structures/components/LineLayer.js +3 -3
- package/dist/structures/components/MorphLayer.d.ts +1 -1
- package/dist/structures/components/MorphLayer.js +5 -5
- package/dist/structures/components/Path2DLayer.d.ts +4 -4
- package/dist/structures/components/Path2DLayer.js +10 -12
- package/dist/structures/components/PolygonLayer.d.ts +1 -1
- package/dist/structures/components/PolygonLayer.js +5 -5
- package/dist/structures/components/QuadraticLayer.d.ts +10 -2
- package/dist/structures/components/QuadraticLayer.js +3 -3
- package/dist/structures/components/TextLayer.d.ts +1 -1
- package/dist/structures/components/TextLayer.js +33 -12
- package/dist/structures/helpers/readers/JSONReader.js +13 -13
- package/dist/structures/managers/LayoutManager.d.ts +23 -0
- package/dist/structures/managers/LayoutManager.js +409 -0
- package/dist/structures/managers/RenderManager.d.ts +1 -0
- package/dist/structures/managers/RenderManager.js +35 -2
- package/dist/types/types.d.ts +25 -0
- package/dist/utils/utils.js +11 -7
- package/package.json +3 -2
- package/biome.json +0 -41
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LayoutManager = void 0;
|
|
4
|
+
const components_1 = require("../components");
|
|
5
|
+
const types_1 = require("../../types");
|
|
6
|
+
const LazyUtil_1 = require("../../utils/LazyUtil");
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
class LayoutManager {
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
this.yoga = null;
|
|
11
|
+
this.debug = opts?.debug || false;
|
|
12
|
+
this.ready = this.init();
|
|
13
|
+
}
|
|
14
|
+
async init() {
|
|
15
|
+
try {
|
|
16
|
+
// Initialize Yoga
|
|
17
|
+
// Use dynamic import to avoid issues with CJS/ESM interop and TLA
|
|
18
|
+
const yogaPkg = await import("yoga-layout");
|
|
19
|
+
const loadYoga = yogaPkg.default || yogaPkg;
|
|
20
|
+
if (typeof loadYoga === "function") {
|
|
21
|
+
this.yoga = await loadYoga((0, fs_1.readFileSync)(require.resolve("yoga-layout/dist/yoga.wasm")));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.yoga = loadYoga;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
// Fallback
|
|
29
|
+
try {
|
|
30
|
+
const yoga = require("yoga-layout");
|
|
31
|
+
this.yoga = yoga;
|
|
32
|
+
}
|
|
33
|
+
catch (e2) {
|
|
34
|
+
console.error("Failed to initialize Yoga Layout", e, e2);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
calculateLayout(root, width, height, ctx, canvas) {
|
|
39
|
+
if (!this.yoga) {
|
|
40
|
+
if (this.debug)
|
|
41
|
+
LazyUtil_1.LazyLog.log("warn", "LayoutManager: Yoga not initialized yet");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const rootNode = this.createNode(root, ctx, canvas);
|
|
45
|
+
if (!rootNode)
|
|
46
|
+
return;
|
|
47
|
+
rootNode.setWidth(width);
|
|
48
|
+
rootNode.setHeight(height);
|
|
49
|
+
rootNode.calculateLayout(width, height, this.yoga.DIRECTION_LTR);
|
|
50
|
+
this.applyLayout(rootNode, root);
|
|
51
|
+
// Clean up
|
|
52
|
+
this.freeNode(rootNode);
|
|
53
|
+
}
|
|
54
|
+
createNode(layer, ctx, canvas) {
|
|
55
|
+
if (!this.yoga)
|
|
56
|
+
return null;
|
|
57
|
+
const node = this.yoga.Node.create();
|
|
58
|
+
const layout = layer.props?.layout || {};
|
|
59
|
+
const size = layer.props?.size || {};
|
|
60
|
+
// Apply explicit layout properties first
|
|
61
|
+
if (layout.width !== undefined) {
|
|
62
|
+
this.setDimension(node, "width", layout.width);
|
|
63
|
+
}
|
|
64
|
+
else if (size.width !== undefined && layer.type !== types_1.LayerType.Text) {
|
|
65
|
+
// For TextLayer, skip size.width to allow measureFunc to work
|
|
66
|
+
this.setDimension(node, "width", size.width);
|
|
67
|
+
}
|
|
68
|
+
else if ((layer instanceof components_1.Div || layer.type === "group") && !layout.flexDirection) {
|
|
69
|
+
// For Div without explicit width and not a flex container, stretch to parent
|
|
70
|
+
// Flex containers should shrink-wrap their content by default
|
|
71
|
+
node.setWidthPercent(100);
|
|
72
|
+
}
|
|
73
|
+
if (layout.height !== undefined) {
|
|
74
|
+
this.setDimension(node, "height", layout.height);
|
|
75
|
+
}
|
|
76
|
+
else if (size.height !== undefined && layer.type !== types_1.LayerType.Text) {
|
|
77
|
+
// For TextLayer, skip size.height to allow measureFunc to work
|
|
78
|
+
this.setDimension(node, "height", size.height);
|
|
79
|
+
}
|
|
80
|
+
else if ((layer instanceof components_1.Div || layer.type === "group") && !layout.flexDirection) {
|
|
81
|
+
// For Div without explicit height and not a flex container, stretch to parent
|
|
82
|
+
// Flex containers should shrink-wrap their content by default
|
|
83
|
+
node.setHeightPercent(100);
|
|
84
|
+
}
|
|
85
|
+
if (layout.flexDirection)
|
|
86
|
+
node.setFlexDirection(this.getFlexDirection(layout.flexDirection));
|
|
87
|
+
if (layout.justifyContent)
|
|
88
|
+
node.setJustifyContent(this.getJustifyContent(layout.justifyContent));
|
|
89
|
+
if (layout.alignItems)
|
|
90
|
+
node.setAlignItems(this.getAlignItems(layout.alignItems));
|
|
91
|
+
if (layout.flexGrow !== undefined)
|
|
92
|
+
node.setFlexGrow(layout.flexGrow);
|
|
93
|
+
if (layout.flexShrink !== undefined)
|
|
94
|
+
node.setFlexShrink(layout.flexShrink);
|
|
95
|
+
if (layout.flexBasis !== undefined)
|
|
96
|
+
node.setFlexBasis(layout.flexBasis);
|
|
97
|
+
if (layout.padding)
|
|
98
|
+
this.setPadding(node, layout.padding);
|
|
99
|
+
if (layout.margin)
|
|
100
|
+
this.setMargin(node, layout.margin);
|
|
101
|
+
if (layout.gap !== undefined)
|
|
102
|
+
node.setGap(this.yoga.GUTTER_ALL, layout.gap);
|
|
103
|
+
// Position type (relative/absolute)
|
|
104
|
+
const isAbsolute = layout.position === "absolute";
|
|
105
|
+
if (isAbsolute) {
|
|
106
|
+
node.setPositionType(this.yoga.POSITION_TYPE_ABSOLUTE);
|
|
107
|
+
// Position values (top, left, right, bottom) - only for absolute positioning
|
|
108
|
+
if (layout.top !== undefined)
|
|
109
|
+
node.setPosition(this.yoga.EDGE_TOP, layout.top);
|
|
110
|
+
if (layout.left !== undefined)
|
|
111
|
+
node.setPosition(this.yoga.EDGE_LEFT, layout.left);
|
|
112
|
+
if (layout.right !== undefined)
|
|
113
|
+
node.setPosition(this.yoga.EDGE_RIGHT, layout.right);
|
|
114
|
+
if (layout.bottom !== undefined)
|
|
115
|
+
node.setPosition(this.yoga.EDGE_BOTTOM, layout.bottom);
|
|
116
|
+
}
|
|
117
|
+
// If not absolute, ignore top/left/right/bottom as they break flexbox layout
|
|
118
|
+
// Handle TextLayer measurement
|
|
119
|
+
if (layer.type === types_1.LayerType.Text && ctx && canvas) {
|
|
120
|
+
node.setMeasureFunc((width, widthMode, height, heightMode) => {
|
|
121
|
+
const textLayer = layer;
|
|
122
|
+
// Save original align/baseline for accurate measurement
|
|
123
|
+
const originalAlign = textLayer.props.align;
|
|
124
|
+
const originalBaseline = textLayer.props.baseline;
|
|
125
|
+
// Set to top-left for measurement (Yoga expects top-left coordinates)
|
|
126
|
+
textLayer.props.align = "left";
|
|
127
|
+
textLayer.props.baseline = "top";
|
|
128
|
+
// Temporarily disable multiline and width to measure natural size
|
|
129
|
+
const originalSize = textLayer.props.size ? { ...textLayer.props.size } : undefined;
|
|
130
|
+
const originalMultiline = textLayer.props.multiline
|
|
131
|
+
? { ...textLayer.props.multiline }
|
|
132
|
+
: undefined;
|
|
133
|
+
// Disable multiline for measurement
|
|
134
|
+
if (textLayer.props.multiline) {
|
|
135
|
+
textLayer.props.multiline.enabled = false;
|
|
136
|
+
}
|
|
137
|
+
// Don't set width constraint for natural measurement
|
|
138
|
+
if (textLayer.props.size) {
|
|
139
|
+
textLayer.props.size.width = undefined;
|
|
140
|
+
}
|
|
141
|
+
const size = textLayer.measureText(ctx, canvas);
|
|
142
|
+
// Restore original props
|
|
143
|
+
if (originalSize)
|
|
144
|
+
textLayer.props.size = originalSize;
|
|
145
|
+
if (originalMultiline)
|
|
146
|
+
textLayer.props.multiline = originalMultiline;
|
|
147
|
+
textLayer.props.align = originalAlign;
|
|
148
|
+
textLayer.props.baseline = originalBaseline;
|
|
149
|
+
return { width: Math.ceil(size.width), height: Math.ceil(size.height) };
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Handle children
|
|
153
|
+
if (layer instanceof components_1.Div || (layer.type === "group" && "layers" in layer)) {
|
|
154
|
+
const children = layer.layers;
|
|
155
|
+
let childIndex = 0;
|
|
156
|
+
children.forEach((child) => {
|
|
157
|
+
const childLayout = child.props?.layout;
|
|
158
|
+
const childPosition = child.props?.position;
|
|
159
|
+
// IMPORTANT: Logic for deciding if child participates in Yoga layout:
|
|
160
|
+
// 1. Div/containers always participate (they manage their own children)
|
|
161
|
+
// 2. If layer has explicit position prop (x or y set), skip Yoga - use position-based positioning
|
|
162
|
+
// 3. If layer has explicit layout prop, use Yoga layout
|
|
163
|
+
// 4. Otherwise (no position, no layout), use Yoga layout by default for proper flow
|
|
164
|
+
const isContainer = child instanceof components_1.Div || child.type === "group";
|
|
165
|
+
const hasExplicitPosition = childPosition && (childPosition.x !== undefined || childPosition.y !== undefined);
|
|
166
|
+
const hasExplicitLayout = childLayout !== undefined && Object.keys(childLayout).length > 0;
|
|
167
|
+
// Skip Yoga layout if:
|
|
168
|
+
// - Not a container AND has explicit position set (user wants manual positioning)
|
|
169
|
+
if (!isContainer && hasExplicitPosition && !hasExplicitLayout) {
|
|
170
|
+
return; // Skip this child - it will use position-based positioning
|
|
171
|
+
}
|
|
172
|
+
const childNode = this.createNode(child, ctx, canvas);
|
|
173
|
+
if (childNode) {
|
|
174
|
+
node.insertChild(childNode, childIndex++);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else if ("children" in layer && Array.isArray(layer.children)) {
|
|
179
|
+
const children = layer.children;
|
|
180
|
+
let childIndex = 0;
|
|
181
|
+
children.forEach((child) => {
|
|
182
|
+
const childLayout = child.props?.layout;
|
|
183
|
+
const childPosition = child.props?.position;
|
|
184
|
+
const isContainer = child instanceof components_1.Div || child.type === "group";
|
|
185
|
+
const hasExplicitPosition = childPosition && (childPosition.x !== undefined || childPosition.y !== undefined);
|
|
186
|
+
const hasExplicitLayout = childLayout !== undefined && Object.keys(childLayout).length > 0;
|
|
187
|
+
if (!isContainer && hasExplicitPosition && !hasExplicitLayout) {
|
|
188
|
+
return; // Skip this child - it will use position-based positioning
|
|
189
|
+
}
|
|
190
|
+
const childNode = this.createNode(child, ctx, canvas);
|
|
191
|
+
if (childNode) {
|
|
192
|
+
node.insertChild(childNode, childIndex++);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return node;
|
|
197
|
+
}
|
|
198
|
+
applyLayout(node, layer) {
|
|
199
|
+
const layout = node.getComputedLayout();
|
|
200
|
+
// Debug logging if enabled
|
|
201
|
+
if (this.debug) {
|
|
202
|
+
console.log(`[Layout] ${layer.id}: left=${layout.left}, top=${layout.top}, width=${layout.width}, height=${layout.height}`);
|
|
203
|
+
}
|
|
204
|
+
// Apply computed layout to layer props
|
|
205
|
+
// We need to be careful not to overwrite original props if we want to recalculate
|
|
206
|
+
// But for rendering, we need the absolute positions.
|
|
207
|
+
// Maybe we should store computed layout separately or update position?
|
|
208
|
+
// For now, let's update position and size if they are not fixed?
|
|
209
|
+
// Or better, update a specific 'computedLayout' property if we added one.
|
|
210
|
+
// Since we didn't add 'computedLayout', let's update position.
|
|
211
|
+
// Note: Yoga calculates relative positions. We might need to convert to absolute if the renderer expects absolute.
|
|
212
|
+
// But if the renderer handles hierarchy (Div draws children), relative is fine.
|
|
213
|
+
if (!layer.props)
|
|
214
|
+
layer.props = {};
|
|
215
|
+
if (!layer.props.position)
|
|
216
|
+
layer.props.position = { x: 0, y: 0 };
|
|
217
|
+
// Mark that this layer has computed layout from Yoga
|
|
218
|
+
// This will be used by TextLayer to know it should use top-left alignment
|
|
219
|
+
layer.props._computedLayout = true;
|
|
220
|
+
// Update position
|
|
221
|
+
layer.props.position.x = layout.left;
|
|
222
|
+
layer.props.position.y = layout.top;
|
|
223
|
+
// If layout is applied, we should probably force centring to top-left (start-top)
|
|
224
|
+
// to match Yoga's coordinate system
|
|
225
|
+
if ("centring" in layer.props) {
|
|
226
|
+
layer.props.centring = "start-top"; // or "none" depending on implementation
|
|
227
|
+
}
|
|
228
|
+
// Update size if applicable (e.g. Div or layers that support size)
|
|
229
|
+
if ("size" in layer.props) {
|
|
230
|
+
// @ts-ignore
|
|
231
|
+
const currentSize = layer.props.size;
|
|
232
|
+
// @ts-ignore
|
|
233
|
+
layer.props.size = { ...currentSize, width: layout.width, height: layout.height };
|
|
234
|
+
}
|
|
235
|
+
else if (layer instanceof components_1.Div) {
|
|
236
|
+
// Div doesn't have size prop usually, but maybe it should?
|
|
237
|
+
}
|
|
238
|
+
// Recursively apply to children
|
|
239
|
+
if (layer instanceof components_1.Div || (layer.type === "group" && "layers" in layer)) {
|
|
240
|
+
const children = layer.layers;
|
|
241
|
+
let yogaChildIndex = 0;
|
|
242
|
+
for (let i = 0; i < children.length; i++) {
|
|
243
|
+
const child = children[i];
|
|
244
|
+
// Check if this child was added to Yoga tree
|
|
245
|
+
// Must match the logic in createNode
|
|
246
|
+
const childLayout = child.props?.layout;
|
|
247
|
+
const childPosition = child.props?.position;
|
|
248
|
+
const isContainer = child instanceof components_1.Div || child.type === "group";
|
|
249
|
+
const hasExplicitPosition = childPosition && (childPosition.x !== undefined || childPosition.y !== undefined);
|
|
250
|
+
const hasExplicitLayout = childLayout !== undefined && Object.keys(childLayout).length > 0;
|
|
251
|
+
// Skip if this child wasn't added to Yoga tree
|
|
252
|
+
if (!isContainer && hasExplicitPosition && !hasExplicitLayout) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const childNode = node.getChild(yogaChildIndex++);
|
|
256
|
+
if (childNode) {
|
|
257
|
+
this.applyLayout(childNode, child);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else if ("children" in layer && Array.isArray(layer.children)) {
|
|
262
|
+
const children = layer.children;
|
|
263
|
+
let yogaChildIndex = 0;
|
|
264
|
+
for (let i = 0; i < children.length; i++) {
|
|
265
|
+
const child = children[i];
|
|
266
|
+
const childLayout = child.props?.layout;
|
|
267
|
+
const childPosition = child.props?.position;
|
|
268
|
+
const isContainer = child instanceof components_1.Div || child.type === "group";
|
|
269
|
+
const hasExplicitPosition = childPosition && (childPosition.x !== undefined || childPosition.y !== undefined);
|
|
270
|
+
const hasExplicitLayout = childLayout !== undefined && Object.keys(childLayout).length > 0;
|
|
271
|
+
if (!isContainer && hasExplicitPosition && !hasExplicitLayout) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const childNode = node.getChild(yogaChildIndex++);
|
|
275
|
+
if (childNode) {
|
|
276
|
+
this.applyLayout(childNode, child);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
freeNode(node) {
|
|
282
|
+
// Recursively free nodes? Yoga might handle this if we free root?
|
|
283
|
+
// Yoga JS usually requires manual freeing.
|
|
284
|
+
// node.freeRecursive(); // if available
|
|
285
|
+
if (node.freeRecursive) {
|
|
286
|
+
node.freeRecursive();
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
node.free();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Helpers for Yoga enums
|
|
293
|
+
getFlexDirection(dir) {
|
|
294
|
+
switch (dir) {
|
|
295
|
+
case "row":
|
|
296
|
+
return this.yoga.FLEX_DIRECTION_ROW;
|
|
297
|
+
case "column":
|
|
298
|
+
return this.yoga.FLEX_DIRECTION_COLUMN;
|
|
299
|
+
case "row-reverse":
|
|
300
|
+
return this.yoga.FLEX_DIRECTION_ROW_REVERSE;
|
|
301
|
+
case "column-reverse":
|
|
302
|
+
return this.yoga.FLEX_DIRECTION_COLUMN_REVERSE;
|
|
303
|
+
default:
|
|
304
|
+
return this.yoga.FLEX_DIRECTION_ROW;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
getJustifyContent(justify) {
|
|
308
|
+
switch (justify) {
|
|
309
|
+
case "flex-start":
|
|
310
|
+
return this.yoga.JUSTIFY_FLEX_START;
|
|
311
|
+
case "center":
|
|
312
|
+
return this.yoga.JUSTIFY_CENTER;
|
|
313
|
+
case "flex-end":
|
|
314
|
+
return this.yoga.JUSTIFY_FLEX_END;
|
|
315
|
+
case "space-between":
|
|
316
|
+
return this.yoga.JUSTIFY_SPACE_BETWEEN;
|
|
317
|
+
case "space-around":
|
|
318
|
+
return this.yoga.JUSTIFY_SPACE_AROUND;
|
|
319
|
+
case "space-evenly":
|
|
320
|
+
return this.yoga.JUSTIFY_SPACE_EVENLY;
|
|
321
|
+
default:
|
|
322
|
+
return this.yoga.JUSTIFY_FLEX_START;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
getAlignItems(align) {
|
|
326
|
+
switch (align) {
|
|
327
|
+
case "flex-start":
|
|
328
|
+
return this.yoga.ALIGN_FLEX_START;
|
|
329
|
+
case "center":
|
|
330
|
+
return this.yoga.ALIGN_CENTER;
|
|
331
|
+
case "flex-end":
|
|
332
|
+
return this.yoga.ALIGN_FLEX_END;
|
|
333
|
+
case "stretch":
|
|
334
|
+
return this.yoga.ALIGN_STRETCH;
|
|
335
|
+
case "baseline":
|
|
336
|
+
return this.yoga.ALIGN_BASELINE;
|
|
337
|
+
default:
|
|
338
|
+
return this.yoga.ALIGN_STRETCH;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
getPositionType(position) {
|
|
342
|
+
if (position === "absolute")
|
|
343
|
+
return this.yoga.POSITION_TYPE_ABSOLUTE;
|
|
344
|
+
return this.yoga.POSITION_TYPE_RELATIVE;
|
|
345
|
+
}
|
|
346
|
+
setDimension(node, prop, value) {
|
|
347
|
+
if (typeof value === "number") {
|
|
348
|
+
if (prop === "width")
|
|
349
|
+
node.setWidth(value);
|
|
350
|
+
else
|
|
351
|
+
node.setHeight(value);
|
|
352
|
+
}
|
|
353
|
+
else if (typeof value === "string") {
|
|
354
|
+
if (value.endsWith("%")) {
|
|
355
|
+
const val = parseFloat(value);
|
|
356
|
+
if (prop === "width")
|
|
357
|
+
node.setWidthPercent(val);
|
|
358
|
+
else
|
|
359
|
+
node.setHeightPercent(val);
|
|
360
|
+
}
|
|
361
|
+
else if (value === "auto") {
|
|
362
|
+
if (prop === "width")
|
|
363
|
+
node.setWidthAuto();
|
|
364
|
+
else
|
|
365
|
+
node.setHeightAuto();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
setPadding(node, padding) {
|
|
370
|
+
if (typeof padding === "number") {
|
|
371
|
+
node.setPadding(this.yoga.EDGE_ALL, padding);
|
|
372
|
+
}
|
|
373
|
+
else if (Array.isArray(padding)) {
|
|
374
|
+
// CSS order: top, right, bottom, left
|
|
375
|
+
if (padding.length === 1)
|
|
376
|
+
node.setPadding(this.yoga.EDGE_ALL, padding[0]);
|
|
377
|
+
else if (padding.length === 2) {
|
|
378
|
+
node.setPadding(this.yoga.EDGE_VERTICAL, padding[0]);
|
|
379
|
+
node.setPadding(this.yoga.EDGE_HORIZONTAL, padding[1]);
|
|
380
|
+
}
|
|
381
|
+
else if (padding.length === 4) {
|
|
382
|
+
node.setPadding(this.yoga.EDGE_TOP, padding[0]);
|
|
383
|
+
node.setPadding(this.yoga.EDGE_RIGHT, padding[1]);
|
|
384
|
+
node.setPadding(this.yoga.EDGE_BOTTOM, padding[2]);
|
|
385
|
+
node.setPadding(this.yoga.EDGE_LEFT, padding[3]);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
setMargin(node, margin) {
|
|
390
|
+
if (typeof margin === "number") {
|
|
391
|
+
node.setMargin(this.yoga.EDGE_ALL, margin);
|
|
392
|
+
}
|
|
393
|
+
else if (Array.isArray(margin)) {
|
|
394
|
+
if (margin.length === 1)
|
|
395
|
+
node.setMargin(this.yoga.EDGE_ALL, margin[0]);
|
|
396
|
+
else if (margin.length === 2) {
|
|
397
|
+
node.setMargin(this.yoga.EDGE_VERTICAL, margin[0]);
|
|
398
|
+
node.setMargin(this.yoga.EDGE_HORIZONTAL, margin[1]);
|
|
399
|
+
}
|
|
400
|
+
else if (margin.length === 4) {
|
|
401
|
+
node.setMargin(this.yoga.EDGE_TOP, margin[0]);
|
|
402
|
+
node.setMargin(this.yoga.EDGE_RIGHT, margin[1]);
|
|
403
|
+
node.setMargin(this.yoga.EDGE_BOTTOM, margin[2]);
|
|
404
|
+
node.setMargin(this.yoga.EDGE_LEFT, margin[3]);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
exports.LayoutManager = LayoutManager;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RenderManager = void 0;
|
|
4
4
|
const types_1 = require("../../types");
|
|
5
|
+
const components_1 = require("../components");
|
|
5
6
|
const LazyUtil_1 = require("../../utils/LazyUtil");
|
|
6
7
|
/**
|
|
7
8
|
* Class responsible for managing rendering operations, including static and animated exports.
|
|
@@ -28,6 +29,32 @@ class RenderManager {
|
|
|
28
29
|
if (layer.visible) {
|
|
29
30
|
this.lazyCanvas.ctx.globalCompositeOperation = layer.props?.globalComposite || "source-over";
|
|
30
31
|
await layer.draw(this.lazyCanvas.ctx, this.lazyCanvas.canvas, this.lazyCanvas.manager.layers, this.debug);
|
|
32
|
+
// Draw children if any (and not a Div, as Div handles its own children)
|
|
33
|
+
// Actually, if we want to support children on any layer, we should handle it here.
|
|
34
|
+
// But Div.draw already handles children.
|
|
35
|
+
// If we handle it here for Div, we get double rendering.
|
|
36
|
+
// So we skip Div.
|
|
37
|
+
const children = layer.children;
|
|
38
|
+
if (!(layer instanceof components_1.Div) && children && Array.isArray(children) && children.length > 0) {
|
|
39
|
+
const ctx = this.lazyCanvas.ctx;
|
|
40
|
+
ctx.save();
|
|
41
|
+
// Apply parent position offset
|
|
42
|
+
// LayoutManager sets position relative to parent.
|
|
43
|
+
// We need to translate context to parent's position so children are drawn relative to it.
|
|
44
|
+
// However, layer.draw() might have already drawn the layer at that position.
|
|
45
|
+
// And layer.draw() usually restores context.
|
|
46
|
+
// So we are back at parent's parent coordinate system.
|
|
47
|
+
// We need to translate to layer's position.
|
|
48
|
+
if (layer.props.position) {
|
|
49
|
+
const x = typeof layer.props.position.x === "number" ? layer.props.position.x : 0;
|
|
50
|
+
const y = typeof layer.props.position.y === "number" ? layer.props.position.y : 0;
|
|
51
|
+
ctx.translate(x, y);
|
|
52
|
+
}
|
|
53
|
+
for (const child of children) {
|
|
54
|
+
await this.renderLayer(child);
|
|
55
|
+
}
|
|
56
|
+
ctx.restore();
|
|
57
|
+
}
|
|
31
58
|
this.lazyCanvas.ctx.shadowColor = "transparent";
|
|
32
59
|
}
|
|
33
60
|
return this.lazyCanvas.ctx;
|
|
@@ -40,7 +67,13 @@ class RenderManager {
|
|
|
40
67
|
async renderStatic(exportType) {
|
|
41
68
|
if (this.debug)
|
|
42
69
|
LazyUtil_1.LazyLog.log("info", `Rendering static...`);
|
|
43
|
-
for
|
|
70
|
+
// Wait for layout engine to be ready
|
|
71
|
+
await this.lazyCanvas.manager.layout.ready;
|
|
72
|
+
const rootLayers = this.lazyCanvas.manager.layers.toArray().filter((l) => !l.parent);
|
|
73
|
+
for (const layer of rootLayers) {
|
|
74
|
+
this.lazyCanvas.manager.layout.calculateLayout(layer, this.lazyCanvas.options.width, this.lazyCanvas.options.height, this.lazyCanvas.ctx, this.lazyCanvas.canvas);
|
|
75
|
+
}
|
|
76
|
+
for (const layer of rootLayers) {
|
|
44
77
|
await this.renderLayer(layer);
|
|
45
78
|
}
|
|
46
79
|
switch (exportType) {
|
|
@@ -74,7 +107,7 @@ class RenderManager {
|
|
|
74
107
|
return await this.renderStatic(types_1.Export.BUFFER);
|
|
75
108
|
case types_1.Export.CTX:
|
|
76
109
|
case "ctx":
|
|
77
|
-
return this.
|
|
110
|
+
return await this.renderStatic(types_1.Export.CTX);
|
|
78
111
|
case types_1.Export.SVG:
|
|
79
112
|
case "svg":
|
|
80
113
|
return await this.renderStatic(types_1.Export.SVG);
|
package/dist/types/types.d.ts
CHANGED
|
@@ -230,3 +230,28 @@ export type StrokeOptions = {
|
|
|
230
230
|
dash?: number[];
|
|
231
231
|
miterLimit?: number;
|
|
232
232
|
};
|
|
233
|
+
|
|
234
|
+
export interface ILayoutProps {
|
|
235
|
+
width?: ScaleType;
|
|
236
|
+
height?: ScaleType;
|
|
237
|
+
flexDirection?: "row" | "column" | "row-reverse" | "column-reverse";
|
|
238
|
+
justifyContent?:
|
|
239
|
+
| "flex-start"
|
|
240
|
+
| "center"
|
|
241
|
+
| "flex-end"
|
|
242
|
+
| "space-between"
|
|
243
|
+
| "space-around"
|
|
244
|
+
| "space-evenly";
|
|
245
|
+
alignItems?: "flex-start" | "center" | "flex-end" | "stretch" | "baseline";
|
|
246
|
+
flexGrow?: number;
|
|
247
|
+
flexShrink?: number;
|
|
248
|
+
flexBasis?: number | string;
|
|
249
|
+
padding?: number | number[];
|
|
250
|
+
margin?: number | number[];
|
|
251
|
+
gap?: number;
|
|
252
|
+
position?: "absolute" | "relative";
|
|
253
|
+
top?: number | string;
|
|
254
|
+
left?: number | string;
|
|
255
|
+
right?: number | string;
|
|
256
|
+
bottom?: number | string;
|
|
257
|
+
}
|
package/dist/utils/utils.js
CHANGED
|
@@ -98,9 +98,9 @@ function parseToNormal(v, ctx, canvas, layer = { width: 0, height: 0 }, options
|
|
|
98
98
|
case "link-h":
|
|
99
99
|
return getLayerHeight(anyLayer, ctx, canvas, manager, parserInstance) + additionalSpacing;
|
|
100
100
|
case "link-x":
|
|
101
|
-
return parserInstance.parse(anyLayer.props.position
|
|
101
|
+
return parserInstance.parse(anyLayer.props.position?.x || 0) + additionalSpacing;
|
|
102
102
|
case "link-y":
|
|
103
|
-
return parserInstance.parse(anyLayer.props.position
|
|
103
|
+
return parserInstance.parse(anyLayer.props.position?.y || 0) + additionalSpacing;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -118,9 +118,9 @@ function parseToNormal(v, ctx, canvas, layer = { width: 0, height: 0 }, options
|
|
|
118
118
|
case types_1.LinkType.Height:
|
|
119
119
|
return getLayerHeight(anyLayer, ctx, canvas, manager, parserInstance) + additionalSpacing;
|
|
120
120
|
case types_1.LinkType.X:
|
|
121
|
-
return parserInstance.parse(anyLayer.props.position
|
|
121
|
+
return parserInstance.parse(anyLayer.props.position?.x || 0) + additionalSpacing;
|
|
122
122
|
case types_1.LinkType.Y:
|
|
123
|
-
return parserInstance.parse(anyLayer.props.position
|
|
123
|
+
return parserInstance.parse(anyLayer.props.position?.y || 0) + additionalSpacing;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
return 0;
|
|
@@ -436,8 +436,10 @@ function resizeLayers(layers, ratio) {
|
|
|
436
436
|
if (layers.length > 0) {
|
|
437
437
|
for (const layer of layers) {
|
|
438
438
|
if (!(layer instanceof components_1.Div || layer instanceof components_1.Path2DLayer)) {
|
|
439
|
-
|
|
440
|
-
|
|
439
|
+
if (layer.props.position) {
|
|
440
|
+
layer.props.position.x = resize(layer.props.position.x, ratio);
|
|
441
|
+
layer.props.position.y = resize(layer.props.position.y, ratio);
|
|
442
|
+
}
|
|
441
443
|
if ("size" in layer.props && layer.props.size) {
|
|
442
444
|
layer.props.size.width = resize(layer.props.size.width, ratio);
|
|
443
445
|
layer.props.size.height = resize(layer.props.size.height, ratio);
|
|
@@ -458,7 +460,9 @@ function resizeLayers(layers, ratio) {
|
|
|
458
460
|
if ("stroke" in layer.props && layer.props.stroke) {
|
|
459
461
|
layer.props.stroke.width = resize(layer.props.stroke.width, ratio);
|
|
460
462
|
}
|
|
461
|
-
if (
|
|
463
|
+
if (layer.props.position &&
|
|
464
|
+
"endX" in layer.props.position &&
|
|
465
|
+
"endY" in layer.props.position) {
|
|
462
466
|
layer.props.position.endX = resize(layer.props.position.endX, ratio);
|
|
463
467
|
layer.props.position.endY = resize(layer.props.position.endY, ratio);
|
|
464
468
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nmmty/lazycanvas",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.7",
|
|
4
4
|
"description": "A simple way to interact with @napi-rs/canvas in an advanced way!",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"@napi-rs/canvas": "^0.1.88",
|
|
26
26
|
"gifenc": "^1.0.3",
|
|
27
27
|
"js-yaml": "^4.1.0",
|
|
28
|
-
"svgson": "^5.3.1"
|
|
28
|
+
"svgson": "^5.3.1",
|
|
29
|
+
"yoga-layout": "^3.2.1"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@biomejs/biome": "1.9.4",
|
package/biome.json
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
-
"vcs": {
|
|
4
|
-
"enabled": false,
|
|
5
|
-
"clientKind": "git",
|
|
6
|
-
"useIgnoreFile": false
|
|
7
|
-
},
|
|
8
|
-
"files": {
|
|
9
|
-
"ignoreUnknown": false,
|
|
10
|
-
"ignore": [
|
|
11
|
-
"node_modules/",
|
|
12
|
-
"dist/",
|
|
13
|
-
"biome.json",
|
|
14
|
-
".biome/",
|
|
15
|
-
".git/",
|
|
16
|
-
".hg/",
|
|
17
|
-
".svn/",
|
|
18
|
-
"public/",
|
|
19
|
-
"docs/",
|
|
20
|
-
"resources/"
|
|
21
|
-
]
|
|
22
|
-
},
|
|
23
|
-
"formatter": {
|
|
24
|
-
"enabled": true,
|
|
25
|
-
"indentStyle": "space",
|
|
26
|
-
"indentWidth": 2,
|
|
27
|
-
"lineWidth": 100
|
|
28
|
-
},
|
|
29
|
-
"organizeImports": {
|
|
30
|
-
"enabled": true
|
|
31
|
-
},
|
|
32
|
-
"linter": {
|
|
33
|
-
"enabled": true,
|
|
34
|
-
"rules": { "recommended": true }
|
|
35
|
-
},
|
|
36
|
-
"javascript": {
|
|
37
|
-
"formatter": {
|
|
38
|
-
"quoteStyle": "double"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|