goatdee-canvas 0.0.33 → 0.0.34
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/dist/ZHCanvasCore.wasm +0 -0
- package/dist/index.cjs +306 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +307 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/ZHCanvasCore.wasm
CHANGED
|
Binary file
|
package/dist/index.cjs
CHANGED
|
@@ -1169,22 +1169,308 @@ const initializeFonts = async () => {
|
|
|
1169
1169
|
}
|
|
1170
1170
|
};
|
|
1171
1171
|
|
|
1172
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1173
|
+
// 子类型 schema(CanvasShadow / CanvasLinearGradient)
|
|
1174
|
+
//
|
|
1175
|
+
// 通过 `satisfies SchemaFor<T>` 与 types.ts 强绑定:
|
|
1176
|
+
// - T 字段新增 → schema 缺少该 key → 编译报错
|
|
1177
|
+
// - T 字段改类型 → schema FieldType 不满足新约束 → 编译报错
|
|
1178
|
+
// - schema 多余 key → 编译报错(excess property check)
|
|
1179
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1180
|
+
const shadowSchema = {
|
|
1181
|
+
blur: 'number',
|
|
1182
|
+
color: 'string',
|
|
1183
|
+
offsetX: 'number',
|
|
1184
|
+
offsetY: 'number',
|
|
1185
|
+
};
|
|
1186
|
+
const linearGradientSchema = {
|
|
1187
|
+
type: 'string',
|
|
1188
|
+
coords: 'object',
|
|
1189
|
+
colorStops: 'array',
|
|
1190
|
+
};
|
|
1191
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1192
|
+
// 基础类型校验(供子类型 schema 驱动的循环内使用,避免递归调用 matchesFieldType)
|
|
1193
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1194
|
+
function checkPrimitive(value, ft) {
|
|
1195
|
+
if (ft === 'array')
|
|
1196
|
+
return Array.isArray(value);
|
|
1197
|
+
if (ft === 'object')
|
|
1198
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1199
|
+
return typeof value === ft;
|
|
1200
|
+
}
|
|
1201
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1202
|
+
// 复合类型守卫
|
|
1203
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1204
|
+
/** CanvasFill / CanvasStroke:string | CanvasLinearGradient | null */
|
|
1205
|
+
function isFill(value) {
|
|
1206
|
+
if (value === null || typeof value === 'string')
|
|
1207
|
+
return true;
|
|
1208
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
1209
|
+
const v = value;
|
|
1210
|
+
return Object.entries(linearGradientSchema)
|
|
1211
|
+
.every(([key, ft]) => checkPrimitive(v[key], ft));
|
|
1212
|
+
}
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
/** CanvasShadow:由 shadowSchema 驱动,与 types.ts 中 CanvasShadow 强绑定 */
|
|
1216
|
+
function isShadow(value) {
|
|
1217
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
1218
|
+
return false;
|
|
1219
|
+
const v = value;
|
|
1220
|
+
return Object.entries(shadowSchema)
|
|
1221
|
+
.every(([key, ft]) => checkPrimitive(v[key], ft));
|
|
1222
|
+
}
|
|
1223
|
+
/** TextStyles(unknown):null / object / array 均合法,拒绝 string / number / boolean 等原始值 */
|
|
1224
|
+
function isTextStyles(value) {
|
|
1225
|
+
return value === null || typeof value === 'object';
|
|
1226
|
+
}
|
|
1227
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1228
|
+
// 主校验函数
|
|
1229
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1230
|
+
/** 判断值是否符合 FieldType 规则 */
|
|
1231
|
+
function matchesFieldType(value, fieldType) {
|
|
1232
|
+
switch (fieldType) {
|
|
1233
|
+
case 'array': return Array.isArray(value);
|
|
1234
|
+
case 'object': return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1235
|
+
case 'fill': return isFill(value);
|
|
1236
|
+
case 'shadow': return isShadow(value);
|
|
1237
|
+
case 'shadow-or-null': return value === null || isShadow(value);
|
|
1238
|
+
case 'number-or-null': return value === null || typeof value === 'number';
|
|
1239
|
+
case 'string-or-null': return value === null || typeof value === 'string';
|
|
1240
|
+
case 'text-styles': return isTextStyles(value);
|
|
1241
|
+
default: return typeof value === fieldType;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* 所有对象类型共享的公共字段 schema。
|
|
1247
|
+
* 各对象类型 schema 通过扩展此对象来复用这些字段。
|
|
1248
|
+
*/
|
|
1249
|
+
const baseSchema = {
|
|
1250
|
+
id: 'string',
|
|
1251
|
+
name: 'string',
|
|
1252
|
+
left: 'number',
|
|
1253
|
+
top: 'number',
|
|
1254
|
+
width: 'number',
|
|
1255
|
+
height: 'number',
|
|
1256
|
+
scaleX: 'number',
|
|
1257
|
+
scaleY: 'number',
|
|
1258
|
+
angle: 'number',
|
|
1259
|
+
strokeWidth: 'number',
|
|
1260
|
+
opacity: 'number',
|
|
1261
|
+
visible: 'boolean',
|
|
1262
|
+
locked: 'boolean',
|
|
1263
|
+
selectable: 'boolean',
|
|
1264
|
+
lockMovementX: 'boolean',
|
|
1265
|
+
lockMovementY: 'boolean',
|
|
1266
|
+
lockRotation: 'boolean',
|
|
1267
|
+
lockScalingX: 'boolean',
|
|
1268
|
+
lockScalingY: 'boolean',
|
|
1269
|
+
lockUniScaling: 'boolean',
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
const textboxSchema = {
|
|
1273
|
+
...baseSchema,
|
|
1274
|
+
type: 'string',
|
|
1275
|
+
flipX: 'boolean',
|
|
1276
|
+
flipY: 'boolean',
|
|
1277
|
+
text: 'string',
|
|
1278
|
+
fontSize: 'number',
|
|
1279
|
+
lineHeight: 'number',
|
|
1280
|
+
fontFamily: 'string',
|
|
1281
|
+
fill: 'fill',
|
|
1282
|
+
stroke: 'fill',
|
|
1283
|
+
shadow: 'shadow-or-null',
|
|
1284
|
+
textAlign: 'string',
|
|
1285
|
+
charSpacing: 'number',
|
|
1286
|
+
underline: 'boolean',
|
|
1287
|
+
linethrough: 'boolean',
|
|
1288
|
+
bold: 'boolean',
|
|
1289
|
+
italic: 'boolean',
|
|
1290
|
+
styles: 'text-styles',
|
|
1291
|
+
editable: 'boolean',
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
const imageSchema = {
|
|
1295
|
+
...baseSchema,
|
|
1296
|
+
type: 'string',
|
|
1297
|
+
flipX: 'boolean',
|
|
1298
|
+
flipY: 'boolean',
|
|
1299
|
+
src: 'string',
|
|
1300
|
+
cropX: 'number',
|
|
1301
|
+
cropY: 'number',
|
|
1302
|
+
loading: 'string-or-null',
|
|
1303
|
+
rx: 'number',
|
|
1304
|
+
ry: 'number',
|
|
1305
|
+
backgroundColor: 'fill',
|
|
1306
|
+
stroke: 'fill',
|
|
1307
|
+
shadow: 'shadow-or-null',
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
const circleSchema = {
|
|
1311
|
+
...baseSchema,
|
|
1312
|
+
type: 'string',
|
|
1313
|
+
flipX: 'boolean',
|
|
1314
|
+
flipY: 'boolean',
|
|
1315
|
+
fill: 'fill',
|
|
1316
|
+
stroke: 'fill',
|
|
1317
|
+
shadow: 'shadow-or-null',
|
|
1318
|
+
borderDashArray: 'array',
|
|
1319
|
+
radius: 'number',
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
const rectangleSchema = {
|
|
1323
|
+
...baseSchema,
|
|
1324
|
+
type: 'string',
|
|
1325
|
+
flipX: 'boolean',
|
|
1326
|
+
flipY: 'boolean',
|
|
1327
|
+
fill: 'fill',
|
|
1328
|
+
stroke: 'fill',
|
|
1329
|
+
shadow: 'shadow-or-null',
|
|
1330
|
+
borderDashArray: 'array',
|
|
1331
|
+
rx: 'number',
|
|
1332
|
+
ry: 'number',
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
const triangleSchema = {
|
|
1336
|
+
...baseSchema,
|
|
1337
|
+
type: 'string',
|
|
1338
|
+
flipX: 'boolean',
|
|
1339
|
+
flipY: 'boolean',
|
|
1340
|
+
fill: 'fill',
|
|
1341
|
+
stroke: 'fill',
|
|
1342
|
+
shadow: 'shadow-or-null',
|
|
1343
|
+
borderDashArray: 'array',
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1346
|
+
const groupSchema = {
|
|
1347
|
+
...baseSchema,
|
|
1348
|
+
type: 'string',
|
|
1349
|
+
flipX: 'boolean',
|
|
1350
|
+
flipY: 'boolean',
|
|
1351
|
+
objects: 'array',
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
const frameSchema = {
|
|
1355
|
+
...baseSchema,
|
|
1356
|
+
type: 'string',
|
|
1357
|
+
backgroundColor: 'fill',
|
|
1358
|
+
stroke: 'fill',
|
|
1359
|
+
autoScaleBg: 'boolean',
|
|
1360
|
+
crossable: 'boolean',
|
|
1361
|
+
clipContent: 'boolean',
|
|
1362
|
+
showName: 'boolean',
|
|
1363
|
+
showSize: 'boolean',
|
|
1364
|
+
objects: 'array',
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
const slideSchema = {
|
|
1368
|
+
...baseSchema,
|
|
1369
|
+
type: 'string',
|
|
1370
|
+
backgroundColor: 'fill',
|
|
1371
|
+
stroke: 'fill',
|
|
1372
|
+
contentBackgroundColor: 'fill',
|
|
1373
|
+
contentStrokeWidth: 'number',
|
|
1374
|
+
contentStroke: 'fill',
|
|
1375
|
+
paddingX: 'number',
|
|
1376
|
+
paddingY: 'number',
|
|
1377
|
+
rx: 'number',
|
|
1378
|
+
ry: 'number',
|
|
1379
|
+
crossable: 'boolean',
|
|
1380
|
+
showName: 'boolean',
|
|
1381
|
+
showSize: 'boolean',
|
|
1382
|
+
objects: 'array',
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1385
|
+
const placeholderSchema = {
|
|
1386
|
+
type: 'string',
|
|
1387
|
+
id: 'string',
|
|
1388
|
+
name: 'string',
|
|
1389
|
+
left: 'number',
|
|
1390
|
+
top: 'number',
|
|
1391
|
+
width: 'number',
|
|
1392
|
+
height: 'number',
|
|
1393
|
+
export: 'boolean',
|
|
1394
|
+
locked: 'boolean',
|
|
1395
|
+
lockMovementX: 'boolean',
|
|
1396
|
+
lockMovementY: 'boolean',
|
|
1397
|
+
lockRotation: 'boolean',
|
|
1398
|
+
lockScalingX: 'boolean',
|
|
1399
|
+
lockScalingY: 'boolean',
|
|
1400
|
+
lockUniScaling: 'boolean',
|
|
1401
|
+
selectable: 'boolean',
|
|
1402
|
+
visible: 'boolean',
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
const pathSchema = {
|
|
1406
|
+
...baseSchema,
|
|
1407
|
+
type: 'string',
|
|
1408
|
+
flipX: 'boolean',
|
|
1409
|
+
flipY: 'boolean',
|
|
1410
|
+
stroke: 'fill',
|
|
1411
|
+
points: 'array',
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* 对象类型 → schema 映射表。
|
|
1416
|
+
* grid / line / grid-cell 使用宽松 schema(CanvasGridLineCellLoose),透传不校验。
|
|
1417
|
+
*
|
|
1418
|
+
* 各 schema 值以 `as SchemaEntry` 断言:schema 文件用 satisfies 保证了每个字段的
|
|
1419
|
+
* 值都是合法的 FieldType 字面量,断言仅消除 TS 对字面量宽泛推断的误报。
|
|
1420
|
+
*/
|
|
1421
|
+
const schemaMap = {
|
|
1422
|
+
textbox: textboxSchema,
|
|
1423
|
+
image: imageSchema,
|
|
1424
|
+
circle: circleSchema,
|
|
1425
|
+
rectangle: rectangleSchema,
|
|
1426
|
+
triangle: triangleSchema,
|
|
1427
|
+
group: groupSchema,
|
|
1428
|
+
frame: frameSchema,
|
|
1429
|
+
slide: slideSchema,
|
|
1430
|
+
placeholder: placeholderSchema,
|
|
1431
|
+
path: pathSchema,
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1172
1434
|
var utils;
|
|
1173
1435
|
(function (utils) {
|
|
1436
|
+
/**
|
|
1437
|
+
* 对属性对象进行安全校验:
|
|
1438
|
+
* - null / undefined / 非对象:原样返回
|
|
1439
|
+
* - 已知对象类型(textbox / image / circle 等):按 schema 逐字段校验类型,
|
|
1440
|
+
* 类型不符的字段打印警告并丢弃,合法字段保留
|
|
1441
|
+
* - 未知类型(grid / line / grid-cell 等):透传不校验
|
|
1442
|
+
*/
|
|
1174
1443
|
function safeProperties(properties) {
|
|
1175
|
-
|
|
1176
|
-
|
|
1444
|
+
if (properties === null || properties === undefined)
|
|
1445
|
+
return properties;
|
|
1446
|
+
if (typeof properties !== 'object' || Array.isArray(properties))
|
|
1447
|
+
return properties;
|
|
1448
|
+
const schema = schemaMap[properties.type];
|
|
1449
|
+
if (!schema)
|
|
1450
|
+
return properties;
|
|
1451
|
+
const result = {};
|
|
1452
|
+
for (const [key, fieldType] of Object.entries(schema)) {
|
|
1453
|
+
if (!(key in properties))
|
|
1454
|
+
continue;
|
|
1455
|
+
const value = properties[key];
|
|
1456
|
+
if (matchesFieldType(value, fieldType)) {
|
|
1457
|
+
result[key] = (key === 'objects' && Array.isArray(value))
|
|
1458
|
+
? value.map(item => safeProperties(item))
|
|
1459
|
+
: value;
|
|
1460
|
+
}
|
|
1461
|
+
else {
|
|
1462
|
+
console.warn(`[safeProperties] 属性 "${key}" 类型不符:期望 ${fieldType},实际 ${Array.isArray(value) ? 'array' : typeof value},已忽略`);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
return result;
|
|
1177
1466
|
}
|
|
1178
1467
|
utils.safeProperties = safeProperties;
|
|
1179
1468
|
function safeAddOptions(options) {
|
|
1180
1469
|
if (!options)
|
|
1181
1470
|
return options;
|
|
1182
1471
|
// 如果包含 points 属性,则过滤掉该属性
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
console.log('safeAddOptions', options);
|
|
1187
|
-
return options;
|
|
1472
|
+
const { points: _points, ...rest } = options;
|
|
1473
|
+
return rest;
|
|
1188
1474
|
}
|
|
1189
1475
|
utils.safeAddOptions = safeAddOptions;
|
|
1190
1476
|
/**
|
|
@@ -1195,19 +1481,13 @@ var utils;
|
|
|
1195
1481
|
*/
|
|
1196
1482
|
function debounce(fn, delay) {
|
|
1197
1483
|
let timer = null;
|
|
1198
|
-
let lastCallTime = 0;
|
|
1199
1484
|
return function (...args) {
|
|
1200
|
-
const now = Date.now();
|
|
1201
|
-
lastCallTime = now;
|
|
1202
1485
|
if (timer !== null) {
|
|
1203
1486
|
clearTimeout(timer);
|
|
1204
1487
|
}
|
|
1205
1488
|
timer = window.setTimeout(() => {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
fn.apply(this, args);
|
|
1209
|
-
timer = null;
|
|
1210
|
-
}
|
|
1489
|
+
fn.apply(this, args);
|
|
1490
|
+
timer = null;
|
|
1211
1491
|
}, delay);
|
|
1212
1492
|
};
|
|
1213
1493
|
}
|
|
@@ -3763,8 +4043,15 @@ const setObjectProperties = async (id, properties) => {
|
|
|
3763
4043
|
if (properties.src && typeof properties.src === "string") {
|
|
3764
4044
|
await manager.registerImageIfNeeded(properties.src);
|
|
3765
4045
|
}
|
|
3766
|
-
//
|
|
3767
|
-
|
|
4046
|
+
// 如果调用方没有传 type,从对象数据中补充,确保 safeProperties 能找到对应 schema
|
|
4047
|
+
let propsForValidation = properties;
|
|
4048
|
+
if (!properties.type) {
|
|
4049
|
+
const obj = getObject(id);
|
|
4050
|
+
if (obj === null || obj === void 0 ? void 0 : obj.type) {
|
|
4051
|
+
propsForValidation = { ...properties, type: obj.type };
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
const safeProperties = utils.safeProperties(propsForValidation);
|
|
3768
4055
|
canvas.setObjectProperties(id, safeProperties);
|
|
3769
4056
|
}
|
|
3770
4057
|
catch (error) {
|
|
@@ -3779,8 +4066,8 @@ const setObjectProperties = async (id, properties) => {
|
|
|
3779
4066
|
const setCanvasProperties = async (properties) => {
|
|
3780
4067
|
try {
|
|
3781
4068
|
const canvas = manager.getCanvas();
|
|
3782
|
-
const safeProperties = utils.safeProperties(properties);
|
|
3783
|
-
canvas.setCanvasProperties(
|
|
4069
|
+
// const safeProperties = utils.safeProperties(properties);
|
|
4070
|
+
canvas.setCanvasProperties(properties);
|
|
3784
4071
|
}
|
|
3785
4072
|
catch (error) {
|
|
3786
4073
|
console.warn("Canvas operation(setCanvasProperties) failed:", error);
|