jack 6 дней назад
Родитель
Сommit
c0241d8bf3

+ 0 - 147
package-lock.json

@@ -53,7 +53,6 @@
         "vuedraggable": "^4.1.0",
         "vuedraggable": "^4.1.0",
         "wangeditor": "^4.7.15",
         "wangeditor": "^4.7.15",
         "webcodecs-encoder": "^0.3.2",
         "webcodecs-encoder": "^0.3.2",
-        "wmf2png": "^1.0.0",
         "y-websocket": "^3.0.0",
         "y-websocket": "^3.0.0",
         "yjs": "^13.6.27"
         "yjs": "^13.6.27"
       },
       },
@@ -2000,15 +1999,6 @@
         "node": ">=0.10.0"
         "node": ">=0.10.0"
       }
       }
     },
     },
-    "node_modules/assert-plus": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz",
-      "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.8"
-      }
-    },
     "node_modules/asynckit": {
     "node_modules/asynckit": {
       "version": "0.4.0",
       "version": "0.4.0",
       "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
       "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
@@ -2964,15 +2954,6 @@
         "node": ">=10"
         "node": ">=10"
       }
       }
     },
     },
-    "node_modules/extsprintf": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz",
-      "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
-      "engines": [
-        "node >=0.6.0"
-      ],
-      "license": "MIT"
-    },
     "node_modules/fast-deep-equal": {
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "version": "3.1.3",
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4549,15 +4530,6 @@
       "resolved": "https://registry.npmmirror.com/orderedmap/-/orderedmap-2.1.1.tgz",
       "resolved": "https://registry.npmmirror.com/orderedmap/-/orderedmap-2.1.1.tgz",
       "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="
       "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="
     },
     },
-    "node_modules/os-tmpdir": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/p-limit": {
     "node_modules/p-limit": {
       "version": "3.1.0",
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
       "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
@@ -5976,38 +5948,6 @@
         "spdx-expression-parse": "^3.0.0"
         "spdx-expression-parse": "^3.0.0"
       }
       }
     },
     },
-    "node_modules/vasync": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmmirror.com/vasync/-/vasync-2.2.1.tgz",
-      "integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
-      "engines": [
-        "node >=0.6.0"
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "verror": "1.10.0"
-      }
-    },
-    "node_modules/verror": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz",
-      "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
-      "engines": [
-        "node >=0.6.0"
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "assert-plus": "^1.0.0",
-        "core-util-is": "1.0.2",
-        "extsprintf": "^1.2.0"
-      }
-    },
-    "node_modules/verror/node_modules/core-util-is": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz",
-      "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
-      "license": "MIT"
-    },
     "node_modules/vite": {
     "node_modules/vite": {
       "version": "5.3.5",
       "version": "5.3.5",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-5.3.5.tgz",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-5.3.5.tgz",
@@ -6190,34 +6130,6 @@
         "node": ">= 8"
         "node": ">= 8"
       }
       }
     },
     },
-    "node_modules/wmf2png": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmmirror.com/wmf2png/-/wmf2png-1.0.0.tgz",
-      "integrity": "sha512-sPVTVKcDdH+oSv9WEkHB3DFUGUfUwy5HH2liQqN8Elqj6YObYac0bkz/L6FeYCgvuBjZicn23CVBBEbNo65I+A==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "BSD-2-Clause",
-      "os": [
-        "win32"
-      ],
-      "dependencies": {
-        "tmp": "0.0.33",
-        "vasync": "^2.2.0"
-      }
-    },
-    "node_modules/wmf2png/node_modules/tmp": {
-      "version": "0.0.33",
-      "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz",
-      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
-      "license": "MIT",
-      "dependencies": {
-        "os-tmpdir": "~1.0.2"
-      },
-      "engines": {
-        "node": ">=0.6.0"
-      }
-    },
     "node_modules/wrap-ansi": {
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
       "version": "7.0.0",
       "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
       "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -7727,11 +7639,6 @@
       "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
       "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
       "dev": true
       "dev": true
     },
     },
-    "assert-plus": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz",
-      "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
-    },
     "asynckit": {
     "asynckit": {
       "version": "0.4.0",
       "version": "0.4.0",
       "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
       "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
@@ -8452,11 +8359,6 @@
         "strip-final-newline": "^2.0.0"
         "strip-final-newline": "^2.0.0"
       }
       }
     },
     },
-    "extsprintf": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz",
-      "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="
-    },
     "fast-deep-equal": {
     "fast-deep-equal": {
       "version": "3.1.3",
       "version": "3.1.3",
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -9657,11 +9559,6 @@
       "resolved": "https://registry.npmmirror.com/orderedmap/-/orderedmap-2.1.1.tgz",
       "resolved": "https://registry.npmmirror.com/orderedmap/-/orderedmap-2.1.1.tgz",
       "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="
       "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="
     },
     },
-    "os-tmpdir": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="
-    },
     "p-limit": {
     "p-limit": {
       "version": "3.1.0",
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
       "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
@@ -10770,31 +10667,6 @@
         "spdx-expression-parse": "^3.0.0"
         "spdx-expression-parse": "^3.0.0"
       }
       }
     },
     },
