@codehz/draw-call 0.1.2 → 0.2.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.
@@ -0,0 +1,155 @@
1
+ /**
2
+ * 示例:使用 draw-call 绘制一个卡片
3
+ * 运行: bun examples/card.ts
4
+ */
5
+ import { Box, linearGradient, printLayout, Svg, svg, Text } from "@codehz/draw-call";
6
+ import { createNodeCanvas } from "@codehz/draw-call/node";
7
+ import { GlobalFonts } from "@napi-rs/canvas";
8
+ import { fileURLToPath } from "bun";
9
+
10
+ GlobalFonts.registerFromPath(fileURLToPath(import.meta.resolve("@fontpkg/unifont/unifont-15.0.01.ttf")), "unifont");
11
+
12
+ const canvas = createNodeCanvas({
13
+ width: 400,
14
+ height: 320,
15
+ pixelRatio: 2,
16
+ });
17
+
18
+ // 绘制背景
19
+ const layout = canvas.render(
20
+ Box({
21
+ width: "fill",
22
+ height: "fill",
23
+ background: "#f0f2f5",
24
+ padding: 20,
25
+ justify: "center",
26
+ align: "center",
27
+ children: [
28
+ // 卡片
29
+ Box({
30
+ width: 360,
31
+ background: "#ffffff",
32
+ border: { radius: 12 },
33
+ shadow: { offsetY: 4, blur: 16, color: "rgba(0,0,0,0.12)" },
34
+ direction: "column",
35
+ clip: true,
36
+ children: [
37
+ // 卡片头部
38
+ Box({
39
+ height: 100,
40
+ background: linearGradient(135, "#667eea", "#764ba2"),
41
+ padding: 20,
42
+ justify: "space-between",
43
+ align: "end",
44
+ children: [
45
+ // SVG 图标演示
46
+ Svg({
47
+ width: 48,
48
+ height: 48,
49
+ viewBox: { width: 24, height: 24 },
50
+ children: [
51
+ // 绘制一个简单的画笔图标
52
+ svg.circle({ cx: 12, cy: 12, r: 10, fill: "rgba(255,255,255,0.2)" }),
53
+ svg.path({
54
+ d: "M4 20h4l10.5-10.5a1.5 1.5 0 0 0-4-4L4 16v4z",
55
+ fill: "#ffffff",
56
+ }),
57
+ svg.line({
58
+ x1: 13.5,
59
+ y1: 6.5,
60
+ x2: 17.5,
61
+ y2: 10.5,
62
+ stroke: { color: "#ffffff", width: 1.5 },
63
+ }),
64
+ ],
65
+ }),
66
+ Text({
67
+ content: "draw-call",
68
+ font: { size: 28, weight: "bold", family: "unifont" },
69
+ color: "#ffffff",
70
+ shadow: {
71
+ offsetX: 1,
72
+ offsetY: 1,
73
+ blur: 2,
74
+ color: "rgba(0,0,0,0.3)",
75
+ },
76
+ }),
77
+ ],
78
+ }),
79
+ // 卡片内容
80
+ Box({
81
+ padding: 20,
82
+ direction: "column",
83
+ gap: 12,
84
+ children: [
85
+ Text({
86
+ content: "声明式 Canvas 绘图",
87
+ font: { size: 18, weight: "bold", family: "unifont" },
88
+ color: "#333333",
89
+ }),
90
+ Text({
91
+ content: "使用类似 UI 框架的方式来绘制 Canvas 内容,支持 Flexbox 布局、文本自动换行等特性。",
92
+ font: { size: 14, family: "unifont" },
93
+ color: "#666666",
94
+ lineHeight: 1.6,
95
+ wrap: true,
96
+ }),
97
+ // 标签
98
+ Box({
99
+ direction: "row",
100
+ gap: 8,
101
+ children: [
102
+ Box({
103
+ padding: { left: 10, right: 10, top: 4, bottom: 4 },
104
+ background: "#e8f4ff",
105
+ border: { radius: 4 },
106
+ children: [
107
+ Text({
108
+ content: "Canvas",
109
+ font: { size: 12, family: "unifont" },
110
+ color: "#1890ff",
111
+ }),
112
+ ],
113
+ }),
114
+ Box({
115
+ padding: { left: 10, right: 10, top: 4, bottom: 4 },
116
+ background: "#f6ffed",
117
+ border: { radius: 4 },
118
+ children: [
119
+ Text({
120
+ content: "TypeScript",
121
+ font: { size: 12, family: "unifont" },
122
+ color: "#52c41a",
123
+ }),
124
+ ],
125
+ }),
126
+ Box({
127
+ padding: { left: 10, right: 10, top: 4, bottom: 4 },
128
+ background: "#fff7e6",
129
+ border: { radius: 4 },
130
+ children: [
131
+ Text({
132
+ content: "声明式",
133
+ font: { size: 12, family: "unifont" },
134
+ color: "#fa8c16",
135
+ }),
136
+ ],
137
+ }),
138
+ ],
139
+ }),
140
+ ],
141
+ }),
142
+ ],
143
+ }),
144
+ ],
145
+ })
146
+ );
147
+
148
+ // 保存到文件
149
+ const buffer = await canvas.toBuffer("image/png");
150
+ await Bun.write("examples/card.png", buffer);
151
+ console.log("Card saved to examples/card.png");
152
+
153
+ // 美观打印布局树
154
+ console.log("\n=== Layout Tree ===");
155
+ printLayout(layout);
@@ -0,0 +1,478 @@
1
+ /**
2
+ * draw-call 网页演示
3
+ * 展示库的各项功能:逐句布局、样式、文本排版、图片渲染等
4
+ */
5
+ import { Box, createCanvas, Image, linearGradient, Svg, svg, Text } from "@codehz/draw-call";
6
+
7
+ // 创建一个 canvas 并绘制图片内容,返回用于 Image 组件的 canvas
8
+ function createDemoImage(): HTMLCanvasElement {
9
+ const imgCanvas = document.createElement("canvas");
10
+ imgCanvas.width = 120;
11
+ imgCanvas.height = 60;
12
+ const ctx = imgCanvas.getContext("2d");
13
+ if (!ctx) return imgCanvas;
14
+
15
+ // 绘制渐变背景
16
+ const gradient = ctx.createLinearGradient(0, 0, 120, 60);
17
+ gradient.addColorStop(0, "#667eea");
18
+ gradient.addColorStop(1, "#764ba2");
19
+ ctx.fillStyle = gradient;
20
+ ctx.beginPath();
21
+ ctx.roundRect(0, 0, 120, 60, 8);
22
+ ctx.fill();
23
+
24
+ // 绘制文字
25
+ ctx.fillStyle = "#ffffff";
26
+ ctx.font = "bold 14px sans-serif";
27
+ ctx.textAlign = "center";
28
+ ctx.textBaseline = "middle";
29
+ ctx.fillText("Image Demo", 60, 30);
30
+
31
+ return imgCanvas;
32
+ }
33
+
34
+ // 获取 canvas 元素并设置尺寸
35
+ const canvasEl = document.getElementById("canvas") as HTMLCanvasElement;
36
+ if (!canvasEl) throw new Error("Canvas element not found");
37
+
38
+ // 根据设备像素比设置实际大小
39
+ const dpr = window.devicePixelRatio || 1;
40
+ const width = 720;
41
+ const height = 600;
42
+
43
+ canvasEl.width = width * dpr;
44
+ canvasEl.height = height * dpr;
45
+ canvasEl.style.width = `${width}px`;
46
+ canvasEl.style.height = `${height}px`;
47
+
48
+ // 创建 Canvas 实例
49
+ const canvas = createCanvas({
50
+ width,
51
+ height,
52
+ pixelRatio: dpr,
53
+ canvas: canvasEl,
54
+ });
55
+
56
+ // 渲染演示内容
57
+ canvas.render(
58
+ Box({
59
+ width: "fill",
60
+ height: "fill",
61
+ background: "#ffffff",
62
+ direction: "row",
63
+ children: [
64
+ // 左侧:功能展示区
65
+ Box({
66
+ width: "fill",
67
+ flex: 1,
68
+ background: linearGradient(135, "#667eea", "#764ba2"),
69
+ padding: 20,
70
+ direction: "column",
71
+ justify: "space-between",
72
+ align: "start",
73
+ children: [
74
+ // 标题部分
75
+ Box({
76
+ direction: "column",
77
+ gap: 8,
78
+ children: [
79
+ Text({
80
+ content: "draw-call",
81
+ font: { size: 32, weight: "bold", family: "sans-serif" },
82
+ color: "#ffffff",
83
+ }),
84
+ Text({
85
+ content: "声明式 Canvas 绘图",
86
+ font: { size: 14, family: "sans-serif" },
87
+ color: "rgba(255,255,255,0.8)",
88
+ }),
89
+ ],
90
+ }),
91
+
92
+ // 功能列表
93
+ Box({
94
+ direction: "column",
95
+ gap: 12,
96
+ children: [
97
+ // 功能项 1
98
+ Box({
99
+ direction: "row",
100
+ gap: 8,
101
+ align: "center",
102
+ children: [
103
+ Box({
104
+ width: 8,
105
+ height: 8,
106
+ background: "#ffd700",
107
+ border: { radius: 4 },
108
+ }),
109
+ Text({
110
+ content: "Flexbox 布局引擎",
111
+ font: { size: 12, family: "sans-serif" },
112
+ color: "#ffffff",
113
+ }),
114
+ ],
115
+ }),
116
+ // 功能项 2
117
+ Box({
118
+ direction: "row",
119
+ gap: 8,
120
+ align: "center",
121
+ children: [
122
+ Box({
123
+ width: 8,
124
+ height: 8,
125
+ background: "#ffd700",
126
+ border: { radius: 4 },
127
+ }),
128
+ Text({
129
+ content: "丰富的样式支持",
130
+ font: { size: 12, family: "sans-serif" },
131
+ color: "#ffffff",
132
+ }),
133
+ ],
134
+ }),
135
+ // 功能项 3
136
+ Box({
137
+ direction: "row",
138
+ gap: 8,
139
+ align: "center",
140
+ children: [
141
+ Box({
142
+ width: 8,
143
+ height: 8,
144
+ background: "#ffd700",
145
+ border: { radius: 4 },
146
+ }),
147
+ Text({
148
+ content: "自动换行 & 排版",
149
+ font: { size: 12, family: "sans-serif" },
150
+ color: "#ffffff",
151
+ }),
152
+ ],
153
+ }),
154
+ ],
155
+ }),
156
+ ],
157
+ }),
158
+
159
+ // 右侧:卡片展示
160
+ Box({
161
+ width: "fill",
162
+ flex: 2,
163
+ padding: 20,
164
+ direction: "column",
165
+ gap: 12,
166
+ background: "#f5f7fa",
167
+ justify: "center",
168
+ align: "center",
169
+ children: [
170
+ // 卡片 1:样式展示
171
+ Box({
172
+ width: "fill",
173
+ background: "#ffffff",
174
+ border: { radius: 8, width: 1, color: "#e0e0e0" },
175
+ padding: 12,
176
+ shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.08)" },
177
+ direction: "column",
178
+ gap: 8,
179
+ children: [
180
+ Text({
181
+ content: "渐变背景示例",
182
+ font: { size: 12, weight: "bold", family: "sans-serif" },
183
+ color: "#333",
184
+ }),
185
+ Box({
186
+ height: 30,
187
+ background: linearGradient(90, "#667eea", "#764ba2", "#f093fb"),
188
+ border: { radius: 4 },
189
+ }),
190
+ ],
191
+ }),
192
+
193
+ // 卡片 2:阴影展示
194
+ Box({
195
+ width: "fill",
196
+ background: "#ffffff",
197
+ border: { radius: 8, width: 1, color: "#e0e0e0" },
198
+ padding: 12,
199
+ shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.08)" },
200
+ direction: "column",
201
+ gap: 8,
202
+ children: [
203
+ Text({
204
+ content: "多彩标签",
205
+ font: { size: 12, weight: "bold", family: "sans-serif" },
206
+ color: "#333",
207
+ }),
208
+ Box({
209
+ direction: "row",
210
+ gap: 6,
211
+ wrap: true,
212
+ children: [
213
+ Box({
214
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
215
+ background: "#e8f4ff",
216
+ border: { radius: 3 },
217
+ children: [
218
+ Text({
219
+ content: "Canvas",
220
+ font: { size: 11, family: "sans-serif" },
221
+ color: "#1890ff",
222
+ }),
223
+ ],
224
+ }),
225
+ Box({
226
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
227
+ background: "#f6ffed",
228
+ border: { radius: 3 },
229
+ children: [
230
+ Text({
231
+ content: "TypeScript",
232
+ font: { size: 11, family: "sans-serif" },
233
+ color: "#52c41a",
234
+ }),
235
+ ],
236
+ }),
237
+ Box({
238
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
239
+ background: "#fff7e6",
240
+ border: { radius: 3 },
241
+ children: [
242
+ Text({
243
+ content: "声明式",
244
+ font: { size: 11, family: "sans-serif" },
245
+ color: "#fa8c16",
246
+ }),
247
+ ],
248
+ }),
249
+ Box({
250
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
251
+ background: "#ffe8e8",
252
+ border: { radius: 3 },
253
+ children: [
254
+ Text({
255
+ content: "Flexbox",
256
+ font: { size: 11, family: "sans-serif" },
257
+ color: "#f5222d",
258
+ }),
259
+ ],
260
+ }),
261
+ Box({
262
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
263
+ background: "#f0f0ff",
264
+ border: { radius: 3 },
265
+ children: [
266
+ Text({
267
+ content: "布局",
268
+ font: { size: 11, family: "sans-serif" },
269
+ color: "#2f54eb",
270
+ }),
271
+ ],
272
+ }),
273
+ Box({
274
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
275
+ background: "#e8f4f8",
276
+ border: { radius: 3 },
277
+ children: [
278
+ Text({
279
+ content: "渲染",
280
+ font: { size: 11, family: "sans-serif" },
281
+ color: "#1765ad",
282
+ }),
283
+ ],
284
+ }),
285
+ Box({
286
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
287
+ background: "#f9f0ff",
288
+ border: { radius: 3 },
289
+ children: [
290
+ Text({
291
+ content: "组件化",
292
+ font: { size: 11, family: "sans-serif" },
293
+ color: "#722ed1",
294
+ }),
295
+ ],
296
+ }),
297
+ Box({
298
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
299
+ background: "#fff0f5",
300
+ border: { radius: 3 },
301
+ children: [
302
+ Text({
303
+ content: "跨平台",
304
+ font: { size: 11, family: "sans-serif" },
305
+ color: "#eb2f96",
306
+ }),
307
+ ],
308
+ }),
309
+ Box({
310
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
311
+ background: "#f6ffec",
312
+ border: { radius: 3 },
313
+ children: [
314
+ Text({
315
+ content: "高性能",
316
+ font: { size: 11, family: "sans-serif" },
317
+ color: "#52c41a",
318
+ }),
319
+ ],
320
+ }),
321
+ Box({
322
+ padding: { left: 6, right: 6, top: 3, bottom: 3 },
323
+ background: "#fffbe6",
324
+ border: { radius: 3 },
325
+ children: [
326
+ Text({
327
+ content: "易用性",
328
+ font: { size: 11, family: "sans-serif" },
329
+ color: "#d48806",
330
+ }),
331
+ ],
332
+ }),
333
+ ],
334
+ }),
335
+ ],
336
+ }),
337
+
338
+ // 卡片 3:文本排版
339
+ Box({
340
+ width: "fill",
341
+ background: "#ffffff",
342
+ border: { radius: 8, width: 1, color: "#e0e0e0" },
343
+ padding: 12,
344
+ shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.08)" },
345
+ direction: "column",
346
+ gap: 8,
347
+ children: [
348
+ Text({
349
+ content: "自动换行示例",
350
+ font: { size: 12, weight: "bold", family: "sans-serif" },
351
+ color: "#333",
352
+ }),
353
+ Text({
354
+ content: "draw-call 支持自动换行和文本排版,可以创建专业的 Canvas 内容。",
355
+ font: { size: 10, family: "sans-serif" },
356
+ color: "#666",
357
+ wrap: true,
358
+ lineHeight: 1.4,
359
+ }),
360
+ ],
361
+ }),
362
+
363
+ // 卡片 4:图片展示
364
+ Box({
365
+ width: "fill",
366
+ background: "#ffffff",
367
+ border: { radius: 8, width: 1, color: "#e0e0e0" },
368
+ padding: 12,
369
+ shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.08)" },
370
+ direction: "column",
371
+ gap: 8,
372
+ children: [
373
+ Text({
374
+ content: "Canvas 图片",
375
+ font: { size: 12, weight: "bold", family: "sans-serif" },
376
+ color: "#333",
377
+ }),
378
+ Image({
379
+ src: createDemoImage(),
380
+ width: 120,
381
+ height: 60,
382
+ border: { radius: 8 },
383
+ }),
384
+ ],
385
+ }),
386
+
387
+ // 卡片 5:SVG 图形展示
388
+ Box({
389
+ width: "fill",
390
+ background: "#ffffff",
391
+ border: { radius: 8, width: 1, color: "#e0e0e0" },
392
+ padding: 12,
393
+ shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.08)" },
394
+ direction: "column",
395
+ gap: 8,
396
+ children: [
397
+ Text({
398
+ content: "SVG 图形",
399
+ font: { size: 12, weight: "bold", family: "sans-serif" },
400
+ color: "#333",
401
+ }),
402
+ Svg({
403
+ width: "fill",
404
+ height: 100,
405
+ viewBox: { width: 200, height: 100 },
406
+ children: [
407
+ // 绘制背景网格
408
+ svg.rect({
409
+ x: 0,
410
+ y: 0,
411
+ width: 200,
412
+ height: 100,
413
+ fill: "rgba(245, 247, 250, 0.5)",
414
+ }),
415
+ // 绘制矩形
416
+ svg.rect({
417
+ x: 10,
418
+ y: 20,
419
+ width: 40,
420
+ height: 30,
421
+ rx: 5,
422
+ fill: "#667eea",
423
+ }),
424
+ // 绘制圆形
425
+ svg.circle({
426
+ cx: 80,
427
+ cy: 35,
428
+ r: 15,
429
+ fill: "#764ba2",
430
+ }),
431
+ // 绘制椭圆
432
+ svg.ellipse({
433
+ cx: 120,
434
+ cy: 35,
435
+ rx: 20,
436
+ ry: 12,
437
+ fill: "#f093fb",
438
+ }),
439
+ // 绘制线条
440
+ svg.line({
441
+ x1: 10,
442
+ y1: 70,
443
+ x2: 60,
444
+ y2: 70,
445
+ stroke: { color: "#3498db", width: 2 },
446
+ }),
447
+ // 绘制多边形
448
+ svg.polygon({
449
+ points: [
450
+ [80, 70],
451
+ [90, 55],
452
+ [100, 70],
453
+ ],
454
+ fill: "#e74c3c",
455
+ }),
456
+ // 绘制路径
457
+ svg.path({
458
+ d: "M120 70 Q130 50, 140 70 T160 70",
459
+ fill: "none",
460
+ stroke: { color: "#2ecc71", width: 2 },
461
+ }),
462
+ // 绘制文本
463
+ svg.text({
464
+ x: 160,
465
+ y: 35,
466
+ content: "SVG",
467
+ font: { size: 16, weight: "bold", family: "sans-serif" },
468
+ fill: "#333",
469
+ }),
470
+ ],
471
+ }),
472
+ ],
473
+ }),
474
+ ],
475
+ }),
476
+ ],
477
+ })
478
+ );