@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 +374 -6
- package/canvas.d.cts +44 -3
- package/canvas.d.mts +44 -3
- package/examples/card.ts +155 -0
- package/examples/demo.ts +478 -0
- package/examples/richtext.ts +284 -0
- package/index.cjs +70 -0
- package/index.d.cts +26 -7
- package/index.d.mts +26 -7
- package/index.mjs +68 -1
- package/node.cjs +2 -2
- package/node.d.cts +2 -2
- package/node.d.mts +2 -2
- package/node.mjs +4 -4
- package/package.json +1 -1
- package/render.cjs +245 -2
- package/render.mjs +245 -2
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 示例:使用 draw-call 的 RichText 组件
|
|
3
|
+
* 运行: bun examples/richtext.ts
|
|
4
|
+
*/
|
|
5
|
+
import { Box, RichText, 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: 820,
|
|
15
|
+
pixelRatio: 2,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// 绘制背景
|
|
19
|
+
canvas.render(
|
|
20
|
+
Box({
|
|
21
|
+
width: "fill",
|
|
22
|
+
height: "fill",
|
|
23
|
+
background: "#f8f9fa",
|
|
24
|
+
padding: 20,
|
|
25
|
+
direction: "column",
|
|
26
|
+
gap: 20,
|
|
27
|
+
children: [
|
|
28
|
+
// 标题
|
|
29
|
+
Text({
|
|
30
|
+
content: "RichText 富文本演示",
|
|
31
|
+
font: { size: 20, weight: "bold", family: "unifont" },
|
|
32
|
+
color: "#333",
|
|
33
|
+
}),
|
|
34
|
+
|
|
35
|
+
// 基本用法
|
|
36
|
+
Box({
|
|
37
|
+
background: "#ffffff",
|
|
38
|
+
border: { radius: 8 },
|
|
39
|
+
padding: 12,
|
|
40
|
+
shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.06)" },
|
|
41
|
+
direction: "column",
|
|
42
|
+
gap: 8,
|
|
43
|
+
children: [
|
|
44
|
+
Text({
|
|
45
|
+
content: "基本用法 - 多样式文本",
|
|
46
|
+
font: { size: 12, family: "unifont" },
|
|
47
|
+
color: "#666",
|
|
48
|
+
}),
|
|
49
|
+
RichText({
|
|
50
|
+
spans: [
|
|
51
|
+
{ text: "Hello, ", color: "#333", font: { size: 14, family: "unifont" } },
|
|
52
|
+
{ text: "World", color: "#ff6b6b", font: { size: 18, weight: "bold", family: "unifont" } },
|
|
53
|
+
{ text: "!", color: "#333", font: { size: 14, family: "unifont" } },
|
|
54
|
+
],
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
58
|
+
|
|
59
|
+
// 渐变色文本
|
|
60
|
+
Box({
|
|
61
|
+
background: "#ffffff",
|
|
62
|
+
border: { radius: 8 },
|
|
63
|
+
padding: 12,
|
|
64
|
+
shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.06)" },
|
|
65
|
+
direction: "column",
|
|
66
|
+
gap: 8,
|
|
67
|
+
children: [
|
|
68
|
+
Text({
|
|
69
|
+
content: "带背景色的文本",
|
|
70
|
+
font: { size: 12, family: "unifont" },
|
|
71
|
+
color: "#666",
|
|
72
|
+
}),
|
|
73
|
+
RichText({
|
|
74
|
+
spans: [
|
|
75
|
+
{ text: "红色背景", background: "#ffe8e8", color: "#f5222d" },
|
|
76
|
+
{ text: " " },
|
|
77
|
+
{ text: "绿色背景", background: "#f6ffed", color: "#52c41a" },
|
|
78
|
+
{ text: " " },
|
|
79
|
+
{ text: "蓝色背景", background: "#e8f4ff", color: "#1890ff" },
|
|
80
|
+
],
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
}),
|
|
84
|
+
|
|
85
|
+
// 下划线和删除线
|
|
86
|
+
Box({
|
|
87
|
+
background: "#ffffff",
|
|
88
|
+
border: { radius: 8 },
|
|
89
|
+
padding: 12,
|
|
90
|
+
shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.06)" },
|
|
91
|
+
direction: "column",
|
|
92
|
+
gap: 8,
|
|
93
|
+
children: [
|
|
94
|
+
Text({
|
|
95
|
+
content: "文本装饰 - 下划线和删除线",
|
|
96
|
+
font: { size: 12, family: "unifont" },
|
|
97
|
+
color: "#666",
|
|
98
|
+
}),
|
|
99
|
+
RichText({
|
|
100
|
+
spans: [
|
|
101
|
+
{ text: "下划线文本", underline: true, color: "#333", font: { size: 14, family: "unifont" } },
|
|
102
|
+
{ text: " " },
|
|
103
|
+
{ text: "删除线文本", strikethrough: true, color: "#333", font: { size: 14, family: "unifont" } },
|
|
104
|
+
{ text: " " },
|
|
105
|
+
{
|
|
106
|
+
text: "组合效果",
|
|
107
|
+
underline: true,
|
|
108
|
+
strikethrough: true,
|
|
109
|
+
color: "#f5222d",
|
|
110
|
+
font: { size: 14, family: "unifont" },
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
}),
|
|
114
|
+
],
|
|
115
|
+
}),
|
|
116
|
+
|
|
117
|
+
// 自动换行
|
|
118
|
+
Box({
|
|
119
|
+
background: "#ffffff",
|
|
120
|
+
border: { radius: 8 },
|
|
121
|
+
padding: 12,
|
|
122
|
+
shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.06)" },
|
|
123
|
+
direction: "column",
|
|
124
|
+
gap: 8,
|
|
125
|
+
children: [
|
|
126
|
+
Text({
|
|
127
|
+
content: "自动换行 - 长文本",
|
|
128
|
+
font: { size: 12, family: "unifont" },
|
|
129
|
+
color: "#666",
|
|
130
|
+
}),
|
|
131
|
+
RichText({
|
|
132
|
+
spans: [
|
|
133
|
+
{
|
|
134
|
+
text: "这是一个很长很长的富文本段落,它会自动换行以适应容器的宽度。",
|
|
135
|
+
color: "#333",
|
|
136
|
+
font: { size: 13, family: "unifont" },
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
}),
|
|
140
|
+
],
|
|
141
|
+
}),
|
|
142
|
+
|
|
143
|
+
// 混合样式换行
|
|
144
|
+
Box({
|
|
145
|
+
background: "#ffffff",
|
|
146
|
+
border: { radius: 8 },
|
|
147
|
+
padding: 12,
|
|
148
|
+
shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.06)" },
|
|
149
|
+
direction: "column",
|
|
150
|
+
gap: 8,
|
|
151
|
+
children: [
|
|
152
|
+
Text({
|
|
153
|
+
content: "混合样式自动换行",
|
|
154
|
+
font: { size: 12, family: "unifont" },
|
|
155
|
+
color: "#666",
|
|
156
|
+
}),
|
|
157
|
+
RichText({
|
|
158
|
+
spans: [
|
|
159
|
+
{ text: "draw-call ", color: "#333", font: { size: 13, family: "unifont" } },
|
|
160
|
+
{ text: "是一个", color: "#666", font: { size: 13, family: "unifont" } },
|
|
161
|
+
{ text: "声明式", color: "#1890ff", font: { size: 13, weight: "bold", family: "unifont" } },
|
|
162
|
+
{ text: " Canvas 绘图库,支持", color: "#333", font: { size: 13, family: "unifont" } },
|
|
163
|
+
{ text: "Flexbox 布局", color: "#52c41a", font: { size: 13, family: "unifont" }, underline: true },
|
|
164
|
+
{ text: "和", color: "#333", font: { size: 13, family: "unifont" } },
|
|
165
|
+
{ text: "富文本渲染", color: "#f5222d", font: { size: 13, weight: "bold", family: "unifont" } },
|
|
166
|
+
{ text: "。", color: "#333", font: { size: 13, family: "unifont" } },
|
|
167
|
+
],
|
|
168
|
+
}),
|
|
169
|
+
],
|
|
170
|
+
}),
|
|
171
|
+
|
|
172
|
+
// 对齐方式
|
|
173
|
+
Box({
|
|
174
|
+
background: "#ffffff",
|
|
175
|
+
border: { radius: 8 },
|
|
176
|
+
padding: 12,
|
|
177
|
+
shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.06)" },
|
|
178
|
+
direction: "column",
|
|
179
|
+
gap: 8,
|
|
180
|
+
children: [
|
|
181
|
+
Text({
|
|
182
|
+
content: "文本对齐方式",
|
|
183
|
+
font: { size: 12, family: "unifont" },
|
|
184
|
+
color: "#666",
|
|
185
|
+
}),
|
|
186
|
+
Box({
|
|
187
|
+
width: "fill",
|
|
188
|
+
background: "#f5f5f5",
|
|
189
|
+
border: { radius: 4 },
|
|
190
|
+
padding: 8,
|
|
191
|
+
direction: "column",
|
|
192
|
+
gap: 4,
|
|
193
|
+
children: [
|
|
194
|
+
Text({
|
|
195
|
+
content: "左对齐:",
|
|
196
|
+
font: { size: 11, family: "unifont" },
|
|
197
|
+
color: "#999",
|
|
198
|
+
}),
|
|
199
|
+
RichText({
|
|
200
|
+
align: "left",
|
|
201
|
+
spans: [{ text: "这是一段左对齐的富文本", color: "#333", font: { size: 12, family: "unifont" } }],
|
|
202
|
+
}),
|
|
203
|
+
],
|
|
204
|
+
}),
|
|
205
|
+
Box({
|
|
206
|
+
width: "fill",
|
|
207
|
+
background: "#f5f5f5",
|
|
208
|
+
border: { radius: 4 },
|
|
209
|
+
padding: 8,
|
|
210
|
+
direction: "column",
|
|
211
|
+
gap: 4,
|
|
212
|
+
children: [
|
|
213
|
+
Text({
|
|
214
|
+
content: "居中对齐:",
|
|
215
|
+
font: { size: 11, family: "unifont" },
|
|
216
|
+
color: "#999",
|
|
217
|
+
}),
|
|
218
|
+
RichText({
|
|
219
|
+
align: "center",
|
|
220
|
+
width: "fill",
|
|
221
|
+
spans: [{ text: "这是一段居中对齐的富文本", color: "#333", font: { size: 12, family: "unifont" } }],
|
|
222
|
+
}),
|
|
223
|
+
],
|
|
224
|
+
}),
|
|
225
|
+
Box({
|
|
226
|
+
width: "fill",
|
|
227
|
+
background: "#f5f5f5",
|
|
228
|
+
border: { radius: 4 },
|
|
229
|
+
padding: 8,
|
|
230
|
+
direction: "column",
|
|
231
|
+
gap: 4,
|
|
232
|
+
children: [
|
|
233
|
+
Text({
|
|
234
|
+
content: "右对齐:",
|
|
235
|
+
font: { size: 11, family: "unifont" },
|
|
236
|
+
color: "#999",
|
|
237
|
+
}),
|
|
238
|
+
RichText({
|
|
239
|
+
align: "right",
|
|
240
|
+
width: "fill",
|
|
241
|
+
spans: [{ text: "这是一段右对齐的富文本", color: "#333", font: { size: 12, family: "unifont" } }],
|
|
242
|
+
}),
|
|
243
|
+
],
|
|
244
|
+
}),
|
|
245
|
+
],
|
|
246
|
+
}),
|
|
247
|
+
|
|
248
|
+
// 彩色标签云
|
|
249
|
+
Box({
|
|
250
|
+
background: "#ffffff",
|
|
251
|
+
border: { radius: 8 },
|
|
252
|
+
padding: 12,
|
|
253
|
+
shadow: { offsetY: 2, blur: 8, color: "rgba(0,0,0,0.06)" },
|
|
254
|
+
direction: "column",
|
|
255
|
+
gap: 8,
|
|
256
|
+
children: [
|
|
257
|
+
Text({
|
|
258
|
+
content: "彩色标签展示",
|
|
259
|
+
font: { size: 12, family: "unifont" },
|
|
260
|
+
color: "#666",
|
|
261
|
+
}),
|
|
262
|
+
RichText({
|
|
263
|
+
spans: [
|
|
264
|
+
{ text: "TypeScript", background: "#e8f4ff", color: "#1890ff", font: { size: 12, family: "unifont" } },
|
|
265
|
+
{ text: " " },
|
|
266
|
+
{ text: "Canvas", background: "#f6ffed", color: "#52c41a", font: { size: 12, family: "unifont" } },
|
|
267
|
+
{ text: " " },
|
|
268
|
+
{ text: "Flexbox", background: "#fff7e6", color: "#fa8c16", font: { size: 12, family: "unifont" } },
|
|
269
|
+
{ text: " " },
|
|
270
|
+
{ text: "声明式", background: "#ffe8e8", color: "#f5222d", font: { size: 12, family: "unifont" } },
|
|
271
|
+
{ text: " " },
|
|
272
|
+
{ text: "组件化", background: "#f0f0ff", color: "#2f54eb", font: { size: 12, family: "unifont" } },
|
|
273
|
+
],
|
|
274
|
+
}),
|
|
275
|
+
],
|
|
276
|
+
}),
|
|
277
|
+
],
|
|
278
|
+
})
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// 保存到文件
|
|
282
|
+
const buffer = await canvas.toBuffer("image/png");
|
|
283
|
+
await Bun.write("examples/richtext.png", buffer);
|
|
284
|
+
console.log("RichText demo saved to examples/richtext.png");
|
package/index.cjs
CHANGED
|
@@ -70,6 +70,15 @@ function Image(props) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/components/RichText.ts
|
|
75
|
+
function RichText(props) {
|
|
76
|
+
return {
|
|
77
|
+
type: "richtext",
|
|
78
|
+
...props
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
//#endregion
|
|
74
83
|
//#region src/components/Stack.ts
|
|
75
84
|
function Stack(props) {
|
|
@@ -135,15 +144,76 @@ function Text(props) {
|
|
|
135
144
|
};
|
|
136
145
|
}
|
|
137
146
|
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/layout/utils/print.ts
|
|
149
|
+
/**
|
|
150
|
+
* 获取元素类型的显示名称
|
|
151
|
+
*/
|
|
152
|
+
function getElementType(element) {
|
|
153
|
+
switch (element.type) {
|
|
154
|
+
case "box": return "Box";
|
|
155
|
+
case "text": return `Text "${element.content.slice(0, 20)}${element.content.length > 20 ? "..." : ""}"`;
|
|
156
|
+
case "stack": return "Stack";
|
|
157
|
+
case "image": return "Image";
|
|
158
|
+
case "svg": return "Svg";
|
|
159
|
+
default: return element.type;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 递归打印布局树
|
|
164
|
+
*/
|
|
165
|
+
function printLayoutToString(node, prefix = "", isLast = true, depth = 0) {
|
|
166
|
+
const lines = [];
|
|
167
|
+
const connector = isLast ? "└─ " : "├─ ";
|
|
168
|
+
const type = getElementType(node.element);
|
|
169
|
+
const { x, y, width, height } = node.layout;
|
|
170
|
+
const childCount = node.children.length;
|
|
171
|
+
lines.push(`${prefix}${connector}${type} @(${Math.round(x)},${Math.round(y)}) size:${Math.round(width)}x${Math.round(height)}`);
|
|
172
|
+
if (node.element.type === "text" && node.lines) {
|
|
173
|
+
const contentPrefix = prefix + (isLast ? " " : "│ ");
|
|
174
|
+
for (let i = 0; i < node.lines.length; i++) {
|
|
175
|
+
const lineText = node.lines[i];
|
|
176
|
+
const isLastLine = i === node.lines.length - 1 && childCount === 0;
|
|
177
|
+
lines.push(`${contentPrefix}${isLastLine ? "└─ " : "├─ "}${JSON.stringify(lineText)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
181
|
+
const child = node.children[i];
|
|
182
|
+
const isChildLast = i === node.children.length - 1;
|
|
183
|
+
const childPrefix = prefix + (isLast ? " " : "│ ");
|
|
184
|
+
lines.push(...printLayoutToString(child, childPrefix, isChildLast, depth + 1));
|
|
185
|
+
}
|
|
186
|
+
return lines;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 打印 LayoutNode 树结构到控制台
|
|
190
|
+
*/
|
|
191
|
+
function printLayout(node) {
|
|
192
|
+
const lines = printLayoutToString(node, "", true);
|
|
193
|
+
console.log(lines.join("\n"));
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 将 LayoutNode 转换为美观的字符串
|
|
197
|
+
* @param node LayoutNode 根节点
|
|
198
|
+
* @param indent 缩进字符串,默认为两个空格
|
|
199
|
+
* @returns 格式化的字符串
|
|
200
|
+
*/
|
|
201
|
+
function layoutToString(node, _indent = " ") {
|
|
202
|
+
return printLayoutToString(node, "", true).join("\n");
|
|
203
|
+
}
|
|
204
|
+
|
|
138
205
|
//#endregion
|
|
139
206
|
exports.Box = Box;
|
|
140
207
|
exports.Image = Image;
|
|
208
|
+
exports.RichText = RichText;
|
|
141
209
|
exports.Stack = Stack;
|
|
142
210
|
exports.Svg = Svg;
|
|
143
211
|
exports.Text = Text;
|
|
144
212
|
exports.computeLayout = require_render.computeLayout;
|
|
145
213
|
exports.createCanvas = createCanvas;
|
|
146
214
|
exports.createCanvasMeasureContext = require_render.createCanvasMeasureContext;
|
|
215
|
+
exports.layoutToString = layoutToString;
|
|
147
216
|
exports.linearGradient = require_render.linearGradient;
|
|
217
|
+
exports.printLayout = printLayout;
|
|
148
218
|
exports.radialGradient = require_render.radialGradient;
|
|
149
219
|
exports.svg = svg;
|
package/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as linearGradient, A as SvgTransformProps, B as LayoutProps, C as SvgPathChild, D as SvgRectChild, E as SvgProps, F as ContainerLayoutProps, G as FontProps, H as Bounds, I as FlexDirection, J as RadialGradientDescriptor, K as GradientDescriptor, L as JustifyContent, M as TextProps, N as AlignItems, O as SvgStyleProps, P as AlignSelf, Q as StrokeProps, R as LayoutConstraints, S as SvgLineChild, T as SvgPolylineChild, U as Color, V as Border, W as ColorStop, X as Size, Y as Shadow, Z as Spacing, _ as SvgChild, a as BoxElement, b as SvgEllipseChild, c as ImageElement, d as RichTextProps, et as radialGradient, f as RichTextSpan, g as SvgAlign, h as StackProps, i as createCanvas, j as TextElement, k as SvgTextChild, l as ImageProps, m as StackElement, n as DrawCallCanvas, o as BoxProps, p as StackAlign, q as LinearGradientDescriptor, r as LayoutSize, s as Element, t as CanvasOptions, u as RichTextElement, v as SvgCircleChild, w as SvgPolygonChild, x as SvgGroupChild, y as SvgElement, z as LayoutNode } from "./canvas.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/components/Box.d.ts
|
|
4
4
|
declare function Box(props: BoxProps): BoxElement;
|
|
@@ -6,6 +6,9 @@ declare function Box(props: BoxProps): BoxElement;
|
|
|
6
6
|
//#region src/components/Image.d.ts
|
|
7
7
|
declare function Image(props: ImageProps): ImageElement;
|
|
8
8
|
//#endregion
|
|
9
|
+
//#region src/components/RichText.d.ts
|
|
10
|
+
declare function RichText(props: RichTextProps): RichTextElement;
|
|
11
|
+
//#endregion
|
|
9
12
|
//#region src/components/Stack.d.ts
|
|
10
13
|
declare function Stack(props: StackProps): StackElement;
|
|
11
14
|
//#endregion
|
|
@@ -27,16 +30,32 @@ declare const svg: {
|
|
|
27
30
|
declare function Text(props: TextProps): TextElement;
|
|
28
31
|
//#endregion
|
|
29
32
|
//#region src/layout/utils/measure.d.ts
|
|
33
|
+
interface MeasureTextResult {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
offset: number;
|
|
37
|
+
ascent: number;
|
|
38
|
+
descent: number;
|
|
39
|
+
}
|
|
30
40
|
interface MeasureContext {
|
|
31
|
-
measureText(text: string, font: FontProps):
|
|
32
|
-
width: number;
|
|
33
|
-
height: number;
|
|
34
|
-
offset: number;
|
|
35
|
-
};
|
|
41
|
+
measureText(text: string, font: FontProps): MeasureTextResult;
|
|
36
42
|
}
|
|
37
43
|
declare function createCanvasMeasureContext(ctx: CanvasRenderingContext2D): MeasureContext;
|
|
38
44
|
//#endregion
|
|
39
45
|
//#region src/layout/engine.d.ts
|
|
40
46
|
declare function computeLayout(element: Element, ctx: MeasureContext, constraints: LayoutConstraints, x?: number, y?: number): LayoutNode;
|
|
41
47
|
//#endregion
|
|
42
|
-
|
|
48
|
+
//#region src/layout/utils/print.d.ts
|
|
49
|
+
/**
|
|
50
|
+
* 打印 LayoutNode 树结构到控制台
|
|
51
|
+
*/
|
|
52
|
+
declare function printLayout(node: LayoutNode): void;
|
|
53
|
+
/**
|
|
54
|
+
* 将 LayoutNode 转换为美观的字符串
|
|
55
|
+
* @param node LayoutNode 根节点
|
|
56
|
+
* @param indent 缩进字符串,默认为两个空格
|
|
57
|
+
* @returns 格式化的字符串
|
|
58
|
+
*/
|
|
59
|
+
declare function layoutToString(node: LayoutNode, _indent?: string): string;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutNode, type LayoutProps, type LayoutSize, type LinearGradientDescriptor, type MeasureContext, type RadialGradientDescriptor, RichText, type RichTextElement, type RichTextProps, type RichTextSpan, type Shadow, type Size, type Spacing, Stack, type StackAlign, type StackElement, type StackProps, type StrokeProps, Svg, type SvgAlign, type SvgChild, type SvgCircleChild, type SvgElement, type SvgEllipseChild, type SvgGroupChild, type SvgLineChild, type SvgPathChild, type SvgPolygonChild, type SvgPolylineChild, type SvgProps, type SvgRectChild, type SvgStyleProps, type SvgTextChild, type SvgTransformProps, Text, type TextElement, type TextProps, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
|
package/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as linearGradient, A as SvgTransformProps, B as LayoutProps, C as SvgPathChild, D as SvgRectChild, E as SvgProps, F as ContainerLayoutProps, G as FontProps, H as Bounds, I as FlexDirection, J as RadialGradientDescriptor, K as GradientDescriptor, L as JustifyContent, M as TextProps, N as AlignItems, O as SvgStyleProps, P as AlignSelf, Q as StrokeProps, R as LayoutConstraints, S as SvgLineChild, T as SvgPolylineChild, U as Color, V as Border, W as ColorStop, X as Size, Y as Shadow, Z as Spacing, _ as SvgChild, a as BoxElement, b as SvgEllipseChild, c as ImageElement, d as RichTextProps, et as radialGradient, f as RichTextSpan, g as SvgAlign, h as StackProps, i as createCanvas, j as TextElement, k as SvgTextChild, l as ImageProps, m as StackElement, n as DrawCallCanvas, o as BoxProps, p as StackAlign, q as LinearGradientDescriptor, r as LayoutSize, s as Element, t as CanvasOptions, u as RichTextElement, v as SvgCircleChild, w as SvgPolygonChild, x as SvgGroupChild, y as SvgElement, z as LayoutNode } from "./canvas.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/components/Box.d.ts
|
|
4
4
|
declare function Box(props: BoxProps): BoxElement;
|
|
@@ -6,6 +6,9 @@ declare function Box(props: BoxProps): BoxElement;
|
|
|
6
6
|
//#region src/components/Image.d.ts
|
|
7
7
|
declare function Image(props: ImageProps): ImageElement;
|
|
8
8
|
//#endregion
|
|
9
|
+
//#region src/components/RichText.d.ts
|
|
10
|
+
declare function RichText(props: RichTextProps): RichTextElement;
|
|
11
|
+
//#endregion
|
|
9
12
|
//#region src/components/Stack.d.ts
|
|
10
13
|
declare function Stack(props: StackProps): StackElement;
|
|
11
14
|
//#endregion
|
|
@@ -27,16 +30,32 @@ declare const svg: {
|
|
|
27
30
|
declare function Text(props: TextProps): TextElement;
|
|
28
31
|
//#endregion
|
|
29
32
|
//#region src/layout/utils/measure.d.ts
|
|
33
|
+
interface MeasureTextResult {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
offset: number;
|
|
37
|
+
ascent: number;
|
|
38
|
+
descent: number;
|
|
39
|
+
}
|
|
30
40
|
interface MeasureContext {
|
|
31
|
-
measureText(text: string, font: FontProps):
|
|
32
|
-
width: number;
|
|
33
|
-
height: number;
|
|
34
|
-
offset: number;
|
|
35
|
-
};
|
|
41
|
+
measureText(text: string, font: FontProps): MeasureTextResult;
|
|
36
42
|
}
|
|
37
43
|
declare function createCanvasMeasureContext(ctx: CanvasRenderingContext2D): MeasureContext;
|
|
38
44
|
//#endregion
|
|
39
45
|
//#region src/layout/engine.d.ts
|
|
40
46
|
declare function computeLayout(element: Element, ctx: MeasureContext, constraints: LayoutConstraints, x?: number, y?: number): LayoutNode;
|
|
41
47
|
//#endregion
|
|
42
|
-
|
|
48
|
+
//#region src/layout/utils/print.d.ts
|
|
49
|
+
/**
|
|
50
|
+
* 打印 LayoutNode 树结构到控制台
|
|
51
|
+
*/
|
|
52
|
+
declare function printLayout(node: LayoutNode): void;
|
|
53
|
+
/**
|
|
54
|
+
* 将 LayoutNode 转换为美观的字符串
|
|
55
|
+
* @param node LayoutNode 根节点
|
|
56
|
+
* @param indent 缩进字符串,默认为两个空格
|
|
57
|
+
* @returns 格式化的字符串
|
|
58
|
+
*/
|
|
59
|
+
declare function layoutToString(node: LayoutNode, _indent?: string): string;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutNode, type LayoutProps, type LayoutSize, type LinearGradientDescriptor, type MeasureContext, type RadialGradientDescriptor, RichText, type RichTextElement, type RichTextProps, type RichTextSpan, type Shadow, type Size, type Spacing, Stack, type StackAlign, type StackElement, type StackProps, type StrokeProps, Svg, type SvgAlign, type SvgChild, type SvgCircleChild, type SvgElement, type SvgEllipseChild, type SvgGroupChild, type SvgLineChild, type SvgPathChild, type SvgPolygonChild, type SvgPolylineChild, type SvgProps, type SvgRectChild, type SvgStyleProps, type SvgTextChild, type SvgTransformProps, Text, type TextElement, type TextProps, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
|
package/index.mjs
CHANGED
|
@@ -70,6 +70,15 @@ function Image(props) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/components/RichText.ts
|
|
75
|
+
function RichText(props) {
|
|
76
|
+
return {
|
|
77
|
+
type: "richtext",
|
|
78
|
+
...props
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
//#endregion
|
|
74
83
|
//#region src/components/Stack.ts
|
|
75
84
|
function Stack(props) {
|
|
@@ -136,4 +145,62 @@ function Text(props) {
|
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
//#endregion
|
|
139
|
-
|
|
148
|
+
//#region src/layout/utils/print.ts
|
|
149
|
+
/**
|
|
150
|
+
* 获取元素类型的显示名称
|
|
151
|
+
*/
|
|
152
|
+
function getElementType(element) {
|
|
153
|
+
switch (element.type) {
|
|
154
|
+
case "box": return "Box";
|
|
155
|
+
case "text": return `Text "${element.content.slice(0, 20)}${element.content.length > 20 ? "..." : ""}"`;
|
|
156
|
+
case "stack": return "Stack";
|
|
157
|
+
case "image": return "Image";
|
|
158
|
+
case "svg": return "Svg";
|
|
159
|
+
default: return element.type;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 递归打印布局树
|
|
164
|
+
*/
|
|
165
|
+
function printLayoutToString(node, prefix = "", isLast = true, depth = 0) {
|
|
166
|
+
const lines = [];
|
|
167
|
+
const connector = isLast ? "└─ " : "├─ ";
|
|
168
|
+
const type = getElementType(node.element);
|
|
169
|
+
const { x, y, width, height } = node.layout;
|
|
170
|
+
const childCount = node.children.length;
|
|
171
|
+
lines.push(`${prefix}${connector}${type} @(${Math.round(x)},${Math.round(y)}) size:${Math.round(width)}x${Math.round(height)}`);
|
|
172
|
+
if (node.element.type === "text" && node.lines) {
|
|
173
|
+
const contentPrefix = prefix + (isLast ? " " : "│ ");
|
|
174
|
+
for (let i = 0; i < node.lines.length; i++) {
|
|
175
|
+
const lineText = node.lines[i];
|
|
176
|
+
const isLastLine = i === node.lines.length - 1 && childCount === 0;
|
|
177
|
+
lines.push(`${contentPrefix}${isLastLine ? "└─ " : "├─ "}${JSON.stringify(lineText)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
181
|
+
const child = node.children[i];
|
|
182
|
+
const isChildLast = i === node.children.length - 1;
|
|
183
|
+
const childPrefix = prefix + (isLast ? " " : "│ ");
|
|
184
|
+
lines.push(...printLayoutToString(child, childPrefix, isChildLast, depth + 1));
|
|
185
|
+
}
|
|
186
|
+
return lines;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 打印 LayoutNode 树结构到控制台
|
|
190
|
+
*/
|
|
191
|
+
function printLayout(node) {
|
|
192
|
+
const lines = printLayoutToString(node, "", true);
|
|
193
|
+
console.log(lines.join("\n"));
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 将 LayoutNode 转换为美观的字符串
|
|
197
|
+
* @param node LayoutNode 根节点
|
|
198
|
+
* @param indent 缩进字符串,默认为两个空格
|
|
199
|
+
* @returns 格式化的字符串
|
|
200
|
+
*/
|
|
201
|
+
function layoutToString(node, _indent = " ") {
|
|
202
|
+
return printLayoutToString(node, "", true).join("\n");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
export { Box, Image, RichText, Stack, Svg, Text, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
|
package/node.cjs
CHANGED
|
@@ -8,7 +8,7 @@ let _napi_rs_canvas = require("@napi-rs/canvas");
|
|
|
8
8
|
* 此函数需要 @napi-rs/canvas 作为依赖
|
|
9
9
|
* 安装: bun add @napi-rs/canvas
|
|
10
10
|
*/
|
|
11
|
-
function
|
|
11
|
+
function createNodeCanvas(options) {
|
|
12
12
|
const { width, height, pixelRatio = 1 } = options;
|
|
13
13
|
const canvas = (0, _napi_rs_canvas.createCanvas)(width * pixelRatio, height * pixelRatio);
|
|
14
14
|
const ctx = canvas.getContext("2d");
|
|
@@ -45,4 +45,4 @@ function createCanvas(options) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
//#endregion
|
|
48
|
-
exports.
|
|
48
|
+
exports.createNodeCanvas = createNodeCanvas;
|
package/node.d.cts
CHANGED
|
@@ -7,6 +7,6 @@ import { n as DrawCallCanvas, t as CanvasOptions } from "./canvas.cjs";
|
|
|
7
7
|
* 此函数需要 @napi-rs/canvas 作为依赖
|
|
8
8
|
* 安装: bun add @napi-rs/canvas
|
|
9
9
|
*/
|
|
10
|
-
declare function
|
|
10
|
+
declare function createNodeCanvas(options: Omit<CanvasOptions, "canvas">): DrawCallCanvas;
|
|
11
11
|
//#endregion
|
|
12
|
-
export {
|
|
12
|
+
export { createNodeCanvas };
|
package/node.d.mts
CHANGED
|
@@ -7,6 +7,6 @@ import { n as DrawCallCanvas, t as CanvasOptions } from "./canvas.mjs";
|
|
|
7
7
|
* 此函数需要 @napi-rs/canvas 作为依赖
|
|
8
8
|
* 安装: bun add @napi-rs/canvas
|
|
9
9
|
*/
|
|
10
|
-
declare function
|
|
10
|
+
declare function createNodeCanvas(options: Omit<CanvasOptions, "canvas">): DrawCallCanvas;
|
|
11
11
|
//#endregion
|
|
12
|
-
export {
|
|
12
|
+
export { createNodeCanvas };
|
package/node.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as computeLayout, r as createCanvasMeasureContext, t as renderNode } from "./render.mjs";
|
|
2
|
-
import { createCanvas
|
|
2
|
+
import { createCanvas } from "@napi-rs/canvas";
|
|
3
3
|
|
|
4
4
|
//#region src/node.ts
|
|
5
5
|
/**
|
|
@@ -8,9 +8,9 @@ import { createCanvas as createCanvas$1 } from "@napi-rs/canvas";
|
|
|
8
8
|
* 此函数需要 @napi-rs/canvas 作为依赖
|
|
9
9
|
* 安装: bun add @napi-rs/canvas
|
|
10
10
|
*/
|
|
11
|
-
function
|
|
11
|
+
function createNodeCanvas(options) {
|
|
12
12
|
const { width, height, pixelRatio = 1 } = options;
|
|
13
|
-
const canvas = createCanvas
|
|
13
|
+
const canvas = createCanvas(width * pixelRatio, height * pixelRatio);
|
|
14
14
|
const ctx = canvas.getContext("2d");
|
|
15
15
|
if (pixelRatio !== 1) ctx.scale(pixelRatio, pixelRatio);
|
|
16
16
|
const measureCtx = createCanvasMeasureContext(ctx);
|
|
@@ -45,4 +45,4 @@ function createCanvas(options) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
//#endregion
|
|
48
|
-
export {
|
|
48
|
+
export { createNodeCanvas };
|