-    "vasync": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmmirror.com/vasync/-/vasync-2.2.1.tgz",
-      "integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
-      "requires": {
-        "verror": "1.10.0"
-      }
-    },
-    "verror": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz",
-      "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
-      "requires": {
-        "assert-plus": "^1.0.0",
-        "core-util-is": "1.0.2",
-        "extsprintf": "^1.2.0"
-      },
-      "dependencies": {
-        "core-util-is": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz",
-          "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
-        }
-      }
-    },
     "vite": {
     "vite": {
       "version": "5.3.5",
       "version": "5.3.5",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-5.3.5.tgz",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-5.3.5.tgz",
@@ -10901,25 +10773,6 @@
         "isexe": "^2.0.0"
         "isexe": "^2.0.0"
       }
       }
     },
     },
-    "wmf2png": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmmirror.com/wmf2png/-/wmf2png-1.0.0.tgz",
-      "integrity": "sha512-sPVTVKcDdH+oSv9WEkHB3DFUGUfUwy5HH2liQqN8Elqj6YObYac0bkz/L6FeYCgvuBjZicn23CVBBEbNo65I+A==",
-      "requires": {
-        "tmp": "0.0.33",
-        "vasync": "^2.2.0"
-      },
-      "dependencies": {
-        "tmp": {
-          "version": "0.0.33",
-          "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz",
-          "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
-          "requires": {
-            "os-tmpdir": "~1.0.2"
-          }
-        }
-      }
-    },
     "wrap-ansi": {
     "wrap-ansi": {
       "version": "7.0.0",
       "version": "7.0.0",
       "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
       "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

+ 0 - 1
package.json

@@ -60,7 +60,6 @@
     "vuedraggable": "^4.1.0",
     "vuedraggable": "^4.1.0",
     "wangeditor": "^4.7.15",
     "wangeditor": "^4.7.15",
     "webcodecs-encoder": "^0.3.2",
     "webcodecs-encoder": "^0.3.2",
-    "wmf2png": "^1.0.0",
     "y-websocket": "^3.0.0",
     "y-websocket": "^3.0.0",
     "yjs": "^13.6.27"
     "yjs": "^13.6.27"
   },
   },

+ 48 - 6
src/hooks/useImport.ts

@@ -738,16 +738,16 @@ export default () => {
     options?: { tolerance?: number }
     options?: { tolerance?: number }
   ): Promise<File> => {
   ): Promise<File> => {
     const tolerance = options?.tolerance ?? 15;
     const tolerance = options?.tolerance ?? 15;
-
+  
     // 1. 统一输入为 Blob 和 MIME
     // 1. 统一输入为 Blob 和 MIME
     const { blob, mime } = await getBlobAndMime(data);
     const { blob, mime } = await getBlobAndMime(data);
     const format = getFormat(mime, filename);
     const format = getFormat(mime, filename);
-
+  
     // 2. 浏览器原生支持的格式直接返回
     // 2. 浏览器原生支持的格式直接返回
     if (format === 'browser') {
     if (format === 'browser') {
       return new File([blob], filename, { type: mime });
       return new File([blob], filename, { type: mime });
     }
     }
-
+  
     // 3. 需要转换成 PNG 的格式
     // 3. 需要转换成 PNG 的格式
     let pngBlob: Blob;
     let pngBlob: Blob;
     if (format === 'tiff') {
     if (format === 'tiff') {
@@ -760,12 +760,42 @@ export default () => {
       // format === 'png' 的情况
       // format === 'png' 的情况
       pngBlob = blob;
       pngBlob = blob;
     }
     }
-
-    // 4. 对 PNG 执行白色变透明处理
-    const transparentPngBlob = await makeWhiteTransparentFromPng(pngBlob, tolerance);
+  
+    // --- 新增:检测 PNG 是否已经包含透明背景 ---
+    let alreadyTransparent = false;
+    // 无论原始格式是 PNG 还是转换后得到的 PNG,都进行检测
+    const checkUrl = URL.createObjectURL(pngBlob);
+    try {
+      const img = await new Promise<HTMLImageElement>((resolve, reject) => {
+        const image = new Image();
+        image.onload = () => resolve(image);
+        image.onerror = reject;
+        image.src = checkUrl;
+      });
+      const canvas = document.createElement('canvas');
+      canvas.width = img.width;
+      canvas.height = img.height;
+      const ctx = canvas.getContext('2d')!;
+      ctx.drawImage(img, 0, 0);
+      alreadyTransparent = hasTransparency(img, ctx);
+    } finally {
+      URL.revokeObjectURL(checkUrl);
+    }
+  
+    let transparentPngBlob: Blob;
+    if (alreadyTransparent) {
+      // 图片已有透明背景,直接使用原 PNG Blob
+      console.log('检测到透明背景,跳过白色变透明处理');
+      transparentPngBlob = pngBlob;
+    } else {
+      // 否则执行白色变透明处理
+      transparentPngBlob = await makeWhiteTransparentFromPng(pngBlob, tolerance);
+    }
+  
     const finalFilename = format === 'png' ? filename : filename.replace(/\.[^.]*$/, '') + '.png';
     const finalFilename = format === 'png' ? filename : filename.replace(/\.[^.]*$/, '') + '.png';
     return new File([transparentPngBlob], finalFilename, { type: 'image/png' });
     return new File([transparentPngBlob], finalFilename, { type: 'image/png' });
   };
   };
