coze-studio/frontend/packages/workflow/fabric-canvas/src/utils/fabric-utils.tsx

174 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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;