feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type Canvas,
type FabricObject,
type Point,
type TMat2D,
type Textbox,
} from 'fabric';
import { Mode, type FabricObjectWithCustomProps } from '../typings';
/**
* 缩放到指定点
*/
export const zoomToPoint = ({
canvas,
point,
zoomLevel,
minZoom,
maxZoom,
}: {
point: Point;
zoomLevel: number;
canvas?: Canvas;
minZoom: number;
maxZoom: number;
}): TMat2D => {
// 设置缩放级别的限制
zoomLevel = Math.max(zoomLevel, minZoom); // 最小缩放级别
zoomLevel = Math.min(zoomLevel, maxZoom); // 最大缩放级别
// 以鼠标位置为中心进行缩放
canvas?.zoomToPoint(point, zoomLevel);
return [...(canvas?.viewportTransform as TMat2D)];
};
/**
* 设置 canvas 视图
*/
export const setViewport = ({
canvas,
vpt,
}: {
vpt: TMat2D;
canvas?: Canvas;
}): TMat2D => {
canvas?.setViewportTransform(vpt);
canvas?.requestRenderAll();
return [...(canvas?.viewportTransform as TMat2D)];
};
/**
* 画布坐标点距离画布左上角距离单位px
*/
export const canvasXYToScreen = ({
canvas,
scale,
point,
}: {
canvas: Canvas;
scale: number;
point: { x: number; y: number };
}) => {
// 获取画布的变换矩阵
const transform = canvas.viewportTransform;
// 应用缩放和平移
const zoomX = transform[0];
const zoomY = transform[3];
const translateX = transform[4];
const translateY = transform[5];
const screenX = (point.x * zoomX + translateX) * scale;
const screenY = (point.y * zoomY + translateY) * scale;
// 获取画布在屏幕上的位置
const x = screenX;
const y = screenY;
// 不做限制
return {
x,
y,
};
};
/**
* 得到选中元素的屏幕坐标(左上 tl、右下 br
*/
export const getPopPosition = ({
canvas,
scale,
}: {
canvas: Canvas;
scale: number;
}) => {
const selection = canvas?.getActiveObject();
if (canvas && selection) {
const boundingRect = selection.getBoundingRect();
// 左上角坐标
const tl = {
x: boundingRect.left,
y: boundingRect.top,
};
// 右下角坐标
const br = {
x: boundingRect.left + boundingRect.width,
y: boundingRect.top + boundingRect.height,
};
return {
tl: canvasXYToScreen({ canvas, scale, point: tl }),
br: canvasXYToScreen({ canvas, scale, point: br }),
};
}
return {
tl: {
x: -9999,
y: -9999,
},
br: {
x: -9999,
y: -9999,
},
};
};
export const resetElementClip = ({ element }: { element: FabricObject }) => {
if (!element.clipPath) {
return;
}
const clipRect = element.clipPath;
const padding = (element as Textbox).padding ?? 0;
const { height, width } = element as FabricObject;
const _height = height + padding * 2;
const _width = width + padding * 2;
const newPosition = {
originX: 'left',
originY: 'top',
left: -_width / 2,
top: -_height / 2,
height: _height,
width: _width,
absolutePositioned: false,
};
clipRect?.set(newPosition);
};
export const isGroupElement = (obj?: FabricObject) =>
(obj as FabricObjectWithCustomProps)?.customType === Mode.GROUP;