+  
 
 
   // ================== 辅助函数 ==================
   // ================== 辅助函数 ==================
 
 
@@ -902,6 +932,18 @@ export default () => {
     return convertMetafileToPng(arrayBuffer, WMFJS.Renderer);
     return convertMetafileToPng(arrayBuffer, WMFJS.Renderer);
   }
   }
 
 
+  function hasTransparency(img: HTMLImageElement, ctx: CanvasRenderingContext2D): boolean {
+    const imageData = ctx.getImageData(0, 0, img.width, img.height);
+    const data = imageData.data;
+    // 遍历 Alpha 通道(索引 3)
+    for (let i = 3; i < data.length; i += 4) {
+      if (data[i] < 255) {
+        return true; // 发现任意一个像素不是完全不透明
+      }
+    }
+    return false;
+  }
+
   // 对 PNG 执行白色变透明
   // 对 PNG 执行白色变透明
   async function makeWhiteTransparentFromPng(pngBlob: Blob, tolerance: number): Promise<Blob> {
   async function makeWhiteTransparentFromPng(pngBlob: Blob, tolerance: number): Promise<Blob> {
     const url = URL.createObjectURL(pngBlob);
     const url = URL.createObjectURL(pngBlob);

+ 23 - 31
src/utils/prosemirror/schema/nodes.ts

@@ -110,6 +110,7 @@ const listItem: NodeSpec = {
     marginBottom: { default: '' },
     marginBottom: { default: '' },
     marginLeft: { default: '' },
     marginLeft: { default: '' },
     marginRight: { default: '' },
     marginRight: { default: '' },
+    lineHeight: { default: '' },   // ✅ 新增
   },
   },
   content: 'paragraph block*',
   content: 'paragraph block*',
   group: 'block',
   group: 'block',
@@ -127,6 +128,7 @@ const listItem: NodeSpec = {
         const marginBottom = style.marginBottom || '';
         const marginBottom = style.marginBottom || '';
         const marginLeft = style.marginLeft || '';
         const marginLeft = style.marginLeft || '';
         const marginRight = style.marginRight || '';
         const marginRight = style.marginRight || '';
+        const lineHeight = style.lineHeight || '';   // ✅ 新增
 
 
         let align = textAlign || textAlignLast || '';
         let align = textAlign || textAlignLast || '';
         align = /^(left|right|center|justify)$/.test(align) ? align : '';
         align = /^(left|right|center|justify)$/.test(align) ? align : '';
@@ -138,6 +140,7 @@ const listItem: NodeSpec = {
           marginBottom,
           marginBottom,
           marginLeft,
           marginLeft,
           marginRight,
           marginRight,
+          lineHeight,   // ✅ 新增
         };
         };
       },
       },
     },
     },
@@ -150,6 +153,7 @@ const listItem: NodeSpec = {
       marginBottom,
       marginBottom,
       marginLeft,
       marginLeft,
       marginRight,
       marginRight,
+      lineHeight,   // ✅ 新增
     } = node.attrs;
     } = node.attrs;
 
 
     let style = '';
     let style = '';
