619 lines
16 KiB
JavaScript
619 lines
16 KiB
JavaScript
import { generateId, optimizeCanvasRendering, getLayerObjectsZIndex } from "../utils/helper";
|
||
import { createLayer, LayerType, OperationType } from "../utils/layerHelper";
|
||
import { Command } from "./Command";
|
||
import i18n from "@/lang/index.ts";
|
||
const { t } = i18n.global;
|
||
|
||
/**
|
||
* 文本内容命令
|
||
* 用于更改文本图层的文本内容
|
||
*/
|
||
export class TextContentCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本内容",
|
||
description: "修改文本图层的文本内容",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.newText = options.newText;
|
||
this.oldText = this.textObject.text;
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set("text", this.newText);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set("text", this.oldText);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文本字体命令
|
||
* 用于更改文本图层的字体
|
||
*/
|
||
export class TextFontCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本字体",
|
||
description: "修改文本图层的字体",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.newFont = options.newFont;
|
||
this.oldFont = this.textObject.fontFamily;
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set("fontFamily", this.newFont);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set("fontFamily", this.oldFont);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文本尺寸命令
|
||
* 用于更改文本图层的字体大小
|
||
*/
|
||
export class TextSizeCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本尺寸",
|
||
description: "修改文本图层的字体大小",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.newSize = options.newSize;
|
||
this.oldSize = this.textObject.fontSize;
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set("fontSize", this.newSize);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set("fontSize", this.oldSize);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文本颜色命令
|
||
* 用于更改文本图层的颜色
|
||
*/
|
||
export class TextColorCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本颜色",
|
||
description: "修改文本图层的颜色",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.newColor = options.newColor;
|
||
this.oldColor = this.textObject.fill;
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set("fill", this.newColor);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set("fill", this.oldColor);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文本对齐方式命令
|
||
* 用于更改文本图层的对齐方式
|
||
*/
|
||
export class TextAlignCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本对齐",
|
||
description: "修改文本图层的对齐方式",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.newAlign = options.newAlign;
|
||
this.oldAlign = this.textObject.textAlign;
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set("textAlign", this.newAlign);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set("textAlign", this.oldAlign);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文本样式命令
|
||
* 用于更改文本图层的样式(粗体、斜体、下划线等)
|
||
*/
|
||
export class TextStyleCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本样式",
|
||
description: "修改文本图层的样式",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.property = options.property; // 'fontWeight', 'fontStyle', 'underline', 'linethrough', 'overline'
|
||
this.newValue = options.newValue;
|
||
this.oldValue = this.textObject[this.property];
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set(this.property, this.newValue);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set(this.property, this.oldValue);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文本间距命令
|
||
* 用于更改文本图层的字符间距或行高
|
||
*/
|
||
export class TextSpacingCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本间距",
|
||
description: "修改文本图层的字符间距或行高",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.property = options.property; // 'charSpacing' 或 'lineHeight'
|
||
this.newValue = options.newValue;
|
||
this.oldValue = this.textObject[this.property];
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set(this.property, this.newValue);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set(this.property, this.oldValue);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文本背景颜色命令
|
||
* 用于更改文本图层的背景颜色
|
||
*/
|
||
export class TextBackgroundCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本背景",
|
||
description: "修改文本图层的背景颜色",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.newColor = options.newColor;
|
||
this.oldColor = this.textObject.textBackgroundColor;
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set("textBackgroundColor", this.newColor);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set("textBackgroundColor", this.oldColor);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 文本透明度命令
|
||
* 用于更改文本图层的透明度
|
||
*/
|
||
export class TextOpacityCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "修改文本透明度",
|
||
description: "修改文本图层的透明度",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.newOpacity = options.newOpacity;
|
||
this.oldOpacity = this.textObject.opacity;
|
||
}
|
||
|
||
execute() {
|
||
this.textObject.set("opacity", this.newOpacity);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
this.textObject.set("opacity", this.oldOpacity);
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 组合文本编辑命令
|
||
* 用于一次性应用多个文本属性更改
|
||
*/
|
||
export class CompositeTextCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "组合文本编辑",
|
||
description: "组合多个文本编辑操作",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.textObject = options.textObject;
|
||
this.changes = options.changes; // {property: newValue} 形式的对象
|
||
this.oldValues = {};
|
||
|
||
// 保存所有属性的旧值
|
||
for (const property in this.changes) {
|
||
if (this.textObject[property] !== undefined) {
|
||
this.oldValues[property] = this.textObject[property];
|
||
}
|
||
}
|
||
}
|
||
|
||
execute() {
|
||
for (const property in this.changes) {
|
||
this.textObject.set(property, this.changes[property]);
|
||
}
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
|
||
undo() {
|
||
for (const property in this.oldValues) {
|
||
this.textObject.set(property, this.oldValues[property]);
|
||
}
|
||
this.canvas.renderAll();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建文本命令
|
||
* 用于创建文本对象和图层的组合操作
|
||
*/
|
||
export class CreateTextCommand extends Command {
|
||
constructor(options) {
|
||
super({
|
||
name: "创建文本",
|
||
});
|
||
this.canvas = options.canvas;
|
||
this.layerManager = options.layerManager;
|
||
this.x = options.x;
|
||
this.y = options.y;
|
||
this.textOptions = options.textOptions || {};
|
||
this.options = options;
|
||
// 生成唯一ID
|
||
this.textId = options?.textId || generateId("text_");
|
||
this.layerId = options?.layerId || generateId("text_layer_");
|
||
|
||
// 生成的对象和图层信息
|
||
this.textObject = null;
|
||
this.oldActiveLayerId = null;
|
||
|
||
// 默认文本属性
|
||
this.defaultOptions = {
|
||
text: options.text || t('Canvas.DoubleClickText'),
|
||
fontFamily: "Arial",
|
||
fontSize: 24,
|
||
fontWeight: "normal",
|
||
fontStyle: "normal",
|
||
textAlign: "left",
|
||
fill: "#000000",
|
||
opacity: 1,
|
||
underline: false,
|
||
overline: false,
|
||
linethrough: false,
|
||
textBackgroundColor: "transparent",
|
||
lineHeight: 1.16,
|
||
charSpacing: 0,
|
||
};
|
||
}
|
||
|
||
async execute() {
|
||
if (!this.canvas || !this.layerManager) {
|
||
console.error("Canvas或LayerManager不存在");
|
||
return null;
|
||
}
|
||
// 保存当前活动图层
|
||
this.oldActiveLayerId = this.layerManager.activeLayerId?.value;
|
||
|
||
// 合并默认选项和用户选项
|
||
const finalOptions = {
|
||
...this.defaultOptions,
|
||
...this.textOptions,
|
||
left: this.x,
|
||
top: this.y,
|
||
};
|
||
|
||
try {
|
||
await optimizeCanvasRendering(this.canvas, async () => {
|
||
// 创建文本对象
|
||
this.textObject = new fabric.IText(finalOptions.text, {
|
||
...finalOptions,
|
||
originX: "center",
|
||
originY: "center",
|
||
});
|
||
|
||
// 创建文本图层 取15
|
||
const layerName = this.options.text?.substring(0, 25) || t('Canvas.TextLayer');
|
||
const layer = createLayer({
|
||
id: this.layerId,
|
||
name: layerName,
|
||
type: LayerType.TEXT,
|
||
});
|
||
|
||
// 设置对象的图层关联
|
||
this.textObject.set({
|
||
id: this.textId,
|
||
layerId: this.layerId,
|
||
layerName: layerName,
|
||
});
|
||
|
||
// 智能插入图层到合适位置
|
||
this._insertLayerAtCorrectPosition(layer);
|
||
|
||
// 添加到画布
|
||
this.canvas.add(this.textObject);
|
||
|
||
// 取消其他对象的选中状态
|
||
this.canvas.discardActiveObject();
|
||
|
||
// 设置新创建的文本对象为活动对象
|
||
this.canvas.setActiveObject(this.textObject);
|
||
|
||
// 更新图层的对象列表
|
||
if (layer) {
|
||
layer.fabricObjects = layer.fabricObjects || [];
|
||
layer.fabricObjects.push(this.textObject.toObject(["id", "layerId", "layerName"]));
|
||
}
|
||
|
||
// 现在可以安全地设置为活动图层
|
||
this.layerManager.setActiveLayer(this.layerId);
|
||
|
||
// 重新排序图层对象
|
||
await this.layerManager?.layerSort?.rearrangeObjects();
|
||
|
||
// 更新对象交互性
|
||
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
||
|
||
// 切换到选择工具
|
||
this.layerManager?.toolManager?.setTool?.(OperationType.SELECT);
|
||
});
|
||
|
||
console.log(`✅ 文本对象已创建: "${finalOptions.text}",位置: (${this.x}, ${this.y})`);
|
||
return this.textObject;
|
||
} catch (error) {
|
||
console.error("创建文本对象失败:", error);
|
||
// 如果创建失败,需要清理已创建的资源
|
||
await this.undo();
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 智能插入图层到正确位置
|
||
* 根据当前激活图层位置确定新图层插入位置
|
||
* @param {Object} newLayer 要插入的新图层
|
||
* @private
|
||
*/
|
||
_insertLayerAtCorrectPosition(newLayer) {
|
||
const layers = this.layerManager.layers.value;
|
||
const currentActiveLayerId = this.layerManager.activeLayerId?.value;
|
||
|
||
// 如果没有当前激活图层,插入到顶部(索引0)
|
||
if (!currentActiveLayerId) {
|
||
layers.splice(0, 0, newLayer);
|
||
return;
|
||
}
|
||
|
||
// 查找当前激活图层的位置
|
||
const {
|
||
layer: activeLayer,
|
||
parent: parentLayer,
|
||
index: activeIndex,
|
||
} = this._findLayerPosition(currentActiveLayerId);
|
||
|
||
if (!activeLayer) {
|
||
// 没找到激活图层,插入到顶部
|
||
layers.splice(0, 0, newLayer);
|
||
return;
|
||
}
|
||
|
||
// 确定插入位置
|
||
let insertIndex = 0;
|
||
|
||
if (parentLayer) {
|
||
// 当前激活图层是子图层
|
||
// 在同一父图层内,插入到激活子图层之上
|
||
insertIndex = Math.max(0, activeIndex);
|
||
parentLayer.children = parentLayer.children || [];
|
||
parentLayer.children.splice(insertIndex, 0, newLayer);
|
||
|
||
console.log(`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`);
|
||
} else {
|
||
// 当前激活图层是一级图层
|
||
// 在一级图层中,插入到激活图层之上
|
||
const activeLayerIndex = layers.findIndex((layer) => layer.id === currentActiveLayerId);
|
||
insertIndex = Math.max(0, activeLayerIndex);
|
||
layers.splice(insertIndex, 0, newLayer);
|
||
|
||
console.log(`新图层已插入到一级图层位置: ${insertIndex}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查找图层位置信息
|
||
* @param {String} layerId 图层ID
|
||
* @returns {Object} 包含图层、父图层和索引的对象
|
||
* @private
|
||
*/
|
||
_findLayerPosition(layerId) {
|
||
const layers = this.layerManager.layers.value;
|
||
|
||
// 先在一级图层中查找
|
||
for (let i = 0; i < layers.length; i++) {
|
||
const layer = layers[i];
|
||
if (layer.isPrintTrimsGroup) continue;
|
||
if (layer.id === layerId) {
|
||
return {
|
||
layer: layer,
|
||
parent: null,
|
||
index: i,
|
||
};
|
||
}
|
||
|
||
// 在子图层中查找
|
||
if (layer.children && Array.isArray(layer.children)) {
|
||
for (let j = 0; j < layer.children.length; j++) {
|
||
const childLayer = layer.children[j];
|
||
if (childLayer.id === layerId) {
|
||
return {
|
||
layer: childLayer,
|
||
parent: layer,
|
||
index: j,
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
layer: null,
|
||
parent: null,
|
||
index: -1,
|
||
};
|
||
}
|
||
|
||
getInfo() {
|
||
return {
|
||
name: this.name,
|
||
textId: this.textObject?.id,
|
||
layerId: this.layerId,
|
||
text: this.textOptions.text || this.defaultOptions.text,
|
||
position: { x: this.x, y: this.y },
|
||
};
|
||
}
|
||
|
||
async undo() {
|
||
try {
|
||
// 从画布移除文本对象
|
||
if (this.textObject && this.canvas) {
|
||
this.canvas.remove(this.textObject);
|
||
}
|
||
const layerObjects = getLayerObjectsZIndex(this.canvas, this.layerId);
|
||
layerObjects.forEach((obj) => {
|
||
if (obj.id === this.textObject?.id) {
|
||
this.canvas.remove(obj.object);
|
||
}
|
||
});
|
||
|
||
// 智能移除创建的图层
|
||
if (this.layerId && this.layerManager) {
|
||
this._removeLayerFromCorrectPosition();
|
||
}
|
||
|
||
// 恢复原活动图层
|
||
if (this.oldActiveLayerId && this.layerManager) {
|
||
// 检查原活动图层是否还存在
|
||
const originalLayer = this.layerManager.getLayerById(this.oldActiveLayerId);
|
||
if (originalLayer) {
|
||
this.layerManager.setActiveLayer(this.oldActiveLayerId);
|
||
} else {
|
||
// 如果原图层不存在,设置为第一个可用的普通图层
|
||
const availableLayers = this.layerManager.layers.value.filter(
|
||
(layer) => !layer.isBackground && !layer.isFixed && !layer.locked
|
||
);
|
||
if (availableLayers.length > 0) {
|
||
this.layerManager.setActiveLayer(availableLayers[0].id);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 更新对象交互性
|
||
await this.layerManager.updateLayersObjectsInteractivity();
|
||
|
||
// 重新渲染画布
|
||
if (this.canvas) {
|
||
this.canvas.renderAll();
|
||
}
|
||
|
||
console.log(`↩️ 文本创建操作已撤销`);
|
||
return true;
|
||
} catch (error) {
|
||
console.error("撤销文本创建操作失败:", error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 智能移除图层
|
||
* 根据图层位置(一级图层或子图层)进行相应的移除操作
|
||
* @private
|
||
*/
|
||
_removeLayerFromCorrectPosition() {
|
||
const layers = this.layerManager.layers.value;
|
||
|
||
// 查找图层位置信息
|
||
const positionInfo = this._findLayerPosition(this.layerId);
|
||
|
||
if (!positionInfo.layer) {
|
||
console.warn(`要移除的图层不存在: ${this.layerId}`);
|
||
return;
|
||
}
|
||
|
||
if (positionInfo.parent) {
|
||
// 从子图层中移除
|
||
if (positionInfo.parent.children && positionInfo.index >= 0) {
|
||
positionInfo.parent.children.splice(positionInfo.index, 1);
|
||
console.log(`已从子图层移除: ${this.layerId} (父图层: ${positionInfo.parent.name})`);
|
||
}
|
||
} else {
|
||
// 从一级图层中移除
|
||
if (positionInfo.index >= 0) {
|
||
layers.splice(positionInfo.index, 1);
|
||
console.log(`已从一级图层移除: ${this.layerId}`);
|
||
}
|
||
}
|
||
}
|
||
}
|