@codehz/draw-call 0.5.2 → 0.6.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 +7 -7
- package/{browser → dist/browser}/index.cjs +103 -148
- package/{node → dist/browser}/index.d.cts +16 -4
- package/dist/browser/index.d.ts +466 -0
- package/{browser → dist/browser}/index.js +102 -146
- package/{node → dist/node}/index.cjs +103 -148
- package/dist/node/index.d.cts +466 -0
- package/{node → dist/node}/index.d.mts +16 -4
- package/{node → dist/node}/index.mjs +102 -147
- package/examples/card.ts +1 -1
- package/examples/customdraw-basic.ts +39 -35
- package/examples/customdraw.ts +63 -59
- package/examples/demo.html +114 -0
- package/examples/demo.ts +1 -1
- package/examples/image-smoothing.ts +1 -1
- package/examples/richtext.ts +1 -1
- package/examples/transform.ts +1 -1
- package/package.json +55 -32
- package/browser/index.d.cts +0 -998
- package/browser/index.d.ts +0 -998
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { DOMMatrix, Path2D, createCanvas as createCanvas$1 } from "@napi-rs/canvas";
|
|
2
|
-
|
|
3
2
|
//#region src/compat/index.ts
|
|
4
3
|
function createRawCanvas(width, height) {
|
|
5
4
|
return createCanvas$1(width, height);
|
|
6
5
|
}
|
|
7
|
-
|
|
8
6
|
//#endregion
|
|
9
7
|
//#region src/types/base.ts
|
|
10
8
|
function linearGradient(angle, ...stops) {
|
|
@@ -75,7 +73,6 @@ function normalizeBorderRadius(value) {
|
|
|
75
73
|
];
|
|
76
74
|
return value;
|
|
77
75
|
}
|
|
78
|
-
|
|
79
76
|
//#endregion
|
|
80
77
|
//#region src/layout/components/box.ts
|
|
81
78
|
/**
|
|
@@ -188,7 +185,6 @@ function measureBoxSize(element, ctx, availableWidth, measureChild) {
|
|
|
188
185
|
height: typeof element.height === "number" ? element.height : intrinsicHeight
|
|
189
186
|
};
|
|
190
187
|
}
|
|
191
|
-
|
|
192
188
|
//#endregion
|
|
193
189
|
//#region src/layout/components/customDraw.ts
|
|
194
190
|
/**
|
|
@@ -205,7 +201,6 @@ function measureCustomDrawSize(element, ctx, availableWidth, measureChild) {
|
|
|
205
201
|
height: 0
|
|
206
202
|
};
|
|
207
203
|
}
|
|
208
|
-
|
|
209
204
|
//#endregion
|
|
210
205
|
//#region src/layout/components/image.ts
|
|
211
206
|
/**
|
|
@@ -231,7 +226,6 @@ function measureImageSize(element, _ctx, _availableWidth) {
|
|
|
231
226
|
height: 0
|
|
232
227
|
};
|
|
233
228
|
}
|
|
234
|
-
|
|
235
229
|
//#endregion
|
|
236
230
|
//#region src/layout/components/richtext.ts
|
|
237
231
|
/**
|
|
@@ -399,7 +393,6 @@ function wrapRichText(ctx, spans, maxWidth, lineHeightScale = 1.2, elementStyle
|
|
|
399
393
|
}];
|
|
400
394
|
return lines;
|
|
401
395
|
}
|
|
402
|
-
|
|
403
396
|
//#endregion
|
|
404
397
|
//#region src/layout/components/stack.ts
|
|
405
398
|
/**
|
|
@@ -436,7 +429,6 @@ function measureStackSize(element, ctx, availableWidth, measureChild) {
|
|
|
436
429
|
height: typeof element.height === "number" ? element.height : intrinsicHeight
|
|
437
430
|
};
|
|
438
431
|
}
|
|
439
|
-
|
|
440
432
|
//#endregion
|
|
441
433
|
//#region src/layout/components/svg.ts
|
|
442
434
|
/**
|
|
@@ -470,7 +462,6 @@ function measureSvgSize(element, _ctx, _availableWidth) {
|
|
|
470
462
|
height: 0
|
|
471
463
|
};
|
|
472
464
|
}
|
|
473
|
-
|
|
474
465
|
//#endregion
|
|
475
466
|
//#region src/render/utils/font.ts
|
|
476
467
|
/**
|
|
@@ -481,23 +472,42 @@ function measureSvgSize(element, _ctx, _availableWidth) {
|
|
|
481
472
|
function buildFontString(font) {
|
|
482
473
|
return `${font.style ?? "normal"} ${font.weight ?? "normal"} ${font.size ?? 16}px ${font.family ?? "sans-serif"}`;
|
|
483
474
|
}
|
|
484
|
-
|
|
485
475
|
//#endregion
|
|
486
476
|
//#region src/layout/utils/measure.ts
|
|
477
|
+
const MEASURE_CACHE_LIMIT = 256;
|
|
487
478
|
function createCanvasMeasureContext(ctx) {
|
|
479
|
+
const cache = /* @__PURE__ */ new Map();
|
|
480
|
+
let lastFontString = null;
|
|
488
481
|
return { measureText(text, font) {
|
|
489
|
-
|
|
490
|
-
|
|
482
|
+
const fontString = buildFontString(font);
|
|
483
|
+
const key = fontString + "\0" + text;
|
|
484
|
+
const hit = cache.get(key);
|
|
485
|
+
if (hit !== void 0) {
|
|
486
|
+
cache.delete(key);
|
|
487
|
+
cache.set(key, hit);
|
|
488
|
+
return hit;
|
|
489
|
+
}
|
|
490
|
+
if (fontString !== lastFontString) {
|
|
491
|
+
ctx.font = fontString;
|
|
492
|
+
ctx.textBaseline = "middle";
|
|
493
|
+
lastFontString = fontString;
|
|
494
|
+
}
|
|
491
495
|
const metrics = ctx.measureText(text);
|
|
492
496
|
const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
493
497
|
const fontSize = font.size || 16;
|
|
494
|
-
|
|
498
|
+
const result = {
|
|
495
499
|
width: metrics.width,
|
|
496
500
|
height: height || fontSize,
|
|
497
501
|
offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
|
|
498
502
|
ascent: metrics.actualBoundingBoxAscent,
|
|
499
503
|
descent: metrics.actualBoundingBoxDescent
|
|
500
504
|
};
|
|
505
|
+
if (cache.size >= MEASURE_CACHE_LIMIT) {
|
|
506
|
+
const oldest = cache.keys().next().value;
|
|
507
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
508
|
+
}
|
|
509
|
+
cache.set(key, result);
|
|
510
|
+
return result;
|
|
501
511
|
} };
|
|
502
512
|
}
|
|
503
513
|
function wrapText(ctx, text, maxWidth, font) {
|
|
@@ -573,7 +583,6 @@ function truncateText(ctx, text, maxWidth, font, ellipsis = "...") {
|
|
|
573
583
|
offset
|
|
574
584
|
};
|
|
575
585
|
}
|
|
576
|
-
|
|
577
586
|
//#endregion
|
|
578
587
|
//#region src/layout/components/text.ts
|
|
579
588
|
/**
|
|
@@ -599,7 +608,6 @@ function measureTextSize(element, ctx, availableWidth) {
|
|
|
599
608
|
height: Math.max(height, lineHeightPx)
|
|
600
609
|
};
|
|
601
610
|
}
|
|
602
|
-
|
|
603
611
|
//#endregion
|
|
604
612
|
//#region src/layout/components/transform.ts
|
|
605
613
|
/**
|
|
@@ -610,7 +618,6 @@ function measureTextSize(element, ctx, availableWidth) {
|
|
|
610
618
|
function measureTransformSize(element, ctx, availableWidth, measureIntrinsicSize) {
|
|
611
619
|
return measureIntrinsicSize(element.children, ctx, availableWidth);
|
|
612
620
|
}
|
|
613
|
-
|
|
614
621
|
//#endregion
|
|
615
622
|
//#region src/layout/components/index.ts
|
|
616
623
|
/**
|
|
@@ -632,7 +639,6 @@ function measureIntrinsicSize(element, ctx, availableWidth) {
|
|
|
632
639
|
};
|
|
633
640
|
}
|
|
634
641
|
}
|
|
635
|
-
|
|
636
642
|
//#endregion
|
|
637
643
|
//#region src/layout/utils/offset.ts
|
|
638
644
|
/**
|
|
@@ -645,7 +651,6 @@ function applyOffset(node, dx, dy) {
|
|
|
645
651
|
node.layout.contentY += dy;
|
|
646
652
|
for (const child of node.children) applyOffset(child, dx, dy);
|
|
647
653
|
}
|
|
648
|
-
|
|
649
654
|
//#endregion
|
|
650
655
|
//#region src/types/layout.ts
|
|
651
656
|
function resolveSize(size, available, auto) {
|
|
@@ -660,7 +665,6 @@ function sizeNeedsParent(size) {
|
|
|
660
665
|
if (typeof size === "string" && size.endsWith("%")) return true;
|
|
661
666
|
return false;
|
|
662
667
|
}
|
|
663
|
-
|
|
664
668
|
//#endregion
|
|
665
669
|
//#region src/layout/engine.ts
|
|
666
670
|
/**
|
|
@@ -780,7 +784,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
|
|
|
780
784
|
}
|
|
781
785
|
if (layoutElement.type === "richtext") {
|
|
782
786
|
const lineHeight = layoutElement.lineHeight ?? 1.2;
|
|
783
|
-
|
|
787
|
+
const elementStyle = {
|
|
788
|
+
font: layoutElement.font,
|
|
789
|
+
color: layoutElement.color,
|
|
790
|
+
background: layoutElement.background,
|
|
791
|
+
underline: layoutElement.underline,
|
|
792
|
+
strikethrough: layoutElement.strikethrough
|
|
793
|
+
};
|
|
794
|
+
let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight, elementStyle);
|
|
784
795
|
if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
|
|
785
796
|
lines = lines.slice(0, layoutElement.maxLines);
|
|
786
797
|
if (layoutElement.ellipsis && lines.length > 0) {
|
|
@@ -1078,7 +1089,6 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
|
|
|
1078
1089
|
}
|
|
1079
1090
|
return node;
|
|
1080
1091
|
}
|
|
1081
|
-
|
|
1082
1092
|
//#endregion
|
|
1083
1093
|
//#region src/render/utils/colors.ts
|
|
1084
1094
|
function isGradientDescriptor$1(color) {
|
|
@@ -1114,7 +1124,6 @@ function resolveColor$1(ctx, color, x, y, width, height) {
|
|
|
1114
1124
|
if (isGradientDescriptor$1(color)) return resolveGradient$1(ctx, color, x, y, width, height);
|
|
1115
1125
|
return color;
|
|
1116
1126
|
}
|
|
1117
|
-
|
|
1118
1127
|
//#endregion
|
|
1119
1128
|
//#region src/render/utils/shadows.ts
|
|
1120
1129
|
function applyShadow$1(ctx, shadow) {
|
|
@@ -1136,7 +1145,6 @@ function clearShadow$1(ctx) {
|
|
|
1136
1145
|
ctx.shadowBlur = 0;
|
|
1137
1146
|
ctx.shadowColor = "transparent";
|
|
1138
1147
|
}
|
|
1139
|
-
|
|
1140
1148
|
//#endregion
|
|
1141
1149
|
//#region src/render/utils/shapes.ts
|
|
1142
1150
|
function roundRectPath(ctx, x, y, width, height, radius) {
|
|
@@ -1153,7 +1161,6 @@ function roundRectPath(ctx, x, y, width, height, radius) {
|
|
|
1153
1161
|
ctx.quadraticCurveTo(x, y, x + tl, y);
|
|
1154
1162
|
ctx.closePath();
|
|
1155
1163
|
}
|
|
1156
|
-
|
|
1157
1164
|
//#endregion
|
|
1158
1165
|
//#region src/render/components/box.ts
|
|
1159
1166
|
function renderBox(ctx, node) {
|
|
@@ -1182,125 +1189,90 @@ function renderBox(ctx, node) {
|
|
|
1182
1189
|
}
|
|
1183
1190
|
if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
|
|
1184
1191
|
}
|
|
1185
|
-
|
|
1186
1192
|
//#endregion
|
|
1187
|
-
//#region src/render/components/
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1193
|
+
//#region src/render/components/CustomDrawContext.ts
|
|
1194
|
+
function cloneMatrix(matrix) {
|
|
1195
|
+
return new DOMMatrix([
|
|
1196
|
+
matrix.a,
|
|
1197
|
+
matrix.b,
|
|
1198
|
+
matrix.c,
|
|
1199
|
+
matrix.d,
|
|
1200
|
+
matrix.e,
|
|
1201
|
+
matrix.f
|
|
1202
|
+
]);
|
|
1203
|
+
}
|
|
1204
|
+
function toRelativeMatrix(transform) {
|
|
1205
|
+
if (transform === void 0) return new DOMMatrix();
|
|
1206
|
+
if (transform instanceof DOMMatrix) return cloneMatrix(transform);
|
|
1207
|
+
return new DOMMatrix(transform);
|
|
1208
|
+
}
|
|
1209
|
+
var ManagedCustomDrawContext = class {
|
|
1210
|
+
canvas;
|
|
1205
1211
|
baseTransform;
|
|
1206
|
-
/**
|
|
1207
|
-
* 相对变换矩阵(用户通过 setTransform 设置)
|
|
1208
|
-
*/
|
|
1209
1212
|
relativeTransform;
|
|
1210
|
-
|
|
1211
|
-
* save/restore 计数器
|
|
1212
|
-
*/
|
|
1213
|
+
transformStack = [];
|
|
1213
1214
|
saveCount = 0;
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
* @param baseTransform 初始的基础变换矩阵
|
|
1218
|
-
*/
|
|
1219
|
-
constructor(ctx, baseTransform) {
|
|
1220
|
-
this.ctx = ctx;
|
|
1221
|
-
this.baseTransform = baseTransform;
|
|
1215
|
+
constructor(canvas, baseTransform) {
|
|
1216
|
+
this.canvas = canvas;
|
|
1217
|
+
this.baseTransform = cloneMatrix(baseTransform);
|
|
1222
1218
|
this.relativeTransform = new DOMMatrix();
|
|
1223
1219
|
}
|
|
1224
|
-
/**
|
|
1225
|
-
* save() - 保存当前状态并增加计数
|
|
1226
|
-
*/
|
|
1227
1220
|
save() {
|
|
1228
1221
|
this.saveCount++;
|
|
1229
|
-
this.
|
|
1222
|
+
this.transformStack.push(cloneMatrix(this.relativeTransform));
|
|
1223
|
+
this.canvas.save();
|
|
1230
1224
|
}
|
|
1231
|
-
/**
|
|
1232
|
-
* restore() - 恢复上一个状态并减少计数
|
|
1233
|
-
*/
|
|
1234
1225
|
restore() {
|
|
1235
|
-
if (this.saveCount
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1226
|
+
if (this.saveCount === 0) return;
|
|
1227
|
+
this.saveCount--;
|
|
1228
|
+
this.relativeTransform = this.transformStack.pop() ?? new DOMMatrix();
|
|
1229
|
+
this.canvas.restore();
|
|
1230
|
+
this.applyRelativeTransform();
|
|
1239
1231
|
}
|
|
1240
|
-
/**
|
|
1241
|
-
* setTransform() - 设置相对变换
|
|
1242
|
-
*/
|
|
1243
|
-
setTransform(...args) {
|
|
1244
|
-
let matrix;
|
|
1245
|
-
if (args.length === 1 && args[0] instanceof DOMMatrix) matrix = args[0];
|
|
1246
|
-
else if (args.length === 6) matrix = new DOMMatrix([
|
|
1247
|
-
args[0],
|
|
1248
|
-
args[1],
|
|
1249
|
-
args[2],
|
|
1250
|
-
args[3],
|
|
1251
|
-
args[4],
|
|
1252
|
-
args[5]
|
|
1253
|
-
]);
|
|
1254
|
-
else return;
|
|
1255
|
-
this.relativeTransform = matrix;
|
|
1256
|
-
const actualTransform = this.baseTransform.multiply(matrix);
|
|
1257
|
-
this.ctx.setTransform(actualTransform);
|
|
1258
|
-
}
|
|
1259
|
-
/**
|
|
1260
|
-
* getTransform() - 返回相对变换(而不是绝对变换)
|
|
1261
|
-
*/
|
|
1262
1232
|
getTransform() {
|
|
1263
|
-
return this.relativeTransform;
|
|
1233
|
+
return cloneMatrix(this.relativeTransform);
|
|
1234
|
+
}
|
|
1235
|
+
setTransform(transform) {
|
|
1236
|
+
this.relativeTransform = toRelativeMatrix(transform);
|
|
1237
|
+
this.applyRelativeTransform();
|
|
1238
|
+
}
|
|
1239
|
+
resetTransform() {
|
|
1240
|
+
this.relativeTransform = new DOMMatrix();
|
|
1241
|
+
this.applyRelativeTransform();
|
|
1242
|
+
}
|
|
1243
|
+
translate(x, y) {
|
|
1244
|
+
this.relativeTransform = this.relativeTransform.translate(x, y);
|
|
1245
|
+
this.applyRelativeTransform();
|
|
1246
|
+
}
|
|
1247
|
+
rotate(angle) {
|
|
1248
|
+
this.relativeTransform = this.relativeTransform.rotate(angle * 180 / Math.PI);
|
|
1249
|
+
this.applyRelativeTransform();
|
|
1250
|
+
}
|
|
1251
|
+
scale(x, y) {
|
|
1252
|
+
this.relativeTransform = this.relativeTransform.scale(x, y ?? x);
|
|
1253
|
+
this.applyRelativeTransform();
|
|
1254
|
+
}
|
|
1255
|
+
transform(a, b, c, d, e, f) {
|
|
1256
|
+
this.relativeTransform = this.relativeTransform.multiply(new DOMMatrix([
|
|
1257
|
+
a,
|
|
1258
|
+
b,
|
|
1259
|
+
c,
|
|
1260
|
+
d,
|
|
1261
|
+
e,
|
|
1262
|
+
f
|
|
1263
|
+
]));
|
|
1264
|
+
this.applyRelativeTransform();
|
|
1264
1265
|
}
|
|
1265
|
-
/**
|
|
1266
|
-
* 析构函数级的清理 - 自动恢复所有未恢复的 save
|
|
1267
|
-
*/
|
|
1268
1266
|
destroy() {
|
|
1269
|
-
while (this.saveCount > 0)
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
}
|
|
1267
|
+
while (this.saveCount > 0) this.restore();
|
|
1268
|
+
}
|
|
1269
|
+
applyRelativeTransform() {
|
|
1270
|
+
this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
|
|
1274
1271
|
}
|
|
1275
1272
|
};
|
|
1276
|
-
function
|
|
1277
|
-
|
|
1278
|
-
return new Proxy(proxy, {
|
|
1279
|
-
get(target, prop, receiver) {
|
|
1280
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
|
|
1281
|
-
const ownValue = Reflect.get(target, prop, receiver);
|
|
1282
|
-
if (ownValue !== void 0) return ownValue;
|
|
1283
|
-
const contextValue = target.ctx[prop];
|
|
1284
|
-
if (typeof contextValue === "function") return contextValue.bind(target.ctx);
|
|
1285
|
-
return contextValue;
|
|
1286
|
-
},
|
|
1287
|
-
set(target, prop, value, _receiver) {
|
|
1288
|
-
target.ctx[prop] = value;
|
|
1289
|
-
return true;
|
|
1290
|
-
},
|
|
1291
|
-
has(target, prop) {
|
|
1292
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
|
|
1293
|
-
return prop in target.ctx;
|
|
1294
|
-
},
|
|
1295
|
-
ownKeys(target) {
|
|
1296
|
-
return Reflect.ownKeys(target.ctx);
|
|
1297
|
-
},
|
|
1298
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
1299
|
-
return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
|
|
1300
|
-
}
|
|
1301
|
-
});
|
|
1273
|
+
function createCustomDrawContext(canvas, baseTransform) {
|
|
1274
|
+
return new ManagedCustomDrawContext(canvas, baseTransform);
|
|
1302
1275
|
}
|
|
1303
|
-
|
|
1304
1276
|
//#endregion
|
|
1305
1277
|
//#region src/render/components/customDraw.ts
|
|
1306
1278
|
/**
|
|
@@ -1311,7 +1283,7 @@ function renderCustomDraw(ctx, node) {
|
|
|
1311
1283
|
const element = node.element;
|
|
1312
1284
|
ctx.save();
|
|
1313
1285
|
ctx.translate(node.layout.x, node.layout.y);
|
|
1314
|
-
const
|
|
1286
|
+
const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
|
|
1315
1287
|
const inner = () => {
|
|
1316
1288
|
if (node.children && node.children.length > 0) {
|
|
1317
1289
|
ctx.save();
|
|
@@ -1320,15 +1292,14 @@ function renderCustomDraw(ctx, node) {
|
|
|
1320
1292
|
ctx.restore();
|
|
1321
1293
|
}
|
|
1322
1294
|
};
|
|
1323
|
-
element.draw(
|
|
1295
|
+
element.draw(customCtx, {
|
|
1324
1296
|
inner,
|
|
1325
1297
|
width: node.layout.contentWidth,
|
|
1326
1298
|
height: node.layout.contentHeight
|
|
1327
1299
|
});
|
|
1328
|
-
|
|
1300
|
+
customCtx.destroy();
|
|
1329
1301
|
ctx.restore();
|
|
1330
1302
|
}
|
|
1331
|
-
|
|
1332
1303
|
//#endregion
|
|
1333
1304
|
//#region src/render/components/image.ts
|
|
1334
1305
|
function renderImage(ctx, node) {
|
|
@@ -1414,7 +1385,6 @@ function renderImage(ctx, node) {
|
|
|
1414
1385
|
}
|
|
1415
1386
|
if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
|
|
1416
1387
|
}
|
|
1417
|
-
|
|
1418
1388
|
//#endregion
|
|
1419
1389
|
//#region src/render/components/richtext.ts
|
|
1420
1390
|
function renderRichText(ctx, node) {
|
|
@@ -1465,7 +1435,6 @@ function renderRichText(ctx, node) {
|
|
|
1465
1435
|
currentY += line.height;
|
|
1466
1436
|
}
|
|
1467
1437
|
}
|
|
1468
|
-
|
|
1469
1438
|
//#endregion
|
|
1470
1439
|
//#region src/render/components/svg.ts
|
|
1471
1440
|
function isGradientDescriptor(color) {
|
|
@@ -1737,7 +1706,6 @@ function renderSvg(ctx, node) {
|
|
|
1737
1706
|
for (const child of element.children) renderSvgChild(ctx, child, transform, bounds, baseTransform);
|
|
1738
1707
|
ctx.restore();
|
|
1739
1708
|
}
|
|
1740
|
-
|
|
1741
1709
|
//#endregion
|
|
1742
1710
|
//#region src/render/components/text.ts
|
|
1743
1711
|
function renderText(ctx, node) {
|
|
@@ -1772,7 +1740,6 @@ function renderText(ctx, node) {
|
|
|
1772
1740
|
}
|
|
1773
1741
|
if (element.shadow) clearShadow$1(ctx);
|
|
1774
1742
|
}
|
|
1775
|
-
|
|
1776
1743
|
//#endregion
|
|
1777
1744
|
//#region src/render/components/transform.ts
|
|
1778
1745
|
/**
|
|
@@ -1848,7 +1815,6 @@ function renderTransform(ctx, node) {
|
|
|
1848
1815
|
renderNode(ctx, childNode);
|
|
1849
1816
|
ctx.restore();
|
|
1850
1817
|
}
|
|
1851
|
-
|
|
1852
1818
|
//#endregion
|
|
1853
1819
|
//#region src/render/index.ts
|
|
1854
1820
|
function renderNode(ctx, node) {
|
|
@@ -1888,7 +1854,6 @@ function renderNode(ctx, node) {
|
|
|
1888
1854
|
break;
|
|
1889
1855
|
}
|
|
1890
1856
|
}
|
|
1891
|
-
|
|
1892
1857
|
//#endregion
|
|
1893
1858
|
//#region src/canvas.ts
|
|
1894
1859
|
/**
|
|
@@ -1958,7 +1923,6 @@ function createCanvas(options) {
|
|
|
1958
1923
|
}
|
|
1959
1924
|
};
|
|
1960
1925
|
}
|
|
1961
|
-
|
|
1962
1926
|
//#endregion
|
|
1963
1927
|
//#region src/components/Box.ts
|
|
1964
1928
|
function Box(props) {
|
|
@@ -1967,7 +1931,6 @@ function Box(props) {
|
|
|
1967
1931
|
...props
|
|
1968
1932
|
};
|
|
1969
1933
|
}
|
|
1970
|
-
|
|
1971
1934
|
//#endregion
|
|
1972
1935
|
//#region src/components/CustomDraw.ts
|
|
1973
1936
|
function CustomDraw(props) {
|
|
@@ -1976,7 +1939,6 @@ function CustomDraw(props) {
|
|
|
1976
1939
|
...props
|
|
1977
1940
|
};
|
|
1978
1941
|
}
|
|
1979
|
-
|
|
1980
1942
|
//#endregion
|
|
1981
1943
|
//#region src/components/Image.ts
|
|
1982
1944
|
function Image(props) {
|
|
@@ -1985,7 +1947,6 @@ function Image(props) {
|
|
|
1985
1947
|
...props
|
|
1986
1948
|
};
|
|
1987
1949
|
}
|
|
1988
|
-
|
|
1989
1950
|
//#endregion
|
|
1990
1951
|
//#region src/components/RichText.ts
|
|
1991
1952
|
function RichText(props) {
|
|
@@ -1994,7 +1955,6 @@ function RichText(props) {
|
|
|
1994
1955
|
...props
|
|
1995
1956
|
};
|
|
1996
1957
|
}
|
|
1997
|
-
|
|
1998
1958
|
//#endregion
|
|
1999
1959
|
//#region src/components/Stack.ts
|
|
2000
1960
|
function Stack(props) {
|
|
@@ -2003,7 +1963,6 @@ function Stack(props) {
|
|
|
2003
1963
|
...props
|
|
2004
1964
|
};
|
|
2005
1965
|
}
|
|
2006
|
-
|
|
2007
1966
|
//#endregion
|
|
2008
1967
|
//#region src/components/Svg.ts
|
|
2009
1968
|
function Svg(props) {
|
|
@@ -2050,7 +2009,6 @@ const svg = {
|
|
|
2050
2009
|
...props
|
|
2051
2010
|
})
|
|
2052
2011
|
};
|
|
2053
|
-
|
|
2054
2012
|
//#endregion
|
|
2055
2013
|
//#region src/components/Text.ts
|
|
2056
2014
|
function Text(props) {
|
|
@@ -2059,7 +2017,6 @@ function Text(props) {
|
|
|
2059
2017
|
...props
|
|
2060
2018
|
};
|
|
2061
2019
|
}
|
|
2062
|
-
|
|
2063
2020
|
//#endregion
|
|
2064
2021
|
//#region src/components/Transform.ts
|
|
2065
2022
|
function Transform(props) {
|
|
@@ -2068,7 +2025,6 @@ function Transform(props) {
|
|
|
2068
2025
|
...props
|
|
2069
2026
|
};
|
|
2070
2027
|
}
|
|
2071
|
-
|
|
2072
2028
|
//#endregion
|
|
2073
2029
|
//#region src/layout/utils/print.ts
|
|
2074
2030
|
/**
|
|
@@ -2128,6 +2084,5 @@ function printLayout(node) {
|
|
|
2128
2084
|
function layoutToString(node, _indent = " ") {
|
|
2129
2085
|
return printLayoutToString(node, "", true).join("\n");
|
|
2130
2086
|
}
|
|
2131
|
-
|
|
2132
2087
|
//#endregion
|
|
2133
|
-
export { Box, CustomDraw, Image, RichText, Stack, Svg, Text, Transform, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
|
|
2088
|
+
export { Box, CustomDraw, Image, RichText, Stack, Svg, Text, Transform, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
|
package/examples/card.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 示例:使用 draw-call 绘制一个卡片
|
|
3
3
|
* 运行: bun examples/card.ts
|
|
4
4
|
*/
|
|
5
|
-
import { Box, createCanvas, linearGradient, printLayout, Svg, svg, Text } from "
|
|
5
|
+
import { Box, createCanvas, linearGradient, printLayout, Svg, svg, Text } from "@/index";
|
|
6
6
|
import { GlobalFonts } from "@napi-rs/canvas";
|
|
7
7
|
import { fileURLToPath } from "bun";
|
|
8
8
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 展示如何使用 CustomDraw 进行简单的自定义绘制
|
|
4
4
|
* 运行: bun examples/customdraw-basic.ts
|
|
5
5
|
*/
|
|
6
|
-
import { Box, createCanvas, CustomDraw, printLayout, Text } from "
|
|
6
|
+
import { Box, createCanvas, CustomDraw, printLayout, Text } from "@/index";
|
|
7
7
|
import { GlobalFonts } from "@napi-rs/canvas";
|
|
8
8
|
import { fileURLToPath } from "bun";
|
|
9
9
|
|
|
@@ -20,13 +20,14 @@ const SimpleRect = CustomDraw({
|
|
|
20
20
|
width: 150,
|
|
21
21
|
height: 80,
|
|
22
22
|
draw(ctx, { width, height }) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
const canvas = ctx.canvas;
|
|
24
|
+
canvas.fillStyle = "#667eea";
|
|
25
|
+
canvas.fillRect(0, 0, width, height);
|
|
26
|
+
canvas.fillStyle = "#000000";
|
|
27
|
+
canvas.font = "bold 16px sans-serif";
|
|
28
|
+
canvas.textAlign = "center";
|
|
29
|
+
canvas.textBaseline = "middle";
|
|
30
|
+
canvas.fillText("Simple Rect", width / 2, height / 2);
|
|
30
31
|
},
|
|
31
32
|
});
|
|
32
33
|
|
|
@@ -35,20 +36,21 @@ const RotatedRect = CustomDraw({
|
|
|
35
36
|
width: 150,
|
|
36
37
|
height: 80,
|
|
37
38
|
draw(ctx, { width, height }) {
|
|
39
|
+
const canvas = ctx.canvas;
|
|
38
40
|
// save/restore 会自动平衡,即使不显式调用也能正确恢复
|
|
39
41
|
ctx.save();
|
|
40
42
|
ctx.translate(width / 2, height / 2);
|
|
41
43
|
ctx.rotate((Math.PI / 4) * 0.3); // 15 度
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
canvas.fillStyle = "#764ba2";
|
|
45
|
+
canvas.fillRect(-width / 2, -height / 2, width, height);
|
|
44
46
|
ctx.restore();
|
|
45
47
|
|
|
46
48
|
// 恢复后可以正常绘制
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
canvas.fillStyle = "#000000";
|
|
50
|
+
canvas.font = "16px sans-serif";
|
|
51
|
+
canvas.textAlign = "center";
|
|
52
|
+
canvas.textBaseline = "middle";
|
|
53
|
+
canvas.fillText("Rotated", width / 2, height / 2);
|
|
52
54
|
},
|
|
53
55
|
});
|
|
54
56
|
|
|
@@ -57,24 +59,25 @@ const CircleProgress = CustomDraw({
|
|
|
57
59
|
width: 140,
|
|
58
60
|
height: 140,
|
|
59
61
|
draw(ctx, { width, height, inner }) {
|
|
62
|
+
const canvas = ctx.canvas;
|
|
60
63
|
const centerX = width / 2;
|
|
61
64
|
const centerY = height / 2;
|
|
62
65
|
const radius = Math.min(width, height) / 2 - 8;
|
|
63
66
|
|
|
64
67
|
// 绘制背景圆
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
canvas.strokeStyle = "rgba(0, 0, 0, 0.1)";
|
|
69
|
+
canvas.lineWidth = 8;
|
|
70
|
+
canvas.beginPath();
|
|
71
|
+
canvas.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
72
|
+
canvas.stroke();
|
|
70
73
|
|
|
71
74
|
// 绘制进度圆(65% 完成)
|
|
72
75
|
const percentage = 65;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
canvas.strokeStyle = "#ff6b6b";
|
|
77
|
+
canvas.lineCap = "round";
|
|
78
|
+
canvas.beginPath();
|
|
79
|
+
canvas.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + (percentage / 100) * Math.PI * 2);
|
|
80
|
+
canvas.stroke();
|
|
78
81
|
|
|
79
82
|
// 调用 inner() 在中央渲染子元素(百分比文本)
|
|
80
83
|
inner?.();
|
|
@@ -99,14 +102,15 @@ const StarShape = CustomDraw({
|
|
|
99
102
|
width: 150,
|
|
100
103
|
height: 150,
|
|
101
104
|
draw(ctx, { width, height }) {
|
|
105
|
+
const canvas = ctx.canvas;
|
|
102
106
|
const centerX = width / 2;
|
|
103
107
|
const centerY = height / 2;
|
|
104
108
|
const points = 5;
|
|
105
109
|
const outerRadius = Math.min(width, height) / 2 - 5;
|
|
106
110
|
const innerRadius = outerRadius * 0.4;
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
canvas.fillStyle = "#ffd93d";
|
|
113
|
+
canvas.beginPath();
|
|
110
114
|
|
|
111
115
|
for (let i = 0; i < points * 2; i++) {
|
|
112
116
|
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
@@ -115,19 +119,19 @@ const StarShape = CustomDraw({
|
|
|
115
119
|
const y = centerY + radius * Math.sin(angle);
|
|
116
120
|
|
|
117
121
|
if (i === 0) {
|
|
118
|
-
|
|
122
|
+
canvas.moveTo(x, y);
|
|
119
123
|
} else {
|
|
120
|
-
|
|
124
|
+
canvas.lineTo(x, y);
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
canvas.closePath();
|
|
129
|
+
canvas.fill();
|
|
126
130
|
|
|
127
131
|
// 描边
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
canvas.strokeStyle = "#fa8c16";
|
|
133
|
+
canvas.lineWidth = 2;
|
|
134
|
+
canvas.stroke();
|
|
131
135
|
},
|
|
132
136
|
});
|
|
133
137
|
|
|
@@ -246,7 +250,7 @@ const layout = canvas.render(
|
|
|
246
250
|
}),
|
|
247
251
|
Text({
|
|
248
252
|
content:
|
|
249
|
-
"CustomDraw
|
|
253
|
+
"CustomDraw 提供受控的 transform/save/restore 能力,并通过 ctx.canvas 暴露原生 Canvas API。使用 inner() 方法可以在自定义绘制中渲染子元素。",
|
|
250
254
|
font: { size: 12, family: "unifont" },
|
|
251
255
|
color: "#1890ff",
|
|
252
256
|
wrap: true,
|