@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/dist/node/index.cjs
CHANGED
|
@@ -475,20 +475,40 @@ function buildFontString(font) {
|
|
|
475
475
|
}
|
|
476
476
|
//#endregion
|
|
477
477
|
//#region src/layout/utils/measure.ts
|
|
478
|
+
const MEASURE_CACHE_LIMIT = 256;
|
|
478
479
|
function createCanvasMeasureContext(ctx) {
|
|
480
|
+
const cache = /* @__PURE__ */ new Map();
|
|
481
|
+
let lastFontString = null;
|
|
479
482
|
return { measureText(text, font) {
|
|
480
|
-
|
|
481
|
-
|
|
483
|
+
const fontString = buildFontString(font);
|
|
484
|
+
const key = fontString + "\0" + text;
|
|
485
|
+
const hit = cache.get(key);
|
|
486
|
+
if (hit !== void 0) {
|
|
487
|
+
cache.delete(key);
|
|
488
|
+
cache.set(key, hit);
|
|
489
|
+
return hit;
|
|
490
|
+
}
|
|
491
|
+
if (fontString !== lastFontString) {
|
|
492
|
+
ctx.font = fontString;
|
|
493
|
+
ctx.textBaseline = "middle";
|
|
494
|
+
lastFontString = fontString;
|
|
495
|
+
}
|
|
482
496
|
const metrics = ctx.measureText(text);
|
|
483
497
|
const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
484
498
|
const fontSize = font.size || 16;
|
|
485
|
-
|
|
499
|
+
const result = {
|
|
486
500
|
width: metrics.width,
|
|
487
501
|
height: height || fontSize,
|
|
488
502
|
offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
|
|
489
503
|
ascent: metrics.actualBoundingBoxAscent,
|
|
490
504
|
descent: metrics.actualBoundingBoxDescent
|
|
491
505
|
};
|
|
506
|
+
if (cache.size >= MEASURE_CACHE_LIMIT) {
|
|
507
|
+
const oldest = cache.keys().next().value;
|
|
508
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
509
|
+
}
|
|
510
|
+
cache.set(key, result);
|
|
511
|
+
return result;
|
|
492
512
|
} };
|
|
493
513
|
}
|
|
494
514
|
function wrapText(ctx, text, maxWidth, font) {
|
|
@@ -765,7 +785,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
|
|
|
765
785
|
}
|
|
766
786
|
if (layoutElement.type === "richtext") {
|
|
767
787
|
const lineHeight = layoutElement.lineHeight ?? 1.2;
|
|
768
|
-
|
|
788
|
+
const elementStyle = {
|
|
789
|
+
font: layoutElement.font,
|
|
790
|
+
color: layoutElement.color,
|
|
791
|
+
background: layoutElement.background,
|
|
792
|
+
underline: layoutElement.underline,
|
|
793
|
+
strikethrough: layoutElement.strikethrough
|
|
794
|
+
};
|
|
795
|
+
let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight, elementStyle);
|
|
769
796
|
if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
|
|
770
797
|
lines = lines.slice(0, layoutElement.maxLines);
|
|
771
798
|
if (layoutElement.ellipsis && lines.length > 0) {
|
|
@@ -1164,121 +1191,88 @@ function renderBox(ctx, node) {
|
|
|
1164
1191
|
if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
|
|
1165
1192
|
}
|
|
1166
1193
|
//#endregion
|
|
1167
|
-
//#region src/render/components/
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1194
|
+
//#region src/render/components/CustomDrawContext.ts
|
|
1195
|
+
function cloneMatrix(matrix) {
|
|
1196
|
+
return new _napi_rs_canvas.DOMMatrix([
|
|
1197
|
+
matrix.a,
|
|
1198
|
+
matrix.b,
|
|
1199
|
+
matrix.c,
|
|
1200
|
+
matrix.d,
|
|
1201
|
+
matrix.e,
|
|
1202
|
+
matrix.f
|
|
1203
|
+
]);
|
|
1204
|
+
}
|
|
1205
|
+
function toRelativeMatrix(transform) {
|
|
1206
|
+
if (transform === void 0) return new _napi_rs_canvas.DOMMatrix();
|
|
1207
|
+
if (transform instanceof _napi_rs_canvas.DOMMatrix) return cloneMatrix(transform);
|
|
1208
|
+
return new _napi_rs_canvas.DOMMatrix(transform);
|
|
1209
|
+
}
|
|
1210
|
+
var ManagedCustomDrawContext = class {
|
|
1211
|
+
canvas;
|
|
1185
1212
|
baseTransform;
|
|
1186
|
-
/**
|
|
1187
|
-
* 相对变换矩阵(用户通过 setTransform 设置)
|
|
1188
|
-
*/
|
|
1189
1213
|
relativeTransform;
|
|
1190
|
-
|
|
1191
|
-
* save/restore 计数器
|
|
1192
|
-
*/
|
|
1214
|
+
transformStack = [];
|
|
1193
1215
|
saveCount = 0;
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
* @param baseTransform 初始的基础变换矩阵
|
|
1198
|
-
*/
|
|
1199
|
-
constructor(ctx, baseTransform) {
|
|
1200
|
-
this.ctx = ctx;
|
|
1201
|
-
this.baseTransform = baseTransform;
|
|
1216
|
+
constructor(canvas, baseTransform) {
|
|
1217
|
+
this.canvas = canvas;
|
|
1218
|
+
this.baseTransform = cloneMatrix(baseTransform);
|
|
1202
1219
|
this.relativeTransform = new _napi_rs_canvas.DOMMatrix();
|
|
1203
1220
|
}
|
|
1204
|
-
/**
|
|
1205
|
-
* save() - 保存当前状态并增加计数
|
|
1206
|
-
*/
|
|
1207
1221
|
save() {
|
|
1208
1222
|
this.saveCount++;
|
|
1209
|
-
this.
|
|
1223
|
+
this.transformStack.push(cloneMatrix(this.relativeTransform));
|
|
1224
|
+
this.canvas.save();
|
|
1210
1225
|
}
|
|
1211
|
-
/**
|
|
1212
|
-
* restore() - 恢复上一个状态并减少计数
|
|
1213
|
-
*/
|
|
1214
1226
|
restore() {
|
|
1215
|
-
if (this.saveCount
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1227
|
+
if (this.saveCount === 0) return;
|
|
1228
|
+
this.saveCount--;
|
|
1229
|
+
this.relativeTransform = this.transformStack.pop() ?? new _napi_rs_canvas.DOMMatrix();
|
|
1230
|
+
this.canvas.restore();
|
|
1231
|
+
this.applyRelativeTransform();
|
|
1219
1232
|
}
|
|
1220
|
-
/**
|
|
1221
|
-
* setTransform() - 设置相对变换
|
|
1222
|
-
*/
|
|
1223
|
-
setTransform(...args) {
|
|
1224
|
-
let matrix;
|
|
1225
|
-
if (args.length === 1 && args[0] instanceof _napi_rs_canvas.DOMMatrix) matrix = args[0];
|
|
1226
|
-
else if (args.length === 6) matrix = new _napi_rs_canvas.DOMMatrix([
|
|
1227
|
-
args[0],
|
|
1228
|
-
args[1],
|
|
1229
|
-
args[2],
|
|
1230
|
-
args[3],
|
|
1231
|
-
args[4],
|
|
1232
|
-
args[5]
|
|
1233
|
-
]);
|
|
1234
|
-
else return;
|
|
1235
|
-
this.relativeTransform = matrix;
|
|
1236
|
-
const actualTransform = this.baseTransform.multiply(matrix);
|
|
1237
|
-
this.ctx.setTransform(actualTransform);
|
|
1238
|
-
}
|
|
1239
|
-
/**
|
|
1240
|
-
* getTransform() - 返回相对变换(而不是绝对变换)
|
|
1241
|
-
*/
|
|
1242
1233
|
getTransform() {
|
|
1243
|
-
return this.relativeTransform;
|
|
1234
|
+
return cloneMatrix(this.relativeTransform);
|
|
1235
|
+
}
|
|
1236
|
+
setTransform(transform) {
|
|
1237
|
+
this.relativeTransform = toRelativeMatrix(transform);
|
|
1238
|
+
this.applyRelativeTransform();
|
|
1239
|
+
}
|
|
1240
|
+
resetTransform() {
|
|
1241
|
+
this.relativeTransform = new _napi_rs_canvas.DOMMatrix();
|
|
1242
|
+
this.applyRelativeTransform();
|
|
1243
|
+
}
|
|
1244
|
+
translate(x, y) {
|
|
1245
|
+
this.relativeTransform = this.relativeTransform.translate(x, y);
|
|
1246
|
+
this.applyRelativeTransform();
|
|
1247
|
+
}
|
|
1248
|
+
rotate(angle) {
|
|
1249
|
+
this.relativeTransform = this.relativeTransform.rotate(angle * 180 / Math.PI);
|
|
1250
|
+
this.applyRelativeTransform();
|
|
1251
|
+
}
|
|
1252
|
+
scale(x, y) {
|
|
1253
|
+
this.relativeTransform = this.relativeTransform.scale(x, y ?? x);
|
|
1254
|
+
this.applyRelativeTransform();
|
|
1255
|
+
}
|
|
1256
|
+
transform(a, b, c, d, e, f) {
|
|
1257
|
+
this.relativeTransform = this.relativeTransform.multiply(new _napi_rs_canvas.DOMMatrix([
|
|
1258
|
+
a,
|
|
1259
|
+
b,
|
|
1260
|
+
c,
|
|
1261
|
+
d,
|
|
1262
|
+
e,
|
|
1263
|
+
f
|
|
1264
|
+
]));
|
|
1265
|
+
this.applyRelativeTransform();
|
|
1244
1266
|
}
|
|
1245
|
-
/**
|
|
1246
|
-
* 析构函数级的清理 - 自动恢复所有未恢复的 save
|
|
1247
|
-
*/
|
|
1248
1267
|
destroy() {
|
|
1249
|
-
while (this.saveCount > 0)
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
}
|
|
1268
|
+
while (this.saveCount > 0) this.restore();
|
|
1269
|
+
}
|
|
1270
|
+
applyRelativeTransform() {
|
|
1271
|
+
this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
|
|
1254
1272
|
}
|
|
1255
1273
|
};
|
|
1256
|
-
function
|
|
1257
|
-
|
|
1258
|
-
return new Proxy(proxy, {
|
|
1259
|
-
get(target, prop, receiver) {
|
|
1260
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
|
|
1261
|
-
const ownValue = Reflect.get(target, prop, receiver);
|
|
1262
|
-
if (ownValue !== void 0) return ownValue;
|
|
1263
|
-
const contextValue = target.ctx[prop];
|
|
1264
|
-
if (typeof contextValue === "function") return contextValue.bind(target.ctx);
|
|
1265
|
-
return contextValue;
|
|
1266
|
-
},
|
|
1267
|
-
set(target, prop, value, _receiver) {
|
|
1268
|
-
target.ctx[prop] = value;
|
|
1269
|
-
return true;
|
|
1270
|
-
},
|
|
1271
|
-
has(target, prop) {
|
|
1272
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
|
|
1273
|
-
return prop in target.ctx;
|
|
1274
|
-
},
|
|
1275
|
-
ownKeys(target) {
|
|
1276
|
-
return Reflect.ownKeys(target.ctx);
|
|
1277
|
-
},
|
|
1278
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
1279
|
-
return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
|
|
1280
|
-
}
|
|
1281
|
-
});
|
|
1274
|
+
function createCustomDrawContext(canvas, baseTransform) {
|
|
1275
|
+
return new ManagedCustomDrawContext(canvas, baseTransform);
|
|
1282
1276
|
}
|
|
1283
1277
|
//#endregion
|
|
1284
1278
|
//#region src/render/components/customDraw.ts
|
|
@@ -1290,7 +1284,7 @@ function renderCustomDraw(ctx, node) {
|
|
|
1290
1284
|
const element = node.element;
|
|
1291
1285
|
ctx.save();
|
|
1292
1286
|
ctx.translate(node.layout.x, node.layout.y);
|
|
1293
|
-
const
|
|
1287
|
+
const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
|
|
1294
1288
|
const inner = () => {
|
|
1295
1289
|
if (node.children && node.children.length > 0) {
|
|
1296
1290
|
ctx.save();
|
|
@@ -1299,12 +1293,12 @@ function renderCustomDraw(ctx, node) {
|
|
|
1299
1293
|
ctx.restore();
|
|
1300
1294
|
}
|
|
1301
1295
|
};
|
|
1302
|
-
element.draw(
|
|
1296
|
+
element.draw(customCtx, {
|
|
1303
1297
|
inner,
|
|
1304
1298
|
width: node.layout.contentWidth,
|
|
1305
1299
|
height: node.layout.contentHeight
|
|
1306
1300
|
});
|
|
1307
|
-
|
|
1301
|
+
customCtx.destroy();
|
|
1308
1302
|
ctx.restore();
|
|
1309
1303
|
}
|
|
1310
1304
|
//#endregion
|
package/dist/node/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/node/index.d.mts
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/node/index.mjs
CHANGED
|
@@ -474,20 +474,40 @@ function buildFontString(font) {
|
|
|
474
474
|
}
|
|
475
475
|
//#endregion
|
|
476
476
|
//#region src/layout/utils/measure.ts
|
|
477
|
+
const MEASURE_CACHE_LIMIT = 256;
|
|
477
478
|
function createCanvasMeasureContext(ctx) {
|
|
479
|
+
const cache = /* @__PURE__ */ new Map();
|
|
480
|
+
let lastFontString = null;
|
|
478
481
|
return { measureText(text, font) {
|
|
479
|
-
|
|
480
|
-
|
|
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
|
+
}
|
|
481
495
|
const metrics = ctx.measureText(text);
|
|
482
496
|
const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
483
497
|
const fontSize = font.size || 16;
|
|
484
|
-
|
|
498
|
+
const result = {
|
|
485
499
|
width: metrics.width,
|
|
486
500
|
height: height || fontSize,
|
|
487
501
|
offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
|
|
488
502
|
ascent: metrics.actualBoundingBoxAscent,
|
|
489
503
|
descent: metrics.actualBoundingBoxDescent
|
|
490
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;
|
|
491
511
|
} };
|
|
492
512
|
}
|
|
493
513
|
function wrapText(ctx, text, maxWidth, font) {
|
|
@@ -764,7 +784,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
|
|
|
764
784
|
}
|
|
765
785
|
if (layoutElement.type === "richtext") {
|
|
766
786
|
const lineHeight = layoutElement.lineHeight ?? 1.2;
|
|
767
|
-
|
|
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);
|
|
768
795
|
if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
|
|
769
796
|
lines = lines.slice(0, layoutElement.maxLines);
|
|
770
797
|
if (layoutElement.ellipsis && lines.length > 0) {
|
|
@@ -1163,121 +1190,88 @@ function renderBox(ctx, node) {
|
|
|
1163
1190
|
if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
|
|
1164
1191
|
}
|
|
1165
1192
|
//#endregion
|
|
1166
|
-
//#region src/render/components/
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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;
|
|
1184
1211
|
baseTransform;
|
|
1185
|
-
/**
|
|
1186
|
-
* 相对变换矩阵(用户通过 setTransform 设置)
|
|
1187
|
-
*/
|
|
1188
1212
|
relativeTransform;
|
|
1189
|
-
|
|
1190
|
-
* save/restore 计数器
|
|
1191
|
-
*/
|
|
1213
|
+
transformStack = [];
|
|
1192
1214
|
saveCount = 0;
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
* @param baseTransform 初始的基础变换矩阵
|
|
1197
|
-
*/
|
|
1198
|
-
constructor(ctx, baseTransform) {
|
|
1199
|
-
this.ctx = ctx;
|
|
1200
|
-
this.baseTransform = baseTransform;
|
|
1215
|
+
constructor(canvas, baseTransform) {
|
|
1216
|
+
this.canvas = canvas;
|
|
1217
|
+
this.baseTransform = cloneMatrix(baseTransform);
|
|
1201
1218
|
this.relativeTransform = new DOMMatrix();
|
|
1202
1219
|
}
|
|
1203
|
-
/**
|
|
1204
|
-
* save() - 保存当前状态并增加计数
|
|
1205
|
-
*/
|
|
1206
1220
|
save() {
|
|
1207
1221
|
this.saveCount++;
|
|
1208
|
-
this.
|
|
1222
|
+
this.transformStack.push(cloneMatrix(this.relativeTransform));
|
|
1223
|
+
this.canvas.save();
|
|
1209
1224
|
}
|
|
1210
|
-
/**
|
|
1211
|
-
* restore() - 恢复上一个状态并减少计数
|
|
1212
|
-
*/
|
|
1213
1225
|
restore() {
|
|
1214
|
-
if (this.saveCount
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1226
|
+
if (this.saveCount === 0) return;
|
|
1227
|
+
this.saveCount--;
|
|
1228
|
+
this.relativeTransform = this.transformStack.pop() ?? new DOMMatrix();
|
|
1229
|
+
this.canvas.restore();
|
|
1230
|
+
this.applyRelativeTransform();
|
|
1218
1231
|
}
|
|
1219
|
-
/**
|
|
1220
|
-
* setTransform() - 设置相对变换
|
|
1221
|
-
*/
|
|
1222
|
-
setTransform(...args) {
|
|
1223
|
-
let matrix;
|
|
1224
|
-
if (args.length === 1 && args[0] instanceof DOMMatrix) matrix = args[0];
|
|
1225
|
-
else if (args.length === 6) matrix = new DOMMatrix([
|
|
1226
|
-
args[0],
|
|
1227
|
-
args[1],
|
|
1228
|
-
args[2],
|
|
1229
|
-
args[3],
|
|
1230
|
-
args[4],
|
|
1231
|
-
args[5]
|
|
1232
|
-
]);
|
|
1233
|
-
else return;
|
|
1234
|
-
this.relativeTransform = matrix;
|
|
1235
|
-
const actualTransform = this.baseTransform.multiply(matrix);
|
|
1236
|
-
this.ctx.setTransform(actualTransform);
|
|
1237
|
-
}
|
|
1238
|
-
/**
|
|
1239
|
-
* getTransform() - 返回相对变换(而不是绝对变换)
|
|
1240
|
-
*/
|
|
1241
1232
|
getTransform() {
|
|
1242
|
-
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();
|
|
1243
1265
|
}
|
|
1244
|
-
/**
|
|
1245
|
-
* 析构函数级的清理 - 自动恢复所有未恢复的 save
|
|
1246
|
-
*/
|
|
1247
1266
|
destroy() {
|
|
1248
|
-
while (this.saveCount > 0)
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
}
|
|
1267
|
+
while (this.saveCount > 0) this.restore();
|
|
1268
|
+
}
|
|
1269
|
+
applyRelativeTransform() {
|
|
1270
|
+
this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
|
|
1253
1271
|
}
|
|
1254
1272
|
};
|
|
1255
|
-
function
|
|
1256
|
-
|
|
1257
|
-
return new Proxy(proxy, {
|
|
1258
|
-
get(target, prop, receiver) {
|
|
1259
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
|
|
1260
|
-
const ownValue = Reflect.get(target, prop, receiver);
|
|
1261
|
-
if (ownValue !== void 0) return ownValue;
|
|
1262
|
-
const contextValue = target.ctx[prop];
|
|
1263
|
-
if (typeof contextValue === "function") return contextValue.bind(target.ctx);
|
|
1264
|
-
return contextValue;
|
|
1265
|
-
},
|
|
1266
|
-
set(target, prop, value, _receiver) {
|
|
1267
|
-
target.ctx[prop] = value;
|
|
1268
|
-
return true;
|
|
1269
|
-
},
|
|
1270
|
-
has(target, prop) {
|
|
1271
|
-
if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
|
|
1272
|
-
return prop in target.ctx;
|
|
1273
|
-
},
|
|
1274
|
-
ownKeys(target) {
|
|
1275
|
-
return Reflect.ownKeys(target.ctx);
|
|
1276
|
-
},
|
|
1277
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
1278
|
-
return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
|
|
1279
|
-
}
|
|
1280
|
-
});
|
|
1273
|
+
function createCustomDrawContext(canvas, baseTransform) {
|
|
1274
|
+
return new ManagedCustomDrawContext(canvas, baseTransform);
|
|
1281
1275
|
}
|
|
1282
1276
|
//#endregion
|
|
1283
1277
|
//#region src/render/components/customDraw.ts
|
|
@@ -1289,7 +1283,7 @@ function renderCustomDraw(ctx, node) {
|
|
|
1289
1283
|
const element = node.element;
|
|
1290
1284
|
ctx.save();
|
|
1291
1285
|
ctx.translate(node.layout.x, node.layout.y);
|
|
1292
|
-
const
|
|
1286
|
+
const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
|
|
1293
1287
|
const inner = () => {
|
|
1294
1288
|
if (node.children && node.children.length > 0) {
|
|
1295
1289
|
ctx.save();
|
|
@@ -1298,12 +1292,12 @@ function renderCustomDraw(ctx, node) {
|
|
|
1298
1292
|
ctx.restore();
|
|
1299
1293
|
}
|
|
1300
1294
|
};
|
|
1301
|
-
element.draw(
|
|
1295
|
+
element.draw(customCtx, {
|
|
1302
1296
|
inner,
|
|
1303
1297
|
width: node.layout.contentWidth,
|
|
1304
1298
|
height: node.layout.contentHeight
|
|
1305
1299
|
});
|
|
1306
|
-
|
|
1300
|
+
customCtx.destroy();
|
|
1307
1301
|
ctx.restore();
|
|
1308
1302
|
}
|
|
1309
1303
|
//#endregion
|