@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.
package/README.md CHANGED
@@ -1,15 +1,383 @@
1
1
  # @codehz/draw-call
2
2
 
3
- To install dependencies:
3
+ 一个声明式 Canvas 绘图库,提供 Flexbox 布局引擎和组件化渲染系统。使用类似 UI 框架的方式来绘制 Canvas 内容,让 Canvas 绘图变得简单直观。
4
+
5
+ ## ✨ 特性
6
+
7
+ - **Flexbox 布局引擎** - 支持完整的 Flexbox 布局,包括方向、对齐、间距等
8
+ - **组件化渲染** - 提供 Box、Text、Image、Svg、Stack 等组件
9
+ - **丰富的样式支持** - 渐变、阴影、边框、圆角等
10
+ - **文本排版** - 自动换行、省略号、行高控制等
11
+ - **SVG 图形** - 支持矩形、圆形、椭圆、路径等 SVG 图形
12
+ - **跨平台** - 支持浏览器和 Node.js 环境
13
+ - **TypeScript** - 完整的类型支持
14
+
15
+ ## 📦 安装
4
16
 
5
17
  ```bash
6
- bun install
18
+ bun install @codehz/draw-call
7
19
  ```
8
20
 
9
- To run:
21
+ ## 🚀 快速开始
10
22
 
