@versa_ai/vmml-editor 1.0.16 → 1.0.18
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/.turbo/turbo-build.log +9 -9
- package/dist/index.js +37 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/EditorCanvas.tsx +32 -33
- package/src/index.tsx +8 -3
- package/src/utils/VmmlConverter.ts +16 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versa_ai/vmml-editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"module": "dist/index.mjs",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.mts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"remotion": "4.0.166",
|
|
17
17
|
"uuid": "^10.0.0",
|
|
18
18
|
"zod": "^3.23.8",
|
|
19
|
-
"@versa_ai/vmml-player": "1.1.
|
|
19
|
+
"@versa_ai/vmml-player": "1.1.16",
|
|
20
20
|
"@versa_ai/vmml-utils": "1.0.15"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
@@ -220,16 +220,6 @@ const EditorCanvas = forwardRef(
|
|
|
220
220
|
};
|
|
221
221
|
|
|
222
222
|
const createEditObjes = async (canvas: any, time: number) => {
|
|
223
|
-
// 先删除所有旧的可编辑对象
|
|
224
|
-
const allObjects = canvas.getObjects();
|
|
225
|
-
const toRemove: any[] = [];
|
|
226
|
-
allObjects.forEach((obj: any) => {
|
|
227
|
-
if (obj?.clipData?.originClip) {
|
|
228
|
-
toRemove.push(obj);
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
toRemove.forEach(obj => canvas.remove(obj));
|
|
232
|
-
|
|
233
223
|
const promises = editClips.map((clip: any) => {
|
|
234
224
|
if (clip.videoClip) {
|
|
235
225
|
return createImageFromClip(clip);
|
|
@@ -345,12 +335,14 @@ const EditorCanvas = forwardRef(
|
|
|
345
335
|
}
|
|
346
336
|
const { width, height } = vmml.template.dimension;
|
|
347
337
|
const fontSize = getFontSize(width, height);
|
|
348
|
-
const { textContent, backgroundColor, textColor, posParam, fontAssetUrl, alignType } = clip.textClip;
|
|
338
|
+
const { textContent, backgroundColor, textColor, posParam, fontAssetUrl, alignType, strokeColor, strokeWidth } = clip.textClip;
|
|
349
339
|
const scaleX = posParam.scaleX * fontSize / 22 / widthScaleRef.current;
|
|
350
340
|
const scaleY = posParam.scaleY * fontSize / 22 / heightScaleRef.current;
|
|
341
|
+
const strokeW = strokeWidth ? (strokeWidth * 22 / fontSize) : 0;
|
|
351
342
|
const left = canvasSize.width * posParam.centerX;
|
|
352
343
|
const top = canvasSize.height * posParam.centerY;
|
|
353
344
|
const bgColor = backgroundColor ? argbToRgba(backgroundColor) : 'transparent';
|
|
345
|
+
const stColor = strokeColor ? argbToRgba(strokeColor) : 'transparent';
|
|
354
346
|
const isAiError = textContent === '请输入文案' && textColor === '#00000000';
|
|
355
347
|
const textFill = argbToRgba(isAiError ? '#ffffffff' : (textColor || '#ffffffff'));
|
|
356
348
|
const textBasicInfo = {
|
|
@@ -359,7 +351,7 @@ const EditorCanvas = forwardRef(
|
|
|
359
351
|
colorName: 'custom',
|
|
360
352
|
textAlign: alignType === 1 ? 'center' : (alignType === 2 ? 'right' : 'left')
|
|
361
353
|
}
|
|
362
|
-
const textImgData = await createTextImg({ textContent, bgColor, textColor: textFill, fontAssetUrl, textBasicInfo });
|
|
354
|
+
const textImgData = await createTextImg({ textContent, bgColor, stColor, strokeW, textColor: textFill, fontAssetUrl, textBasicInfo });
|
|
363
355
|
const fontJSON = localStorage.getItem("VMML_PLAYER_FONTSMAP");
|
|
364
356
|
let fontMap: any = {};
|
|
365
357
|
try {
|
|
@@ -417,23 +409,22 @@ const EditorCanvas = forwardRef(
|
|
|
417
409
|
});
|
|
418
410
|
}
|
|
419
411
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
};
|
|
412
|
+
// 生成简短的字体名称
|
|
413
|
+
const getFontFamilyName = (url: string) => {
|
|
414
|
+
const filename = url.split('/').pop() || '';
|
|
415
|
+
const name = filename.replace(/\.(ttf|otf|woff2?|eot)$/i, '');
|
|
416
|
+
return `CustomFont_${name.substring(0, 20)}`; // 限制长度
|
|
417
|
+
};
|
|
427
418
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
419
|
+
// 检测字体格式
|
|
420
|
+
const detectFontFormat = (url: string) => {
|
|
421
|
+
const lower = url.toLowerCase();
|
|
422
|
+
if (lower.includes('.woff2')) return 'woff2';
|
|
423
|
+
if (lower.includes('.woff')) return 'woff';
|
|
424
|
+
if (lower.includes('.otf')) return 'opentype';
|
|
425
|
+
if (lower.includes('.ttf')) return 'truetype';
|
|
426
|
+
return null;
|
|
427
|
+
};
|
|
437
428
|
|
|
438
429
|
const embedFontInSVG = async (svgString: string, url: string) => {
|
|
439
430
|
if (url) {
|
|
@@ -455,7 +446,7 @@ const EditorCanvas = forwardRef(
|
|
|
455
446
|
}
|
|
456
447
|
|
|
457
448
|
//文字转图片
|
|
458
|
-
const createTextImg = async ({ textContent, bgColor, textColor, fontAssetUrl = null, textBasicInfo }: any) => {
|
|
449
|
+
const createTextImg = async ({ textContent, bgColor, textColor, stColor, strokeW, fontAssetUrl = null, textBasicInfo }: any) => {
|
|
459
450
|
const container = document.createElement('div');
|
|
460
451
|
container.style.backgroundColor = bgColor
|
|
461
452
|
// container.style.width = `fit-content`
|
|
@@ -472,8 +463,12 @@ const EditorCanvas = forwardRef(
|
|
|
472
463
|
p.style.lineHeight = '22px'
|
|
473
464
|
p.style.fontFamily = fontFamily;
|
|
474
465
|
p.style.whiteSpace = "nowrap"
|
|
475
|
-
// p.style.backgroundColor = bgColor;
|
|
476
466
|
p.style.padding = '0';
|
|
467
|
+
p.style.margin = '0';
|
|
468
|
+
|
|
469
|
+
p.style.webkitTextStrokeWidth = `${strokeW ?? 0}px`;
|
|
470
|
+
p.style.webkitTextStrokeColor = stColor;
|
|
471
|
+
|
|
477
472
|
p.textContent = line || " "
|
|
478
473
|
container.appendChild(p);
|
|
479
474
|
})
|
|
@@ -571,7 +566,6 @@ const EditorCanvas = forwardRef(
|
|
|
571
566
|
return fc.getObjects();
|
|
572
567
|
}
|
|
573
568
|
}
|
|
574
|
-
|
|
575
569
|
const styles: any = useMemo(() => {
|
|
576
570
|
return {
|
|
577
571
|
position: "absolute",
|
|
@@ -632,6 +626,10 @@ const EditorCanvas = forwardRef(
|
|
|
632
626
|
}
|
|
633
627
|
}, [fc]);
|
|
634
628
|
|
|
629
|
+
const getCanvasCtx = () => {
|
|
630
|
+
return fc
|
|
631
|
+
}
|
|
632
|
+
|
|
635
633
|
useImperativeHandle(ref, () => ({
|
|
636
634
|
createImage,
|
|
637
635
|
createText,
|
|
@@ -645,8 +643,9 @@ const EditorCanvas = forwardRef(
|
|
|
645
643
|
checkObjectInPoint,
|
|
646
644
|
createImageFromClip,
|
|
647
645
|
createTextFromClip,
|
|
648
|
-
changeObjectVisible
|
|
649
|
-
|
|
646
|
+
changeObjectVisible,
|
|
647
|
+
getCanvasCtx
|
|
648
|
+
}), [fc]);
|
|
650
649
|
|
|
651
650
|
const getActions = () => {
|
|
652
651
|
if (history) {
|
package/src/index.tsx
CHANGED
|
@@ -55,7 +55,7 @@ const EditorFn = <Schema extends AnyZodObject, Props>(
|
|
|
55
55
|
const videoList = useRef<any>([]);
|
|
56
56
|
const [signList, setSignList] = useState<any>([]);
|
|
57
57
|
const [tips, setTips] = useState("");
|
|
58
|
-
const [dragState, setDragState] = useState(0);
|
|
58
|
+
const [dragState, setDragState] = useState(0); // 1: onPointerDown 2: onPointerMove 3: onPointerUp 4: onClickSign
|
|
59
59
|
const vmmlConverterRef = useRef<any>(null);
|
|
60
60
|
const [initFcObjs, setInitFcObjs] = useState([]);
|
|
61
61
|
const [editClips, setEditClips] = useState<any[]>([]); // 可编辑的 clips
|
|
@@ -76,10 +76,12 @@ const EditorFn = <Schema extends AnyZodObject, Props>(
|
|
|
76
76
|
const onPlayerReady = () => {
|
|
77
77
|
const { current }: any = vmmlPlayerRef;
|
|
78
78
|
vmmlFlag.current = false;
|
|
79
|
+
let show = false
|
|
79
80
|
if (current && current.playerRef) {
|
|
80
81
|
setPlayer(current.playerRef);
|
|
81
82
|
current.unmute();
|
|
82
83
|
if (!once.current) {
|
|
84
|
+
show = true
|
|
83
85
|
once.current = true;
|
|
84
86
|
} else {
|
|
85
87
|
if (needPlay.current) {
|
|
@@ -89,7 +91,7 @@ const EditorFn = <Schema extends AnyZodObject, Props>(
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
|
-
setShowCanvas(
|
|
94
|
+
setShowCanvas(show);
|
|
93
95
|
setLoading(false);
|
|
94
96
|
};
|
|
95
97
|
|
|
@@ -264,9 +266,11 @@ const EditorFn = <Schema extends AnyZodObject, Props>(
|
|
|
264
266
|
setFrame(e.detail.frame)
|
|
265
267
|
};
|
|
266
268
|
const onPlay = () => {
|
|
269
|
+
setShowCanvas(false)
|
|
267
270
|
setIsPlaying(true);
|
|
268
271
|
};
|
|
269
272
|
const onPause = () => {
|
|
273
|
+
setShowCanvas(true)
|
|
270
274
|
setIsPlaying(false);
|
|
271
275
|
};
|
|
272
276
|
const onWaiting = () => {
|
|
@@ -426,7 +430,8 @@ const EditorFn = <Schema extends AnyZodObject, Props>(
|
|
|
426
430
|
const { current: playerCurrent }: any = vmmlPlayerRef;
|
|
427
431
|
const { current: canvasCurrent }: any = canvasRef;
|
|
428
432
|
if (!playerCurrent) return;
|
|
429
|
-
|
|
433
|
+
canvasCurrent?.getCanvasCtx()?.clear?.()
|
|
434
|
+
|
|
430
435
|
const convertedVmml = convertVmmlTextScaleByForbidden(v);
|
|
431
436
|
setVmmlState(convertedVmml);
|
|
432
437
|
setDurationInFrames(getFrames(v?.template?.duration || 1, fps));
|
|
@@ -28,7 +28,7 @@ class VmmlConverter {
|
|
|
28
28
|
//更新位置
|
|
29
29
|
private setPosParam(fObj: any): object {
|
|
30
30
|
const {
|
|
31
|
-
clipData: { type },
|
|
31
|
+
clipData: { type, originClip },
|
|
32
32
|
centerPoint,
|
|
33
33
|
scaleX,
|
|
34
34
|
scaleY,
|
|
@@ -36,28 +36,41 @@ class VmmlConverter {
|
|
|
36
36
|
width,
|
|
37
37
|
height,
|
|
38
38
|
} = fObj;
|
|
39
|
-
let _scaleX, _scaleY, _centerX, _centerY;
|
|
40
|
-
|
|
39
|
+
let _scaleX, _scaleY, _centerX, _centerY, _scaleZ, _centerZ, _rotationX, _rotationY;
|
|
40
|
+
let key = '';
|
|
41
41
|
if (type === "文字") {
|
|
42
42
|
// const [rect, textbox] = fObj.objects;
|
|
43
43
|
_scaleX = (22 * scaleX * this.widthScale) / this.fontSize;
|
|
44
44
|
_scaleY = (22 * scaleY * this.heightScale) / this.fontSize;
|
|
45
45
|
_centerX = centerPoint.x / this.canvasSize.width;
|
|
46
46
|
_centerY = centerPoint.y / this.canvasSize.height;
|
|
47
|
+
|
|
48
|
+
key = 'textClip'
|
|
47
49
|
// fObj.clipData.lineSpacing = 22 * textbox.lineHeight * scaleY * this.heightScale;
|
|
48
50
|
} else if (type === "表情包") {
|
|
49
51
|
_scaleX = (width * scaleX * this.widthScale) / width;
|
|
50
52
|
_scaleY = (height * scaleY * this.heightScale) / height;
|
|
51
53
|
_centerX = centerPoint.x / this.canvasSize.width;
|
|
52
54
|
_centerY = centerPoint.y / this.canvasSize.height;
|
|
55
|
+
|
|
56
|
+
key = 'videoClip'
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
_scaleZ = originClip?.[key]?.posParam?.scaleZ ?? 1;
|
|
60
|
+
_centerZ = originClip?.[key]?.posParam?.centerZ ?? 0.5;
|
|
61
|
+
_rotationX = originClip?.[key]?.posParam?.rotationX ?? 0;
|
|
62
|
+
_rotationY = originClip?.[key]?.posParam?.rotationY ?? 0;
|
|
63
|
+
|
|
55
64
|
return {
|
|
56
65
|
scaleX: _scaleX,
|
|
57
66
|
scaleY: _scaleY,
|
|
67
|
+
scaleZ: _scaleZ,
|
|
58
68
|
centerX: _centerX,
|
|
59
69
|
centerY: _centerY,
|
|
70
|
+
centerZ: _centerZ,
|
|
60
71
|
rotationZ: angle,
|
|
72
|
+
rotationX: _rotationX,
|
|
73
|
+
rotationY: _rotationY,
|
|
61
74
|
};
|
|
62
75
|
}
|
|
63
76
|
|