@@ -157,8 +161,9 @@ const listItem: NodeSpec = {
     if (align) {
     if (align) {
       style += `text-align: ${align}; text-align-last: ${align};`;
       style += `text-align: ${align}; text-align-last: ${align};`;
     }
     }
+    // 🔧 原写法 text-indent: (100% - ${textIndent}); 为非法 CSS,建议改为:
     if (textIndent) {
     if (textIndent) {
-      style += `text-indent: (100% - ${textIndent});`;
+      style += `text-indent: ${textIndent};`;   // 假设 textIndent 已是合法 CSS 值(如 "2em")
     }
     }
     if (marginTop) {
     if (marginTop) {
       style += `margin-top: ${marginTop};`;
       style += `margin-top: ${marginTop};`;
@@ -172,6 +177,10 @@ const listItem: NodeSpec = {
     if (marginRight) {
     if (marginRight) {
       style += `margin-right: ${marginRight};`;
       style += `margin-right: ${marginRight};`;
     }
     }
+    // ✅ 新增 line-height 输出
+    if (lineHeight) {
+      style += `line-height: ${lineHeight};`;
+    }
 
 
     const attrs: { style?: string } = {};
     const attrs: { style?: string } = {};
     if (style) attrs.style = style;
     if (style) attrs.style = style;
@@ -179,17 +188,16 @@ const listItem: NodeSpec = {
     return ['li', attrs, 0];
     return ['li', attrs, 0];
   },
   },
 };
 };
-
 const paragraph: NodeSpec = {
 const paragraph: NodeSpec = {
   attrs: {
   attrs: {
     align: { default: '' },
     align: { default: '' },
     indent: { default: 0 },
     indent: { default: 0 },
-    textIndent: { default: 0 },
-    // 新增 margin 相关属性,以字符串形式存储原始样式值
+    textIndent: { default: 0 },     // 注意:这里原定义为数字,但 parse 中存的是字符串,需统一
     marginTop: { default: '' },
     marginTop: { default: '' },
     marginBottom: { default: '' },
     marginBottom: { default: '' },
     marginLeft: { default: '' },
     marginLeft: { default: '' },
     marginRight: { default: '' },
     marginRight: { default: '' },
+    lineHeight: { default: '' },     // ✅ 新增
   },
   },
   content: 'inline*',
   content: 'inline*',
   group: 'block',
   group: 'block',
@@ -200,35 +208,16 @@ const paragraph: NodeSpec = {
         const el = dom as HTMLElement;
         const el = dom as HTMLElement;
         const style = el.style;
         const style = el.style;
 
 
-        // 原有 align 解析逻辑
         const textAlign = style.textAlign || '';
         const textAlign = style.textAlign || '';
         let align = el.getAttribute('align') || textAlign || '';
         let align = el.getAttribute('align') || textAlign || '';
         align = /^(left|right|center|justify)$/.test(align) ? align : '';
         align = /^(left|right|center|justify)$/.test(align) ? align : '';
 
 
-        // 原有 textIndent 解析逻辑(转换为数字级别)
-        /*
-        const textIndentStyle = style.textIndent || '';
-        let textIndentLevel = 0;
-        if (textIndentStyle) {
-          if (/em/.test(textIndentStyle)) {
-            textIndentLevel = parseInt(textIndentStyle, 10);
-          } else if (/px/.test(textIndentStyle)) {
-            textIndentLevel = Math.floor(parseInt(textIndentStyle, 10) / 16);
-            if (!textIndentLevel) textIndentLevel = 1;
-          }
-        }
-
-
-        // 原有 indent(数据属性)
-        const indent = +(el.getAttribute('data-indent') || 0);
-        */
-
-        // 新增 margin 解析(直接取原始字符串)
         const marginTop = style.marginTop || '';
         const marginTop = style.marginTop || '';
         const marginBottom = style.marginBottom || '';
         const marginBottom = style.marginBottom || '';
         const marginLeft = style.marginLeft || '';
         const marginLeft = style.marginLeft || '';
         const marginRight = style.marginRight || '';
         const marginRight = style.marginRight || '';
-        const textIndent = style.textIndent || '';
+        const textIndent = style.textIndent || '';   // 此处存为字符串,但与 attrs 定义的默认类型不一致,建议统一改为字符串默认 ''
+        const lineHeight = style.lineHeight || '';    // ✅ 新增
 
 
         return {
         return {
           align,
           align,
@@ -237,6 +226,7 @@ const paragraph: NodeSpec = {
           marginBottom,
           marginBottom,
           marginLeft,
           marginLeft,
           marginRight,
           marginRight,
+          lineHeight,   // ✅ 新增
         };
         };
       },
       },
     },
     },
@@ -257,6 +247,7 @@ const paragraph: NodeSpec = {
       marginBottom,
       marginBottom,
       marginLeft,
       marginLeft,
       marginRight,
       marginRight,
+      lineHeight,   // ✅ 新增
     } = node.attrs;
     } = node.attrs;
 
 
     let style = '';
     let style = '';
@@ -265,21 +256,22 @@ const paragraph: NodeSpec = {
       style += `text-align: ${align};text-align-last: ${align};`;
       style += `text-align: ${align};text-align-last: ${align};`;
     }
     }
 
 
-    // 注意:原有 textIndent 为数字,这里直接输出数字(不带单位)
-    // 如果你希望输出带单位的样式,需根据业务调整(见后文说明)
     if (textIndent) {
     if (textIndent) {
-      style += `text-indent: (100% - ${textIndent});`;
+      // 🔧 修复非法 CSS 语法
+      style += `text-indent: ${textIndent};`;
     }
     }
 
 
-    // 新增 margin 样式输出
     if (marginTop) style += `margin-top: ${marginTop};`;
     if (marginTop) style += `margin-top: ${marginTop};`;
     if (marginBottom) style += `margin-bottom: ${marginBottom};`;
     if (marginBottom) style += `margin-bottom: ${marginBottom};`;
     if (marginLeft) style += `margin-left: ${marginLeft};`;
     if (marginLeft) style += `margin-left: ${marginLeft};`;
     if (marginRight) style += `margin-right: ${marginRight};`;
     if (marginRight) style += `margin-right: ${marginRight};`;
 
 
+    // ✅ 新增 line-height 输出
+    if (lineHeight) {
+      style += `line-height: ${lineHeight};`;
+    }
+
     const attr: Attr = { style };
     const attr: Attr = { style };
-    // 原有的 indent 存储方式(根据需要可保留或取消注释)
-    // if (indent) attr['data-indent'] = indent;
 
 
     return ['p', attr, 0];
     return ['p', attr, 0];
   },
   },

+ 1 - 1
src/views/components/element/ShapeElement/BaseShapeElement.vue

@@ -149,7 +149,7 @@ const text = computed<ShapeText>(() => {
   }
   }
 
 
   &.top {
   &.top {
-    justify-content: space-around;
+    justify-content: flex-start;
   }
   }
   &.middle {
   &.middle {
     justify-content: center;
     justify-content: center;

+ 127 - 105
src/views/components/element/ShapeElement/index.vue

@@ -1,8 +1,8 @@
 <template>
 <template>
-  <div 
+  <div
     class="editable-element-shape"
     class="editable-element-shape"
     :class="{
     :class="{
-      'lock': elementInfo.lock,
+      lock: elementInfo.lock,
       'format-painter': shapeFormatPainter,
       'format-painter': shapeFormatPainter,
     }"
     }"
     :style="{
     :style="{