11
- ```bash
12
- bun run index.ts
23
+ ### 浏览器环境
24
+
25
+ ```typescript
26
+ import { createCanvas, Box, Text } from "@codehz/draw-call";
27
+
28
+ // 获取 canvas 元素
29
+ const canvasEl = document.getElementById("canvas") as HTMLCanvasElement;
30
+
31
+ // 创建 Canvas 实例
32
+ const canvas = createCanvas({
33
+ width: 400,
34
+ height: 300,
35
+ pixelRatio: window.devicePixelRatio || 1,
36
+ canvas: canvasEl,
37
+ });
38
+
39
+ // 渲染内容
40
+ canvas.render(
41
+ Box({
42
+ width: "fill",
43
+ height: "fill",
44
+ background: "#ffffff",
45
+ padding: 20,
46
+ children: [
47
+ Text({
48
+ content: "Hello, draw-call!",
49
+ font: { size: 24, weight: "bold" },
50
+ color: "#333333",
51
+ }),
52
+ ],
53
+ })
54
+ );
55
+ ```
56
+
57
+ ### Node.js 环境
58
+
59
+ ```typescript
60
+ import { createCanvas, Box, Text } from "@codehz/draw-call";
61
+ import { createNodeCanvas } from "@codehz/draw-call/node";
62
+
63
+ // 创建 Canvas 实例
64
+ const canvas = createNodeCanvas({
65
+ width: 400,
66
+ height: 300,
67
+ pixelRatio: 2,
68
+ });
69
+
70
+ // 渲染内容
71
+ canvas.render(
72
+ Box({
73
+ width: "fill",
74
+ height: "fill",
75
+ background: "#ffffff",
76
+ padding: 20,
77
+ children: [
78
+ Text({
79
+ content: "Hello, draw-call!",
80
+ font: { size: 24, weight: "bold" },
81
+ color: "#333333",
82
+ }),
83
+ ],
84
+ })
85
+ );
86
+
87
+ // 保存为图片
88
+ const buffer = await canvas.toBuffer("image/png");
89
+ await Bun.write("output.png", buffer);
90
+ ```
91
+
92
+ ## 📚 组件
93
+
94
+ ### Box
95
+
96
+ 容器组件,支持 Flexbox 布局和丰富的样式。
97
+
98
+ ```typescript
99
+ Box({
100
+ width: 200,
101
+ height: 100,
102
+ background: "#ffffff",
103
+ border: { radius: 8, width: 1, color: "#e0e0e0" },
104
+ shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.08)" },
105
+ padding: 20,
106
+ direction: "row",
107
+ justify: "center",
108
+ align: "center",
109
+ children: [...],
110
+ })
111
+ ```
112
+
113
+ ### Text
114
+
115
+ 文本组件,支持自动换行、行高控制等。
116
+
117
+ ```typescript
118
+ Text({
119
+ content: "这是一段文本",
120
+ font: { size: 16, weight: "bold", family: "sans-serif" },
121
+ color: "#333333",
122
+ align: "center",
123
+ lineHeight: 1.5,
124
+ wrap: true,
125
+ maxLines: 3,
126
+ ellipsis: true,
127
+ });
128
+ ```
129
+
130
+ ### RichText
131
+
132
+ 富文本组件,支持同时展示多种样式的文本,每个文本段落可以有不同的字体、颜色、背景、装饰等。
133
+
134
+ ```typescript
135
+ RichText({
136
+ spans: [
137
+ {
138
+ text: "普通文本 ",
139
+ color: "#333333",
140
+ },
141
+ {
142
+ text: "加粗文本 ",
143
+ font: { size: 16, weight: "bold" },
144
+ color: "#333333",
145
+ },
146
+ {
147
+ text: "彩色文本 ",
148
+ font: { size: 16, weight: "bold" },
149
+ color: "#667eea",
150
+ },
151
+ {
152
+ text: "带背景 ",
153
+ color: "#ffffff",
154
+ background: "#764ba2",
155
+ },
156
+ {
157
+ text: "带下划线",
158
+ underline: true,
159
+ color: "#333333",
160
+ },
161
+ {
162
+ text: "删除线",
163
+ strikethrough: true,
164
+ color: "#999999",
165
+ },
166
+ ],
167
+ lineHeight: 1.5,
168
+ align: "left",
169
+ maxLines: 5,
170
+ ellipsis: true,
171
+ });
172
+ ```
173
+
174
+ ### Image
175
+
176
+ 图片组件,支持多种适配模式。
177
+
178
+ ```typescript
179
+ Image({
180
+ src: imageBitmap,
181
+ width: 200,
182
+ height: 150,
183
+ fit: "cover", // contain | cover | fill | none | scale-down
184
+ position: { x: "center", y: "center" },
185
+ border: { radius: 8 },
186
+ });
187
+ ```
188
+
189
+ ### Svg
190
+
191
+ SVG 图形组件,支持多种图形元素。
192
+
193
+ ```typescript
194
+ import { svg } from "@codehz/draw-call";
195
+
196
+ Svg({
197
+ width: 200,
198
+ height: 100,
199
+ viewBox: { width: 200, height: 100 },
200
+ children: [
201
+ svg.rect({ x: 10, y: 10, width: 50, height: 30, fill: "#667eea" }),
202
+ svg.circle({ cx: 100, cy: 50, r: 20, fill: "#764ba2" }),
203
+ svg.path({ d: "M150 50 Q170 30, 190 50", stroke: { color: "#333", width: 2 } }),
204
+ ],
205
+ });
206
+ ```
207
+
208
+ ### Stack
209
+
210
+ 堆叠组件,子元素绝对定位。
211
+
212
+ ```typescript
213
+ Stack({
214
+ width: 200,
215
+ height: 200,
216
+ align: "center",
217
+ justify: "center",
218
+ children: [
219
+ Box({ width: 100, height: 100, background: "#ff0000" }),
220
+ Box({ width: 50, height: 50, background: "#00ff00" }),
221
+ ],
222
+ });
223
+ ```
224
+
225
+ ## 🎨 样式
226
+
227
+ ### 尺寸
228
+
229
+ ```typescript
230
+ // 固定像素值
231
+ width: 100;
232
+
233
+ // 百分比
234
+ width: "50%";
235
+
236
+ // 自动计算
237
+ width: "auto";
238
+
239
+ // 填充可用空间
240
+ width: "fill";
241
+ ```
242
+
243
+ ### 颜色
244
+
245
+ ```typescript
246
+ // CSS 颜色字符串
247
+ background: "#ff0000";
248
+ background: "rgb(255, 0, 0)";
249
+ background: "red";
250
+
251
+ // 渐变
252
+ import { linearGradient, radialGradient } from "@codehz/draw-call";
253
+
254
+ // 线性渐变
255
+ background: linearGradient(135, "#667eea", "#764ba2");
256
+
257
+ // 带位置色标的线性渐变
258
+ background: linearGradient(90, [0, "#667eea"], [0.5, "#764ba2"], [1, "#f093fb"]);
259
+
260
+ // 径向渐变
261
+ background: radialGradient({ startX: 0.5, startY: 0.5, endRadius: 0.5 }, "#667eea", "#764ba2");
262
+ ```
263
+
264
+ ### 边框
265
+
266
+ ```typescript
267
+ border: {
268
+ width: 2,
269
+ color: "#e0e0e0",
270
+ radius: 8, // 或 [8, 8, 8, 8] 分别对应左上、右上、右下、左下
271
+ }
272
+ ```
273
+
274
+ ### 阴影
275
+
276
+ ```typescript
277
+ shadow: {
278
+ offsetX: 0,
279
+ offsetY: 4,
280
+ blur: 16,
281
+ color: "rgba(0,0,0,0.12)",
282
+ }
283
+ ```
284
+
285
+ ### 间距
286
+
287
+ ```typescript
288
+ // 四边相同
289
+ padding: 20
290
+ margin: 10
291
+
292
+ // 分别设置
293
+ padding: { top: 10, right: 20, bottom: 10, left: 20 }
294
+ margin: { top: 5, right: 10, bottom: 5, left: 10 }
295
+ ```
296
+
297
+ ## 📐 布局
298
+
299
+ ### Flexbox 布局
300
+
301
+ ```typescript
302
+ Box({
303
+ direction: "row", // row | column
304
+ justify: "center", // flex-start | center | flex-end | space-between | space-around | space-evenly
305
+ align: "center", // flex-start | center | flex-end | stretch | baseline
306
+ gap: 10,
307
+ wrap: true,
308
+ children: [...],
309
+ })
310
+ ```
311
+
312
+ ### 弹性布局
313
+
314
+ ```typescript
315
+ Box({
316
+ direction: "row",
317
+ children: [
318
+ Box({ width: 100 }), // 固定宽度
319
+ Box({ flex: 1 }), // 占据剩余空间
320
+ Box({ flex: 2 }), // 占据 2 份剩余空间
321
+ ],
322
+ });
323
+ ```
324
+
325
+ ## 🔧 高级用法
326
+
327
+ ### 自定义字体(Node.js)
328
+
329
+ ```typescript
330
+ import { GlobalFonts } from "@napi-rs/canvas";
331
+
332
+ GlobalFonts.registerFromPath("/path/to/font.ttf", "CustomFont");
333
+
334
+ Text({
335
+ content: "自定义字体",
336
+ font: { family: "CustomFont", size: 16 },
337
+ });
13
338
  ```
14
339
 
15
- This project was created using `bun init` in bun v1.3.6. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
340
+ ### 导出图片
341
+
342
+ ```typescript
343
+ // 导出为 PNG
344
+ const buffer = await canvas.toBuffer("image/png");
345
+
346
+ // 导出为 JPEG
347
+ const buffer = await canvas.toBuffer("image/jpeg", { quality: 0.9 });
348
+
349
+ // 导出为 WebP
350
+ const buffer = await canvas.toBuffer("image/webp", { quality: 0.8 });
351
+ ```
352
+
353
+ ### 获取布局信息
354
+
355
+ ```typescript
356
+ const layoutNode = canvas.render(
357
+ Box({
358
+ width: 200,
359
+ height: 100,
360
+ children: [...],
361
+ })
362
+ );
363
+
364
+ console.log(layoutNode.layout.width); // 200
365
+ console.log(layoutNode.layout.height); // 100
366
+ console.log(layoutNode.layout.x); // 0
367
+ console.log(layoutNode.layout.y); // 0
368
+ ```
369
+
370
+ ## 📖 示例
371
+
372
+ 查看 [examples](examples) 目录获取更多示例:
373
+
374
+ - [demo.ts](examples/demo.ts) - 浏览器环境演示
375
+ - [card.ts](examples/card.ts) - Node.js 环境卡片示例
376
+
377
+ ## 🤝 贡献
378
+
379
+ 欢迎提交 Issue 和 Pull Request!
380
+
381
+ ## 📄 许可证
382
+
383
+ MIT
package/canvas.d.cts CHANGED
@@ -101,12 +101,32 @@ interface ComputedLayout {
101
101
  contentWidth: number;
102
102
  contentHeight: number;
103
103
  }
104
+ interface RichTextSpanSegment {
105
+ text: string;
106
+ font: FontProps;
107
+ color: Color;
108
+ background: Color;
109
+ underline: boolean;
110
+ strikethrough: boolean;
111
+ width: number;
112
+ height: number;
113
+ ascent: number;
114
+ descent: number;
115
+ offset: number;
116
+ }
117
+ interface RichTextLine {
118
+ segments: RichTextSpanSegment[];
119
+ width: number;
120
+ height: number;
121
+ baseline: number;
122
+ }
104
123
  interface LayoutNode {
105
124
  element: Element;
106
125
  layout: ComputedLayout;
107
126
  children: LayoutNode[];
108
127
  lines?: string[];
109
128
  lineOffsets?: number[];
129
+ richLines?: RichTextLine[];
110
130
  }
111
131
  interface LayoutConstraints {
112
132
  minWidth: number;
@@ -116,7 +136,7 @@ interface LayoutConstraints {
116
136
  }
117
137
  //#endregion
118
138
  //#region src/types/components.d.ts
119
- type ElementType = "box" | "text" | "image" | "svg" | "stack";
139
+ type ElementType = "box" | "text" | "richtext" | "image" | "svg" | "stack";
120
140
  interface ElementBase {
121
141
  type: ElementType;
122
142
  }
@@ -147,6 +167,27 @@ interface TextProps extends LayoutProps {
147
167
  interface TextElement extends ElementBase, TextProps {
148
168
  type: "text";
149
169
  }
170
+ interface RichTextStyleProps {
171
+ font?: FontProps;
172
+ color?: Color;
173
+ background?: Color;
174
+ underline?: boolean;
175
+ strikethrough?: boolean;
176
+ }
177
+ interface RichTextSpan extends RichTextStyleProps {
178
+ text: string;
179
+ }
180
+ interface RichTextProps extends LayoutProps, RichTextStyleProps {
181
+ spans: RichTextSpan[];
182
+ lineHeight?: number;
183
+ align?: "left" | "center" | "right";
184
+ verticalAlign?: "top" | "middle" | "bottom";
185
+ maxLines?: number;
186
+ ellipsis?: boolean;
187
+ }
188
+ interface RichTextElement extends ElementBase, RichTextProps {
189
+ type: "richtext";
190
+ }
150
191
  interface ImageProps extends LayoutProps {
151
192
  src: ImageBitmap | CanvasImageSource;
152
193
  fit?: "contain" | "cover" | "fill" | "none" | "scale-down";
@@ -266,7 +307,7 @@ interface StackProps extends LayoutProps {
266
307
  interface StackElement extends ElementBase, StackProps {
267
308
  type: "stack";
268
309
  }
269
- type Element = BoxElement | TextElement | ImageElement | SvgElement | StackElement;
310
+ type Element = BoxElement | TextElement | RichTextElement | ImageElement | SvgElement | StackElement;
270
311
  //#endregion
271
312
  //#region src/canvas.d.ts
272
313
  interface CanvasOptions {
@@ -301,4 +342,4 @@ interface DrawCallCanvas {
301
342
  */
302
343
  declare function createCanvas(options: CanvasOptions): DrawCallCanvas;
303
344
  //#endregion
304
- export { AlignItems as A, Color as B, SvgProps as C, SvgTransformProps as D, SvgTextChild as E, LayoutConstraints as F, RadialGradientDescriptor as G, FontProps as H, LayoutNode as I, Spacing as J, Shadow as K, LayoutProps as L, ContainerLayoutProps as M, FlexDirection as N, TextElement as O, JustifyContent as P, Border as R, SvgPolylineChild as S, SvgStyleProps as T, GradientDescriptor as U, ColorStop as V, LinearGradientDescriptor as W, linearGradient as X, StrokeProps as Y, radialGradient as Z, SvgEllipseChild as _, BoxElement as a, SvgPathChild as b, ImageElement as c, StackElement as d, StackProps as f, SvgElement as g, SvgCircleChild as h, createCanvas as i, AlignSelf as j, TextProps as k, ImageProps as l, SvgChild as m, DrawCallCanvas as n, BoxProps as o, SvgAlign as p, Size as q, LayoutSize as r, Element as s, CanvasOptions as t, StackAlign as u, SvgGroupChild as v, SvgRectChild as w, SvgPolygonChild as x, SvgLineChild as y, Bounds as z };
345
+ export { linearGradient as $, SvgTransformProps as A, LayoutProps as B, SvgPathChild as C, SvgRectChild as D, SvgProps as E, ContainerLayoutProps as F, FontProps as G, Bounds as H, FlexDirection as I, RadialGradientDescriptor as J, GradientDescriptor as K, JustifyContent as L, TextProps as M, AlignItems as N, SvgStyleProps as O, AlignSelf as P, StrokeProps as Q, LayoutConstraints as R, SvgLineChild as S, SvgPolylineChild as T, Color as U, Border as V, ColorStop as W, Size as X, Shadow as Y, Spacing as Z, SvgChild as _, BoxElement as a, SvgEllipseChild as b, ImageElement as c, RichTextProps as d, radialGradient as et, RichTextSpan as f, SvgAlign as g, StackProps as h, createCanvas as i, TextElement as j, SvgTextChild as k, ImageProps as l, StackElement as m, DrawCallCanvas as n, BoxProps as o, StackAlign as p, LinearGradientDescriptor as q, LayoutSize as r, Element as s, CanvasOptions as t, RichTextElement as u, SvgCircleChild as v, SvgPolygonChild as w, SvgGroupChild as x, SvgElement as y, LayoutNode as z };
package/canvas.d.mts CHANGED
@@ -101,12 +101,32 @@ interface ComputedLayout {
101
101
  contentWidth: number;
102
102
  contentHeight: number;
103
103
  }
104
+ interface RichTextSpanSegment {
105
+ text: string;
106
+ font: FontProps;
107
+ color: Color;
108
+ background: Color;
109
+ underline: boolean;
110
+ strikethrough: boolean;
111
+ width: number;
112
+ height: number;
113
+ ascent: number;
114
+ descent: number;
115
+ offset: number;
116
+ }
117
+ interface RichTextLine {
118
+ segments: RichTextSpanSegment[];
119
+ width: number;
120
+ height: number;
121
+ baseline: number;
122
+ }
104
123
  interface LayoutNode {
105
124
  element: Element;
106
125
  layout: ComputedLayout;
107
126
  children: LayoutNode[];
108
127
  lines?: string[];
109
128
  lineOffsets?: number[];
129
+ richLines?: RichTextLine[];
110
130
  }
111
131
  interface LayoutConstraints {
112
132
  minWidth: number;
@@ -116,7 +136,7 @@ interface LayoutConstraints {
116
136
  }
117
137
  //#endregion
118
138
  //#region src/types/components.d.ts
119
- type ElementType = "box" | "text" | "image" | "svg" | "stack";
139
+ type ElementType = "box" | "text" | "richtext" | "image" | "svg" | "stack";
120
140
  interface ElementBase {
121
141
  type: ElementType;
122
142
  }
@@ -147,6 +167,27 @@ interface TextProps extends LayoutProps {
147
167
  interface TextElement extends ElementBase, TextProps {
148
168
  type: "text";
149
169
  }
170
+ interface RichTextStyleProps {
171
+ font?: FontProps;
172
+ color?: Color;
173
+ background?: Color;
174
+ underline?: boolean;
175
+ strikethrough?: boolean;
176
+ }
177
+ interface RichTextSpan extends RichTextStyleProps {
178
+ text: string;
179
+ }
180
+ interface RichTextProps extends LayoutProps, RichTextStyleProps {
181
+ spans: RichTextSpan[];
182
+ lineHeight?: number;
183
+ align?: "left" | "center" | "right";
184
+ verticalAlign?: "top" | "middle" | "bottom";
185
+ maxLines?: number;
186
+ ellipsis?: boolean;
187
+ }
188
+ interface RichTextElement extends ElementBase, RichTextProps {
189
+ type: "richtext";
190
+ }
150
191
  interface ImageProps extends LayoutProps {
151
192
  src: ImageBitmap | CanvasImageSource;
152
193
  fit?: "contain" | "cover" | "fill" | "none" | "scale-down";
@@ -266,7 +307,7 @@ interface StackProps extends LayoutProps {
266
307
  interface StackElement extends ElementBase, StackProps {
267
308
  type: "stack";
268
309
  }
269
- type Element = BoxElement | TextElement | ImageElement | SvgElement | StackElement;
310
+ type Element = BoxElement | TextElement | RichTextElement | ImageElement | SvgElement | StackElement;
270
311
  //#endregion
271
312
  //#region src/canvas.d.ts
272
313
  interface CanvasOptions {
@@ -301,4 +342,4 @@ interface DrawCallCanvas {
301
342
  */
302
343
  declare function createCanvas(options: CanvasOptions): DrawCallCanvas;
303
344
  //#endregion
304
- export { AlignItems as A, Color as B, SvgProps as C, SvgTransformProps as D, SvgTextChild as E, LayoutConstraints as F, RadialGradientDescriptor as G, FontProps as H, LayoutNode as I, Spacing as J, Shadow as K, LayoutProps as L, ContainerLayoutProps as M, FlexDirection as N, TextElement as O, JustifyContent as P, Border as R, SvgPolylineChild as S, SvgStyleProps as T, GradientDescriptor as U, ColorStop as V, LinearGradientDescriptor as W, linearGradient as X, StrokeProps as Y, radialGradient as Z, SvgEllipseChild as _, BoxElement as a, SvgPathChild as b, ImageElement as c, StackElement as d, StackProps as f, SvgElement as g, SvgCircleChild as h, createCanvas as i, AlignSelf as j, TextProps as k, ImageProps as l, SvgChild as m, DrawCallCanvas as n, BoxProps as o, SvgAlign as p, Size as q, LayoutSize as r, Element as s, CanvasOptions as t, StackAlign as u, SvgGroupChild as v, SvgRectChild as w, SvgPolygonChild as x, SvgLineChild as y, Bounds as z };
345
+ export { linearGradient as $, SvgTransformProps as A, LayoutProps as B, SvgPathChild as C, SvgRectChild as D, SvgProps as E, ContainerLayoutProps as F, FontProps as G, Bounds as H, FlexDirection as I, RadialGradientDescriptor as J, GradientDescriptor as K, JustifyContent as L, TextProps as M, AlignItems as N, SvgStyleProps as O, AlignSelf as P, StrokeProps as Q, LayoutConstraints as R, SvgLineChild as S, SvgPolylineChild as T, Color as U, Border as V, ColorStop as W, Size as X, Shadow as Y, Spacing as Z, SvgChild as _, BoxElement as a, SvgEllipseChild as b, ImageElement as c, RichTextProps as d, radialGradient as et, RichTextSpan as f, SvgAlign as g, StackProps as h, createCanvas as i, TextElement as j, SvgTextChild as k, ImageProps as l, StackElement as m, DrawCallCanvas as n, BoxProps as o, StackAlign as p, LinearGradientDescriptor as q, LayoutSize as r, Element as s, CanvasOptions as t, RichTextElement as u, SvgCircleChild as v, SvgPolygonChild as w, SvgGroupChild as x, SvgElement as y, LayoutNode as z };