@codehz/draw-call 0.5.3 → 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/dist/browser/index.cjs +101 -107
- package/dist/browser/index.d.cts +16 -4
- package/dist/browser/index.d.ts +16 -4
- package/dist/browser/index.js +101 -107
- package/dist/node/index.cjs +101 -107
- package/dist/node/index.d.cts +16 -4
- package/dist/node/index.d.mts +16 -4
- package/dist/node/index.mjs +101 -107
- package/examples/customdraw-basic.ts +38 -34
- package/examples/customdraw.ts +62 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -244,7 +244,7 @@ Transform({
|
|
|
244
244
|
|
|
245
245
|
### CustomDraw
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
自定义绘制组件,提供受控的绘制上下文。原生 Canvas API 可通过 `ctx.canvas` 访问,`save`/`restore`/transform 由 `CustomDrawContext` 管理。
|
|
248
248
|
|
|
249
249
|
```typescript
|
|
250
250
|
CustomDraw({
|
|
@@ -252,12 +252,12 @@ CustomDraw({
|
|
|
252
252
|
height: 100,
|
|
253
253
|
draw: (ctx, options) => {
|
|
254
254
|
// 使用原生 Canvas API 进行绘制
|
|
255
|
-
ctx.fillStyle = "#667eea";
|
|
256
|
-
ctx.fillRect(10, 10, 180, 80);
|
|
257
|
-
ctx.fillStyle = "#ffffff";
|
|
258
|
-
ctx.font = "bold 16px sans-serif";
|
|
259
|
-
ctx.textAlign = "center";
|
|
260
|
-
ctx.fillText("Custom Draw", 100, 60);
|
|
255
|
+
ctx.canvas.fillStyle = "#667eea";
|
|
256
|
+
ctx.canvas.fillRect(10, 10, 180, 80);
|
|
257
|
+
ctx.canvas.fillStyle = "#ffffff";
|
|
258
|
+
ctx.canvas.font = "bold 16px sans-serif";
|
|
259
|
+
ctx.canvas.textAlign = "center";
|
|
260
|
+
ctx.canvas.fillText("Custom Draw", 100, 60);
|
|
261
261
|
},
|
|
262
262
|
});
|
|
263
263
|
```
|
package/dist/browser/index.cjs
CHANGED
|
@@ -479,20 +479,40 @@ function buildFontString(font) {
|
|
|
479
479
|
}
|
|
480
480
|
//#endregion
|
|
481
481
|
//#region src/layout/utils/measure.ts
|
|
482
|
+
const MEASURE_CACHE_LIMIT = 256;
|
|
482
483
|
function createCanvasMeasureContext(ctx) {
|
|
484
|
+
const cache = /* @__PURE__ */ new Map();
|
|
485
|
+
let lastFontString = null;
|
|
483
486
|
return { measureText(text, font) {
|
|
484
|
-
|
|
485
|
-
|
|
487
|
+
const fontString = buildFontString(font);
|
|
488
|
+
const key = fontString + "\0" + text;
|
|
489
|
+
const hit = cache.get(key);
|
|
490
|
+
if (hit !== void 0) {
|
|
491
|
+
cache.delete(key);
|
|
492
|
+
cache.set(key, hit);
|
|
493
|
+
return hit;
|
|
494
|
+
}
|
|
495
|
+
if (fontString !== lastFontString) {
|
|
496
|
+
ctx.font = fontString;
|
|
497
|
+
ctx.textBaseline = "middle";
|
|
498
|
+
lastFontString = fontString;
|
|
499
|
+
}
|
|
486
500
|
const metrics = ctx.measureText(text);
|
|
487
501
|
const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
488
502
|
const fontSize = font.size || 16;
|
|
489
|
-
|
|
503
|
+
const result = {
|
|
490
504
|
width: metrics.width,
|
|
491
505
|
height: height || fontSize,
|
|
492
506
|
offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
|
|
493
507
|
ascent: metrics.actualBoundingBoxAscent,
|
|
494
508
|
descent: metrics.actualBoundingBoxDescent
|
|
495
509
|
};
|
|
510
|
+
if (cache.size >= MEASURE_CACHE_LIMIT) {
|
|
511
|
+
const oldest = cache.keys().next().value;
|
|
512
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
513
|
+
}
|
|
514
|
+
cache.set(key, result);
|
|
515
|
+
return result;
|
|
496
516
|
} };
|
|
497
517
|
}
|
|
498
518
|
function wrapText(ctx, text, maxWidth, font) {
|
|
@@ -769,7 +789,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
|
|
|
769
789
|
}
|
|
770
790
|
if (layoutElement.type === "richtext") {
|
|
771
791
|
const lineHeight = layoutElement.lineHeight ?? 1.2;
|
|
772
|
-
|
|
792
|
+
const elementStyle = {
|
|
793
|
+
font: layoutElement.font,
|
|
794
|
+
color: layoutElement.color,
|
|
795
|
+
background: layoutElement.background,
|
|
796
|
+
underline: layoutElement.underline,
|
|
797
|
+
strikethrough: layoutElement.strikethrough
|
|
798
|
+
};
|
|
799
|
+
let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight, elementStyle);
|
|
773
800
|
if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
|
|
774
801
|
lines = lines.slice(0, layoutElement.maxLines);
|
|
775
802
|
if (layoutElement.ellipsis && lines.length > 0) {
|
|
@@ -1168,121 +1195,88 @@ function renderBox(ctx, node) {
|
|
|
1168
1195
|
if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
|
|
1169
1196
|
}
|
|
1170
1197
|
//#endregion
|
|
1171
|
-
//#region src/render/components/
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1198
|
+
//#region src/render/components/CustomDrawContext.ts
|
|
1199
|
+
function cloneMatrix(matrix) {
|
|
1200
|
+
return new DOMMatrix([
|
|
1201
|
+
matrix.a,
|
|
1202
|
+
matrix.b,
|
|
1203
|
+
matrix.c,
|
|
1204
|
+
matrix.d,
|
|
1205
|
+
matrix.e,
|
|
1206
|
+
matrix.f
|
|
1207
|
+
]);
|
|
1208
|
+
}
|
|
1209
|
+
function toRelativeMatrix(transform) {
|
|
1210
|
+
if (transform === void 0) return new DOMMatrix();
|
|
1211
|
+
if (transform instanceof DOMMatrix) return cloneMatrix(transform);
|
|
1212
|
+
return new DOMMatrix(transform);
|
|
1213
|
+
}
|
|
1214
|
+
var ManagedCustomDrawContext = class {
|
|
1215
|
+
canvas;
|
|
1189
1216
|
baseTransform;
|
|
1190
|
-
/**
|
|
1191
|
-
* 相对变换矩阵(用户通过 setTransform 设置)
|
|
1192
|
-
*/
|
|
1193
1217
|
relativeTransform;
|
|
1194
|
-
|
|
1195
|
-
* save/restore 计数器
|
|
1196
|
-
*/
|
|
1218
|
+
transformStack = [];
|
|
1197
1219
|
saveCount = 0;
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
* @param baseTransform 初始的基础变换矩阵
|
|
1202
|
-
*/
|
|
1203
|
-
constructor(ctx, baseTransform) {
|
|
1204
|
-
this.ctx = ctx;
|
|
1205
|
-
this.baseTransform = baseTransform;
|
|
1220
|
+
constructor(canvas, baseTransform) {
|
|
1221
|
+
this.canvas = canvas;
|
|
1222
|
+
this.baseTransform = cloneMatrix(baseTransform);
|
|
1206
1223
|
this.relativeTransform = new DOMMatrix();
|
|
1207
1224
|
}
|
|
1208
|
-
/**
|
|
1209
|
-
* save() - 保存当前状态并增加计数
|
|
1210
|
-
*/
|
|
1211
1225
|
save() {
|
|
1212
1226
|
this.saveCount++;
|
|
1213
|
-
this.
|
|
1227
|
+
this.transformStack.push(cloneMatrix(this.relativeTransform));
|
|
1228
|
+
this.canvas.save();
|
|
1214
1229
|
}
|
|
1215
|
-
/**
|
|
1216
|
-
* restore() - 恢复上一个状态并减少计数
|
|
1217
|
-
*/
|
|
1218
1230
|
restore() {
|
|
1219
|
-
if (this.saveCount
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1231
|
+
if (this.saveCount === 0) return;
|
|
1232
|
+
this.saveCount--;
|
|
1233
|
+
this.relativeTransform = this.transformStack.pop() ?? new DOMMatrix();
|
|
1234
|
+
this.canvas.restore();
|
|
1235
|
+
this.applyRelativeTransform();
|
|
1223
1236
|
}
|
|
1224
|
-
/**
|
|
1225
|
-
* setTransform() - 设置相对变换
|
|
1226
|
-
*/
|
|
1227
|
-
setTransform(...args) {
|
|
1228
|
-
let matrix;
|
|
1229
|
-
if (args.length === 1 && args[0] instanceof DOMMatrix) matrix = args[0];
|
|
1230
|
-
else if (args.length === 6) matrix = new DOMMatrix([
|
|
1231
|
-
args[0],
|
|
1232
|
-
args[1],
|
|
1233
|
-
args[2],
|
|
1234
|
-
args[3],
|
|
1235
|
-
args[4],
|
|
1236
|
-
args[5]
|
|
1237
|
-
]);
|
|
1238
|
-
else return;
|
|
1239
|
-
this.relativeTransform = matrix;
|
|
1240
|
-
const actualTransform = this.baseTransform.multiply(matrix);
|
|
1241
|
-
this.ctx.setTransform(actualTransform);
|
|
1242
|
-
}
|
|
1243
|
-
/**
|
|
1244
|
-
* getTransform() - 返回相对变换(而不是绝对变换)
|
|
1245
|
-
*/
|
|
1246
1237
|
getTransform() {
|
|
1247
|
-
return this.relativeTransform;
|
|
1238
|
+
return cloneMatrix(this.relativeTransform);
|
|
1239
|
+
}
|
|
1240
|
+
setTransform(transform) {
|
|
1241
|
+
this.relativeTransform = toRelativeMatrix(transform);
|
|
1242
|
+
this.applyRelativeTransform();
|
|
1243
|
+
}
|
|
1244
|
+
resetTransform() {
|
|
1245
|
+
this.relativeTransform = new DOMMatrix();
|
|
1246
|
+
this.applyRelativeTransform();
|
|
1247
|
+
}
|
|
1248
|
+
translate(x, y) {
|
|
1249
|
+
this.relativeTransform = this.relativeTransform.translate(x, y);
|
|
1250
|
+
this.applyRelativeTransform();
|
|
1251
|
+
}
|
|
1252
|
+
rotate(angle) {
|
|
1253
|
+
this.relativeTransform = this.relativeTransform.rotate(angle * 180 / Math.PI);
|
|
1254
|
+
this.applyRelativeTransform();
|
|
1255
|
+
}
|
|
1256
|
+
scale(x, y) {
|
|
1257
|
+
this.relativeTransform = this.relativeTransform.scale(x, y ?? x);
|
|
1258
|
+
this.applyRelativeTransform();
|
|
1259
|
+
}
|
|
1260
|
+
transform(a, b, c, d, e, f) {
|
|
1261
|
+
this.relativeTransform = this.relativeTransform.multiply(new DOMMatrix([
|
|
1262
|
+
a,
|
|
1263
|
+
b,
|
|
1264
|
+
c,
|
|
1265
|
+
d,
|
|
1266
|
+
e,
|
|
1267
|
+
f
|
|
1268
|
+
]));
|
|
1269
|
+
this.applyRelativeTransform();
|
|
1248
1270
|
}
|
|
1249
|
-
/**
|
|
1250
|
-
* 析构函数级的清理 - 自动恢复所有未恢复的 save
|
|
1251
|
-
*/
|
|
1252
1271
|
destroy() {
|
|
1253
|
-
while (this.saveCount > 0)
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}
|
|
1272
|
+
while (this.saveCount > 0) this.restore();
|
|
1273
|
+
}
|
|
1274
|
+
applyRelativeTransform() {
|
|
1275
|
+
this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
|
|
1258
1276
|
}
|
|
1259
1277
|
};
|
|
1260
|
-
function
|
|
1261
|
-
|
|
1262
|
-
return new Proxy(proxy, {
|
|
1263
|
-
get(target, prop, receiver) {
|
|
1264
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
|
|
1265
|
-
const ownValue = Reflect.get(target, prop, receiver);
|
|
1266
|
-
if (ownValue !== void 0) return ownValue;
|
|
1267
|
-
const contextValue = target.ctx[prop];
|
|
1268
|
-
if (typeof contextValue === "function") return contextValue.bind(target.ctx);
|
|
1269
|
-
return contextValue;
|
|
1270
|
-
},
|
|
1271
|
-
set(target, prop, value, _receiver) {
|
|
1272
|
-
target.ctx[prop] = value;
|
|
1273
|
-
return true;
|
|
1274
|
-
},
|
|
1275
|
-
has(target, prop) {
|
|
1276
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
|
|
1277
|
-
return prop in target.ctx;
|
|
1278
|
-
},
|
|
1279
|
-
ownKeys(target) {
|
|
1280
|
-
return Reflect.ownKeys(target.ctx);
|
|
1281
|
-
},
|
|
1282
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
1283
|
-
return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
|
|
1284
|
-
}
|
|
1285
|
-
});
|
|
1278
|
+
function createCustomDrawContext(canvas, baseTransform) {
|
|
1279
|
+
return new ManagedCustomDrawContext(canvas, baseTransform);
|
|
1286
1280
|
}
|
|
1287
1281
|
//#endregion
|
|
1288
1282
|
//#region src/render/components/customDraw.ts
|
|
@@ -1294,7 +1288,7 @@ function renderCustomDraw(ctx, node) {
|
|
|
1294
1288
|
const element = node.element;
|
|
1295
1289
|
ctx.save();
|
|
1296
1290
|
ctx.translate(node.layout.x, node.layout.y);
|
|
1297
|
-
const
|
|
1291
|
+
const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
|
|
1298
1292
|
const inner = () => {
|
|
1299
1293
|
if (node.children && node.children.length > 0) {
|
|
1300
1294
|
ctx.save();
|
|
@@ -1303,12 +1297,12 @@ function renderCustomDraw(ctx, node) {
|
|
|
1303
1297
|
ctx.restore();
|
|
1304
1298
|
}
|
|
1305
1299
|
};
|
|
1306
|
-
element.draw(
|
|
1300
|
+
element.draw(customCtx, {
|
|
1307
1301
|
inner,
|
|
1308
1302
|
width: node.layout.contentWidth,
|
|
1309
1303
|
height: node.layout.contentHeight
|
|
1310
1304
|
});
|
|
1311
|
-
|
|
1305
|
+
customCtx.destroy();
|
|
1312
1306
|
ctx.restore();
|
|
1313
1307
|
}
|
|
1314
1308
|
//#endregion
|
package/dist/browser/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Canvas } from "@napi-rs/canvas";
|
|
1
|
+
import { Canvas, DOMMatrix } from "@napi-rs/canvas";
|
|
2
2
|
|
|
3
3
|
//#region src/types/base.d.ts
|
|
4
4
|
type Size = number | `${number}%` | "auto" | "fill";
|
|
@@ -152,7 +152,19 @@ interface LayoutConstraints {
|
|
|
152
152
|
//#endregion
|
|
153
153
|
//#region src/types/components.d.ts
|
|
154
154
|
type ElementType = "box" | "text" | "richtext" | "image" | "svg" | "stack" | "transform" | "customdraw";
|
|
155
|
-
interface
|
|
155
|
+
interface CustomDrawContext {
|
|
156
|
+
readonly canvas: CanvasRenderingContext2D;
|
|
157
|
+
save(): void;
|
|
158
|
+
restore(): void;
|
|
159
|
+
getTransform(): DOMMatrix;
|
|
160
|
+
setTransform(transform?: DOMMatrix | [number, number, number, number, number, number]): void;
|
|
161
|
+
resetTransform(): void;
|
|
162
|
+
translate(x: number, y: number): void;
|
|
163
|
+
rotate(angle: number): void;
|
|
164
|
+
scale(x: number, y?: number): void;
|
|
165
|
+
transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
|
166
|
+
}
|
|
167
|
+
interface CustomDrawOptions {
|
|
156
168
|
inner?: () => void;
|
|
157
169
|
width: number;
|
|
158
170
|
height: number;
|
|
@@ -357,7 +369,7 @@ interface TransformElement extends ElementBase, TransformProps {
|
|
|
357
369
|
type: "transform";
|
|
358
370
|
}
|
|
359
371
|
interface CustomDrawProps extends LayoutProps {
|
|
360
|
-
draw: (ctx:
|
|
372
|
+
draw: (ctx: CustomDrawContext, options: CustomDrawOptions) => void;
|
|
361
373
|
children?: Element;
|
|
362
374
|
}
|
|
363
375
|
interface CustomDrawElement extends ElementBase, CustomDrawProps {
|
|
@@ -451,4 +463,4 @@ declare function printLayout(node: LayoutNode): void;
|
|
|
451
463
|
*/
|
|
452
464
|
declare function layoutToString(node: LayoutNode, _indent?: string): string;
|
|
453
465
|
//#endregion
|
|
454
|
-
export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawElement, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutElement, type LayoutNode, type LayoutProps, type LinearGradientDescriptor, type MeasureContext, type
|
|
466
|
+
export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawContext, type CustomDrawElement, type CustomDrawOptions, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutElement, type LayoutNode, type LayoutProps, 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, Transform, type TransformElement, type TransformProps, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Canvas } from "@napi-rs/canvas";
|
|
1
|
+
import { Canvas, DOMMatrix } from "@napi-rs/canvas";
|
|
2
2
|
|
|
3
3
|
//#region src/types/base.d.ts
|
|
4
4
|
type Size = number | `${number}%` | "auto" | "fill";
|
|
@@ -152,7 +152,19 @@ interface LayoutConstraints {
|
|
|
152
152
|
//#endregion
|
|
153
153
|
//#region src/types/components.d.ts
|
|
154
154
|
type ElementType = "box" | "text" | "richtext" | "image" | "svg" | "stack" | "transform" | "customdraw";
|
|
155
|
-
interface
|
|
155
|
+
interface CustomDrawContext {
|
|
156
|
+
readonly canvas: CanvasRenderingContext2D;
|
|
157
|
+
save(): void;
|
|
158
|
+
restore(): void;
|
|
159
|
+
getTransform(): DOMMatrix;
|
|
160
|
+
setTransform(transform?: DOMMatrix | [number, number, number, number, number, number]): void;
|
|
161
|
+
resetTransform(): void;
|
|
162
|
+
translate(x: number, y: number): void;
|
|
163
|
+
rotate(angle: number): void;
|
|
164
|
+
scale(x: number, y?: number): void;
|
|
165
|
+
transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
|
166
|
+
}
|
|
167
|
+
interface CustomDrawOptions {
|
|
156
168
|
inner?: () => void;
|
|
157
169
|
width: number;
|
|
158
170
|
height: number;
|
|
@@ -357,7 +369,7 @@ interface TransformElement extends ElementBase, TransformProps {
|
|
|
357
369
|
type: "transform";
|
|
358
370
|
}
|
|
359
371
|
interface CustomDrawProps extends LayoutProps {
|
|
360
|
-
draw: (ctx:
|
|
372
|
+
draw: (ctx: CustomDrawContext, options: CustomDrawOptions) => void;
|
|
361
373
|
children?: Element;
|
|
362
374
|
}
|
|
363
375
|
interface CustomDrawElement extends ElementBase, CustomDrawProps {
|
|
@@ -451,4 +463,4 @@ declare function printLayout(node: LayoutNode): void;
|
|
|
451
463
|
*/
|
|
452
464
|
declare function layoutToString(node: LayoutNode, _indent?: string): string;
|
|
453
465
|
//#endregion
|
|
454
|
-
export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawElement, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutElement, type LayoutNode, type LayoutProps, type LinearGradientDescriptor, type MeasureContext, type
|
|
466
|
+
export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawContext, type CustomDrawElement, type CustomDrawOptions, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutElement, type LayoutNode, type LayoutProps, 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, Transform, type TransformElement, type TransformProps, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
|
package/dist/browser/index.js
CHANGED
|
@@ -478,20 +478,40 @@ function buildFontString(font) {
|
|
|
478
478
|
}
|
|
479
479
|
//#endregion
|
|
480
480
|
//#region src/layout/utils/measure.ts
|
|
481
|
+
const MEASURE_CACHE_LIMIT = 256;
|
|
481
482
|
function createCanvasMeasureContext(ctx) {
|
|
483
|
+
const cache = /* @__PURE__ */ new Map();
|
|
484
|
+
let lastFontString = null;
|
|
482
485
|
return { measureText(text, font) {
|
|
483
|
-
|
|
484
|
-
|
|
486
|
+
const fontString = buildFontString(font);
|
|
487
|
+
const key = fontString + "\0" + text;
|
|
488
|
+
const hit = cache.get(key);
|
|
489
|
+
if (hit !== void 0) {
|
|
490
|
+
cache.delete(key);
|
|
491
|
+
cache.set(key, hit);
|
|
492
|
+
return hit;
|
|
493
|
+
}
|
|
494
|
+
if (fontString !== lastFontString) {
|
|
495
|
+
ctx.font = fontString;
|
|
496
|
+
ctx.textBaseline = "middle";
|
|
497
|
+
lastFontString = fontString;
|
|
498
|
+
}
|
|
485
499
|
const metrics = ctx.measureText(text);
|
|
486
500
|
const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
487
501
|
const fontSize = font.size || 16;
|
|
488
|
-
|
|
502
|
+
const result = {
|
|
489
503
|
width: metrics.width,
|
|
490
504
|
height: height || fontSize,
|
|
491
505
|
offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
|
|
492
506
|
ascent: metrics.actualBoundingBoxAscent,
|
|
493
507
|
descent: metrics.actualBoundingBoxDescent
|
|
494
508
|
};
|
|
509
|
+
if (cache.size >= MEASURE_CACHE_LIMIT) {
|
|
510
|
+
const oldest = cache.keys().next().value;
|
|
511
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
512
|
+
}
|
|
513
|
+
cache.set(key, result);
|
|
514
|
+
return result;
|
|
495
515
|
} };
|
|
496
516
|
}
|
|
497
517
|
function wrapText(ctx, text, maxWidth, font) {
|
|
@@ -768,7 +788,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
|
|
|
768
788
|
}
|
|
769
789
|
if (layoutElement.type === "richtext") {
|
|
770
790
|
const lineHeight = layoutElement.lineHeight ?? 1.2;
|
|
771
|
-
|
|
791
|
+
const elementStyle = {
|
|
792
|
+
font: layoutElement.font,
|
|
793
|
+
color: layoutElement.color,
|
|
794
|
+
background: layoutElement.background,
|
|
795
|
+
underline: layoutElement.underline,
|
|
796
|
+
strikethrough: layoutElement.strikethrough
|
|
797
|
+
};
|
|
798
|
+
let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight, elementStyle);
|
|
772
799
|
if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
|
|
773
800
|
lines = lines.slice(0, layoutElement.maxLines);
|
|
774
801
|
if (layoutElement.ellipsis && lines.length > 0) {
|
|
@@ -1167,121 +1194,88 @@ function renderBox(ctx, node) {
|
|
|
1167
1194
|
if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
|
|
1168
1195
|
}
|
|
1169
1196
|
//#endregion
|
|
1170
|
-
//#region src/render/components/
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1197
|
+
//#region src/render/components/CustomDrawContext.ts
|
|
1198
|
+
function cloneMatrix(matrix) {
|
|
1199
|
+
return new DOMMatrix([
|
|
1200
|
+
matrix.a,
|
|
1201
|
+
matrix.b,
|
|
1202
|
+
matrix.c,
|
|
1203
|
+
matrix.d,
|
|
1204
|
+
matrix.e,
|
|
1205
|
+
matrix.f
|
|
1206
|
+
]);
|
|
1207
|
+
}
|
|
1208
|
+
function toRelativeMatrix(transform) {
|
|
1209
|
+
if (transform === void 0) return new DOMMatrix();
|
|
1210
|
+
if (transform instanceof DOMMatrix) return cloneMatrix(transform);
|
|
1211
|
+
return new DOMMatrix(transform);
|
|
1212
|
+
}
|
|
1213
|
+
var ManagedCustomDrawContext = class {
|
|
1214
|
+
canvas;
|
|
1188
1215
|
baseTransform;
|
|
1189
|
-
/**
|
|
1190
|
-
* 相对变换矩阵(用户通过 setTransform 设置)
|
|
1191
|
-
*/
|
|
1192
1216
|
relativeTransform;
|
|
1193
|
-
|
|
1194
|
-
* save/restore 计数器
|
|
1195
|
-
*/
|
|
1217
|
+
transformStack = [];
|
|
1196
1218
|
saveCount = 0;
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
* @param baseTransform 初始的基础变换矩阵
|
|
1201
|
-
*/
|
|
1202
|
-
constructor(ctx, baseTransform) {
|
|
1203
|
-
this.ctx = ctx;
|
|
1204
|
-
this.baseTransform = baseTransform;
|
|
1219
|
+
constructor(canvas, baseTransform) {
|
|
1220
|
+
this.canvas = canvas;
|
|
1221
|
+
this.baseTransform = cloneMatrix(baseTransform);
|
|
1205
1222
|
this.relativeTransform = new DOMMatrix();
|
|
1206
1223
|
}
|
|
1207
|
-
/**
|
|
1208
|
-
* save() - 保存当前状态并增加计数
|
|
1209
|
-
*/
|
|
1210
1224
|
save() {
|
|
1211
1225
|
this.saveCount++;
|
|
1212
|
-
this.
|
|
1226
|
+
this.transformStack.push(cloneMatrix(this.relativeTransform));
|
|
1227
|
+
this.canvas.save();
|
|
1213
1228
|
}
|
|
1214
|
-
/**
|
|
1215
|
-
* restore() - 恢复上一个状态并减少计数
|
|
1216
|
-
*/
|
|
1217
1229
|
restore() {
|
|
1218
|
-
if (this.saveCount
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1230
|
+
if (this.saveCount === 0) return;
|
|
1231
|
+
this.saveCount--;
|
|
1232
|
+
this.relativeTransform = this.transformStack.pop() ?? new DOMMatrix();
|
|
1233
|
+
this.canvas.restore();
|
|
1234
|
+
this.applyRelativeTransform();
|
|
1222
1235
|
}
|
|
1223
|
-
/**
|
|
1224
|
-
* setTransform() - 设置相对变换
|
|
1225
|
-
*/
|
|
1226
|
-
setTransform(...args) {
|
|
1227
|
-
let matrix;
|
|
1228
|
-
if (args.length === 1 && args[0] instanceof DOMMatrix) matrix = args[0];
|
|
1229
|
-
else if (args.length === 6) matrix = new DOMMatrix([
|
|
1230
|
-
args[0],
|
|
1231
|
-
args[1],
|
|
1232
|
-
args[2],
|
|
1233
|
-
args[3],
|
|
1234
|
-
args[4],
|
|
1235
|
-
args[5]
|
|
1236
|
-
]);
|
|
1237
|
-
else return;
|
|
1238
|
-
this.relativeTransform = matrix;
|
|
1239
|
-
const actualTransform = this.baseTransform.multiply(matrix);
|
|
1240
|
-
this.ctx.setTransform(actualTransform);
|
|
1241
|
-
}
|
|
1242
|
-
/**
|
|
1243
|
-
* getTransform() - 返回相对变换(而不是绝对变换)
|
|
1244
|
-
*/
|
|
1245
1236
|
getTransform() {
|
|
1246
|
-
return this.relativeTransform;
|
|
1237
|
+
return cloneMatrix(this.relativeTransform);
|
|
1238
|
+
}
|
|
1239
|
+
setTransform(transform) {
|
|
1240
|
+
this.relativeTransform = toRelativeMatrix(transform);
|
|
1241
|
+
this.applyRelativeTransform();
|
|
1242
|
+
}
|
|
1243
|
+
resetTransform() {
|
|
1244
|
+
this.relativeTransform = new DOMMatrix();
|
|
1245
|
+
this.applyRelativeTransform();
|
|
1246
|
+
}
|
|
1247
|
+
translate(x, y) {
|
|
1248
|
+
this.relativeTransform = this.relativeTransform.translate(x, y);
|
|
1249
|
+
this.applyRelativeTransform();
|
|
1250
|
+
}
|
|
1251
|
+
rotate(angle) {
|
|
1252
|
+
this.relativeTransform = this.relativeTransform.rotate(angle * 180 / Math.PI);
|
|
1253
|
+
this.applyRelativeTransform();
|
|
1254
|
+
}
|
|
1255
|
+
scale(x, y) {
|
|
1256
|
+
this.relativeTransform = this.relativeTransform.scale(x, y ?? x);
|
|
1257
|
+
this.applyRelativeTransform();
|
|
1258
|
+
}
|
|
1259
|
+
transform(a, b, c, d, e, f) {
|
|
1260
|
+
this.relativeTransform = this.relativeTransform.multiply(new DOMMatrix([
|
|
1261
|
+
a,
|
|
1262
|
+
b,
|
|
1263
|
+
c,
|
|
1264
|
+
d,
|
|
1265
|
+
e,
|
|
1266
|
+
f
|
|
1267
|
+
]));
|
|
1268
|
+
this.applyRelativeTransform();
|
|
1247
1269
|
}
|
|
1248
|
-
/**
|
|
1249
|
-
* 析构函数级的清理 - 自动恢复所有未恢复的 save
|
|
1250
|
-
*/
|
|
1251
1270
|
destroy() {
|
|
1252
|
-
while (this.saveCount > 0)
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
}
|
|
1271
|
+
while (this.saveCount > 0) this.restore();
|
|
1272
|
+
}
|
|
1273
|
+
applyRelativeTransform() {
|
|
1274
|
+
this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
|
|
1257
1275
|
}
|
|
1258
1276
|
};
|
|
1259
|
-
function
|
|
1260
|
-
|
|
1261
|
-
return new Proxy(proxy, {
|
|
1262
|
-
get(target, prop, receiver) {
|
|
1263
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
|
|
1264
|
-
const ownValue = Reflect.get(target, prop, receiver);
|
|
1265
|
-
if (ownValue !== void 0) return ownValue;
|
|
1266
|
-
const contextValue = target.ctx[prop];
|
|
1267
|
-
if (typeof contextValue === "function") return contextValue.bind(target.ctx);
|
|
1268
|
-
return contextValue;
|
|
1269
|
-
},
|
|
1270
|
-
set(target, prop, value, _receiver) {
|
|
1271
|
-
target.ctx[prop] = value;
|
|
1272
|
-
return true;
|
|
1273
|
-
},
|
|
1274
|
-
has(target, prop) {
|
|
1275
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
|
|
1276
|
-
return prop in target.ctx;
|
|
1277
|
-
},
|
|
1278
|
-
ownKeys(target) {
|
|
1279
|
-
return Reflect.ownKeys(target.ctx);
|
|
1280
|
-
},
|
|
1281
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
1282
|
-
return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
|
|
1283
|
-
}
|
|
1284
|
-
});
|
|
1277
|
+
function createCustomDrawContext(canvas, baseTransform) {
|
|
1278
|
+
return new ManagedCustomDrawContext(canvas, baseTransform);
|
|
1285
1279
|
}
|
|
1286
1280
|
//#endregion
|
|
1287
1281
|
//#region src/render/components/customDraw.ts
|
|
@@ -1293,7 +1287,7 @@ function renderCustomDraw(ctx, node) {
|
|
|
1293
1287
|
const element = node.element;
|
|
1294
1288
|
ctx.save();
|
|
1295
1289
|
ctx.translate(node.layout.x, node.layout.y);
|
|
1296
|
-
const
|
|
1290
|
+
const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
|
|
1297
1291
|
const inner = () => {
|
|
1298
1292
|
if (node.children && node.children.length > 0) {
|
|
1299
1293
|
ctx.save();
|
|
@@ -1302,12 +1296,12 @@ function renderCustomDraw(ctx, node) {
|
|
|
1302
1296
|
ctx.restore();
|
|
1303
1297
|
}
|
|
1304
1298
|
};
|
|
1305
|
-
element.draw(
|
|
1299
|
+
element.draw(customCtx, {
|
|
1306
1300
|
inner,
|
|
1307
1301
|
width: node.layout.contentWidth,
|
|
1308
1302
|
height: node.layout.contentHeight
|
|
1309
1303
|
});
|
|
1310
|
-
|
|
1304
|
+
customCtx.destroy();
|
|
1311
1305
|
ctx.restore();
|
|
1312
1306
|
}
|
|
1313
1307
|
//#endregion
|