@@ -16,8 +16,8 @@
       class="rotate-wrapper"
       class="rotate-wrapper"
       :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
       :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
     >
     >
-      <div 
-        class="element-content" 
+      <div
+        class="element-content"
         :style="{
         :style="{
           opacity: elementInfo.opacity,
           opacity: elementInfo.opacity,
           filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
           filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
@@ -26,61 +26,69 @@
           fontFamily: text.defaultFontName,
           fontFamily: text.defaultFontName,
         }"
         }"
         v-contextmenu="contextmenus"
         v-contextmenu="contextmenus"
-        @mousedown="$event => handleSelectElement($event)"
+        @mousedown="($event) => handleSelectElement($event)"
         @mouseup="execFormatPainter()"
         @mouseup="execFormatPainter()"
-        @touchstart="$event => handleSelectElement($event)"
+        @touchstart="($event) => handleSelectElement($event)"
         @dblclick="startEdit()"
         @dblclick="startEdit()"
       >
       >
-        <svg 
-          overflow="visible" 
+        <svg
+          overflow="visible"
           :width="elementInfo.width"
           :width="elementInfo.width"
           :height="elementInfo.height"
           :height="elementInfo.height"
         >
         >
           <defs>
           <defs>
             <PatternDefs
             <PatternDefs
               v-if="elementInfo.pattern"
               v-if="elementInfo.pattern"
-              :id="`editable-pattern-${elementInfo.id}`" 
+              :id="`editable-pattern-${elementInfo.id}`"
               :src="elementInfo.pattern"
               :src="elementInfo.pattern"
             />
             />
             <GradientDefs
             <GradientDefs
               v-else-if="elementInfo.gradient"
               v-else-if="elementInfo.gradient"
-              :id="`editable-gradient-${elementInfo.id}`" 
+              :id="`editable-gradient-${elementInfo.id}`"
               :type="elementInfo.gradient.type"
               :type="elementInfo.gradient.type"
               :colors="elementInfo.gradient.colors"
               :colors="elementInfo.gradient.colors"
               :rotate="elementInfo.gradient.rotate"
               :rotate="elementInfo.gradient.rotate"
             />
             />
           </defs>
           </defs>
-          <g 
-            :transform="`scale(${elementInfo.width / elementInfo.viewBox[0]}, ${elementInfo.height / elementInfo.viewBox[1]}) translate(0,0) matrix(1,0,0,1,0,0)`"
+          <g
+            :transform="`scale(${elementInfo.width / elementInfo.viewBox[0]}, ${
+              elementInfo.height / elementInfo.viewBox[1]
+            }) translate(0,0) matrix(1,0,0,1,0,0)`"
           >
           >
-            <path 
+            <path
               class="shape-path"
               class="shape-path"
-              vector-effect="non-scaling-stroke" 
-              stroke-linecap="butt" 
+              vector-effect="non-scaling-stroke"
+              stroke-linecap="butt"
+              stroke-linejoin="round"
               stroke-miterlimit="8"
               stroke-miterlimit="8"
-              :d="elementInfo.path" 
+              transform="translate(1, 1)"
+              stroke-opacity="1"
+              :d="elementInfo.path"
               :fill="fill"
               :fill="fill"
               :stroke="outlineColor"
               :stroke="outlineColor"
-              :stroke-width="outlineWidth" 
-              :stroke-dasharray="strokeDashArray" 
+              :stroke-width="outlineWidth"
+              :stroke-dasharray="strokeDashArray"
             ></path>
             ></path>
           </g>
           </g>
         </svg>
         </svg>
 
 
-        <div class="shape-text" :style="text.style" :class="[text.align, { 'editable': editable || text.content }]">
-            <ProsemirrorEditor
-              ref="prosemirrorEditorRef"
-              v-if="editable || text.content"
-              :elementId="elementInfo.id"
-              :defaultColor="text.defaultColor"
-              :defaultFontName="text.defaultFontName"
-              :editable="!elementInfo.lock"
-              :value="text.content"
-              @update="({ value, ignore }) => updateText(value, ignore)"
-              @blur="checkEmptyText()"
-              @mousedown="$event => handleSelectElement($event, false)"
-            />
-
+        <div
+          class="shape-text"
+          :style="text.style"
+          :class="[text.align, { editable: editable || text.content }]"
+        >
+          <ProsemirrorEditor
+            ref="prosemirrorEditorRef"
+            v-if="editable || text.content"
+            :elementId="elementInfo.id"
+            :defaultColor="text.defaultColor"
+            :defaultFontName="text.defaultFontName"
+            :editable="!elementInfo.lock"
+            :value="text.content"
+            @update="({ value, ignore }) => updateText(value, ignore)"
+            @blur="checkEmptyText()"
+            @mousedown="($event) => handleSelectElement($event, false)"
+          />
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
@@ -88,112 +96,124 @@
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { computed, nextTick, ref, watch, useTemplateRef } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useMainStore, useSlidesStore } from '@/store'
-import type { PPTShapeElement, ShapeText } from '@/types/slides'
-import type { ContextmenuItem } from '@/components/Contextmenu/types'
-import useElementOutline from '@/views/components/element/hooks/useElementOutline'
-import useElementShadow from '@/views/components/element/hooks/useElementShadow'
-import useElementFlip from '@/views/components/element/hooks/useElementFlip'
-import useElementFill from '@/views/components/element/hooks/useElementFill'
-import useHistorySnapshot from '@/hooks/useHistorySnapshot'
+import { computed, nextTick, ref, watch, useTemplateRef } from "vue";
+import { storeToRefs } from "pinia";
+import { useMainStore, useSlidesStore } from "@/store";
+import type { PPTShapeElement, ShapeText } from "@/types/slides";
+import type { ContextmenuItem } from "@/components/Contextmenu/types";
+import useElementOutline from "@/views/components/element/hooks/useElementOutline";
+import useElementShadow from "@/views/components/element/hooks/useElementShadow";
+import useElementFlip from "@/views/components/element/hooks/useElementFlip";
+import useElementFill from "@/views/components/element/hooks/useElementFill";
+import useHistorySnapshot from "@/hooks/useHistorySnapshot";
 
 
-import GradientDefs from './GradientDefs.vue'
-import PatternDefs from './PatternDefs.vue'
-import ProsemirrorEditor from '@/views/components/element/ProsemirrorEditor.vue'
+import GradientDefs from "./GradientDefs.vue";
+import PatternDefs from "./PatternDefs.vue";
+import ProsemirrorEditor from "@/views/components/element/ProsemirrorEditor.vue";
 
 
 const props = defineProps<{
 const props = defineProps<{
-  elementInfo: PPTShapeElement
-  selectElement: (e: MouseEvent | TouchEvent, element: PPTShapeElement, canMove?: boolean) => void
-  contextmenus: () => ContextmenuItem[] | null
-}>()
+  elementInfo: PPTShapeElement;
+  selectElement: (
+    e: MouseEvent | TouchEvent,
+    element: PPTShapeElement,
+    canMove?: boolean
+  ) => void;
+  contextmenus: () => ContextmenuItem[] | null;
+}>();
 
 
-const mainStore = useMainStore()
-const slidesStore = useSlidesStore()
-const { theme } = storeToRefs(slidesStore)
-const { handleElementId, shapeFormatPainter } = storeToRefs(mainStore)
+const mainStore = useMainStore();
+const slidesStore = useSlidesStore();
+const { theme } = storeToRefs(slidesStore);
+const { handleElementId, shapeFormatPainter } = storeToRefs(mainStore);
 
 
-const { addHistorySnapshot } = useHistorySnapshot()
+const { addHistorySnapshot } = useHistorySnapshot();
 
 
 const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
 const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
-  if (props.elementInfo.lock) return
-  e.stopPropagation()
+  if (props.elementInfo.lock) return;
+  e.stopPropagation();
 
 
-  props.selectElement(e, props.elementInfo, canMove)
-}
+  props.selectElement(e, props.elementInfo, canMove);
+};
 
 
 const execFormatPainter = () => {
 const execFormatPainter = () => {
-  if (!shapeFormatPainter.value) return
-  const { keep, ...newProps } = shapeFormatPainter.value
+  if (!shapeFormatPainter.value) return;
+  const { keep, ...newProps } = shapeFormatPainter.value;
 
 
   slidesStore.updateElement({
   slidesStore.updateElement({
-    id: props.elementInfo.id, 
+    id: props.elementInfo.id,
     props: newProps,
     props: newProps,
-  })
-  
-  addHistorySnapshot()
-  if (!keep) mainStore.setShapeFormatPainter(null)
-}
+  });
+
+  addHistorySnapshot();
+  if (!keep) mainStore.setShapeFormatPainter(null);
+};
 
 
-const element = computed(() => props.elementInfo)
-const { fill } = useElementFill(element, 'editable')
+const element = computed(() => props.elementInfo);
+const { fill } = useElementFill(element, "editable");
 
 
-const outline = computed(() => props.elementInfo.outline)
-const { outlineWidth, outlineColor, strokeDashArray } = useElementOutline(outline)
+const outline = computed(() => props.elementInfo.outline);
+const { outlineWidth, outlineColor, strokeDashArray } =
+  useElementOutline(outline);
 
 
-const shadow = computed(() => props.elementInfo.shadow)
-const { shadowStyle } = useElementShadow(shadow)
+const shadow = computed(() => props.elementInfo.shadow);
+const { shadowStyle } = useElementShadow(shadow);
 
 
-const flipH = computed(() => props.elementInfo.flipH)
-const flipV = computed(() => props.elementInfo.flipV)
-const { flipStyle } = useElementFlip(flipH, flipV)
+const flipH = computed(() => props.elementInfo.flipH);
+const flipV = computed(() => props.elementInfo.flipV);
+const { flipStyle } = useElementFlip(flipH, flipV);
 
 
-const editable = ref(false)
+const editable = ref(false);
 
 
 watch(handleElementId, () => {
 watch(handleElementId, () => {
   if (handleElementId.value !== props.elementInfo.id) {
   if (handleElementId.value !== props.elementInfo.id) {
-    if (editable.value) editable.value = false
+    if (editable.value) editable.value = false;
   }
   }
-})
+});
 
 
 const text = computed<ShapeText>(() => {
 const text = computed<ShapeText>(() => {
   const defaultText: ShapeText = {
   const defaultText: ShapeText = {
-    content: '',
-    align: 'middle',
+    content: "",
+    align: "middle",
     defaultFontName: theme.value.fontName,
     defaultFontName: theme.value.fontName,
     defaultColor: theme.value.fontColor,
     defaultColor: theme.value.fontColor,
-  }
-  if (!props.elementInfo.text) return defaultText
+  };
+  if (!props.elementInfo.text) return defaultText;
 
 
-  return props.elementInfo.text
-})
+  return props.elementInfo.text;
+});
 
 
 const updateText = (content: string, ignore = false) => {
 const updateText = (content: string, ignore = false) => {
-  const _text = { ...text.value, content }
+  const _text = { ...text.value, content };
   slidesStore.updateElement({
   slidesStore.updateElement({
-    id: props.elementInfo.id, 
+    id: props.elementInfo.id,
     props: { text: _text },
     props: { text: _text },
-  })
-  
-  if (!ignore) addHistorySnapshot()
-}
+  });
+
+  if (!ignore) addHistorySnapshot();
+};
 
 
 const checkEmptyText = () => {
 const checkEmptyText = () => {
-  if (!props.elementInfo.text) return
+  if (!props.elementInfo.text) return;
 
 
-  const pureText = props.elementInfo.text.content.replace(/<[^>]+>/g, '')
+  const pureText = props.elementInfo.text.content.replace(/<[^>]+>/g, "");
   if (!pureText) {
   if (!pureText) {
-    slidesStore.removeElementProps({ id: props.elementInfo.id, propName: 'text' })
-    addHistorySnapshot()
+    slidesStore.removeElementProps({
+      id: props.elementInfo.id,
+      propName: "text",
+    });
+    addHistorySnapshot();
   }
   }
-}
+};
 
 
-const prosemirrorEditorRef = useTemplateRef<InstanceType<typeof ProsemirrorEditor>>('prosemirrorEditorRef')
+const prosemirrorEditorRef = useTemplateRef<
+  InstanceType<typeof ProsemirrorEditor>
+>("prosemirrorEditorRef");
 const startEdit = () => {
 const startEdit = () => {
-  editable.value = true
-  nextTick(() => prosemirrorEditorRef.value && prosemirrorEditorRef.value.focus())
-}
+  editable.value = true;
+  nextTick(
+    () => prosemirrorEditorRef.value && prosemirrorEditorRef.value.focus()
+  );
+};
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
@@ -206,7 +226,9 @@ const startEdit = () => {
     cursor: default;
     cursor: default;
   }
   }
   &.format-painter .element-content {
   &.format-painter .element-content {
-    cursor: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzQiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNzUgMTMuNzY0VjEuNDIxYS4zLjMgMCAwMS40NDgtLjI2bDEwLjkxIDYuMTk3YS4zLjMgMCAwMS0uMTE2LjU1OWwtNC4xOTYuNDQyIDIuNTgyIDQuNDcyYS4zLjMgMCAwMS0uMTEuNDFsLTMuMTg0IDEuODM4YS4zLjMgMCAwMS0uNDEtLjExbC0yLjU4MS00LjQ3Mi0yLjgxIDMuNDU2YS4zLjMgMCAwMS0uNTMzLS4xODl6IiBmaWxsPSIjZmZmIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNMjYgMTQuNWw0LjUtNC41LTYtNmMtMiAyLTMgMi01LjUgMi41LjQgMy4yIDQuODMzIDYuNjY3IDcgOHptNC41ODgtNC40OTRhLjMuMyAwIDAwLjQyNCAwbC42OC0uNjhhMS41IDEuNSAwIDAwMC0yLjEyMUwzMC4zNCA1Ljg1MmwyLjAyNi0xLjU4MmExLjYyOSAxLjYyOSAwIDEwLTIuMjgtMi4yOTZsLTEuNjAzIDIuMDIxLTEuMzU3LTEuMzU2YTEuNSAxLjUgMCAwMC0yLjEyIDBsLS42ODEuNjhhLjMuMyAwIDAwMCAuNDI0bDYuMjYzIDYuMjYzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0yNC41NDMgMy45NjFzLTEuMDMgMS4yMDItMi40OTQgMS44OTFjLTEuMDA2LjQ3NC0yLjE4MS41ODUtMi43MzQuNjI3LS4yLjAxNC0uMzQ0LjIwOS0uMjc3LjM5OC4yOTMuODIgMS4xMTIgMi44MDEgMi42NTggNC4zNDcgMi4xMjYgMi4xMjYgMy42NTkgMi45NjggNC4xNDIgMy4yMDIuMS4wNDguMjE1LjAzLjI5OS0uMDQxLjM4NS0uMzI2IDEuNS0xLjI3NyAyLjIxLTEuOTg2Ljg5MS0uODkgMi4xODYtMi40NDggMi4xODYtMi40NDhtLjQ4LjA1NWEuMy4zIDAgMDEtLjQyNSAwbC02LjI2My02LjI2M2EuMy4zIDAgMDEwLS40MjRsLjY4LS42OGExLjUgMS41IDAgMDEyLjEyMiAwbDEuMzU2IDEuMzU2IDEuNjA0LTIuMDIxYTEuNjI5IDEuNjI5IDAgMTEyLjI3OSAyLjI5NkwzMC4zNCA1Ljg1MmwxLjM1MyAxLjM1M2ExLjUgMS41IDAgMDEwIDIuMTIxbC0uNjguNjh6IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4=) 2 5, default !important;
+    cursor: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzQiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNzUgMTMuNzY0VjEuNDIxYS4zLjMgMCAwMS40NDgtLjI2bDEwLjkxIDYuMTk3YS4zLjMgMCAwMS0uMTE2LjU1OWwtNC4xOTYuNDQyIDIuNTgyIDQuNDcyYS4zLjMgMCAwMS0uMTEuNDFsLTMuMTg0IDEuODM4YS4zLjMgMCAwMS0uNDEtLjExbC0yLjU4MS00LjQ3Mi0yLjgxIDMuNDU2YS4zLjMgMCAwMS0uNTMzLS4xODl6IiBmaWxsPSIjZmZmIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNMjYgMTQuNWw0LjUtNC41LTYtNmMtMiAyLTMgMi01LjUgMi41LjQgMy4yIDQuODMzIDYuNjY3IDcgOHptNC41ODgtNC40OTRhLjMuMyAwIDAwLjQyNCAwbC42OC0uNjhhMS41IDEuNSAwIDAwMC0yLjEyMUwzMC4zNCA1Ljg1MmwyLjAyNi0xLjU4MmExLjYyOSAxLjYyOSAwIDEwLTIuMjgtMi4yOTZsLTEuNjAzIDIuMDIxLTEuMzU3LTEuMzU2YTEuNSAxLjUgMCAwMC0yLjEyIDBsLS42ODEuNjhhLjMuMyAwIDAwMCAuNDI0bDYuMjYzIDYuMjYzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0yNC41NDMgMy45NjFzLTEuMDMgMS4yMDItMi40OTQgMS44OTFjLTEuMDA2LjQ3NC0yLjE4MS41ODUtMi43MzQuNjI3LS4yLjAxNC0uMzQ0LjIwOS0uMjc3LjM5OC4yOTMuODIgMS4xMTIgMi44MDEgMi42NTggNC4zNDcgMi4xMjYgMi4xMjYgMy42NTkgMi45NjggNC4xNDIgMy4yMDIuMS4wNDguMjE1LjAzLjI5OS0uMDQxLjM4NS0uMzI2IDEuNS0xLjI3NyAyLjIxLTEuOTg2Ljg5MS0uODkgMi4xODYtMi40NDggMi4xODYtMi40NDhtLjQ4LjA1NWEuMy4zIDAgMDEtLjQyNSAwbC02LjI2My02LjI2M2EuMy4zIDAgMDEwLS40MjRsLjY4LS42OGExLjUgMS41IDAgMDEyLjEyMiAwbDEuMzU2IDEuMzU2IDEuNjA0LTIuMDIxYTEuNjI5IDEuNjI5IDAgMTEyLjI3OSAyLjI5NkwzMC4zNCA1Ljg1MmwxLjM1MyAxLjM1M2ExLjUgMS41IDAgMDEwIDIuMTIxbC0uNjguNjh6IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4=)
+        2 5,
+      default !important;
   }
   }
 }
 }
 .rotate-wrapper {
 .rotate-wrapper {
@@ -230,8 +252,8 @@ const startEdit = () => {
   }
   }
 }
 }
 .shape-text {
 .shape-text {
-  width:100%;
-  height:100%;
+  width: 100%;
+  height: 100%;
   position: absolute;
   position: absolute;
   top: 0;
   top: 0;
   bottom: 0;
   bottom: 0;
@@ -248,14 +270,14 @@ const startEdit = () => {
   }
   }
 
 
   &.top {
   &.top {
-    justify-content: space-around;
+    justify-content: flex-start;
   }
   }
   &.middle {
   &.middle {
     justify-content: center;
     justify-content: center;
     left: 50%;
     left: 50%;
     top: 50%;
     top: 50%;
-    -webkit-transform: translate(-50%,-50%);
-    transform: translate(-50%,-50%);
+    -webkit-transform: translate(-50%, -50%);
+    transform: translate(-50%, -50%);
   }
   }
   &.bottom {
   &.bottom {
     justify-content: flex-end;
     justify-content: flex-end;

+ 1 - 1
src/views/components/element/TextElement/BaseTextElement.vue

@@ -107,7 +107,7 @@ const { shadowStyle } = useElementShadow(shadow)
   }
   }
 
 
   &.top {
   &.top {
-    justify-content: space-around;
+    justify-content: flex-start;
   }
   }
   &.middle {
   &.middle {
     justify-content: center;
     justify-content: center;

+ 1 - 1
src/views/components/element/TextElement/index.vue

@@ -239,7 +239,7 @@ watch(isHandleElement, () => {
   }
   }
 
 
   &.top {
   &.top {
-    justify-content: space-around;
+    justify-content: flex-start;
   }
   }
   &.middle {
   &.middle {
     justify-content: center;
     justify-content: center;