Kaynağa Gözat

feat(PictureEditor): 添加文字对象字体自动加载功能

- 实现编辑层文字对象的字体自动加载机制
- 添加Canvas JSON数据中字体预加载功能
- 集成font.json配置文件支持自定义字体
- 实现模板加载时文字对象字体预加载
- 添加字体加载状态检查和错误处理机制
- 优化文字对象字体属性设置和渲染流程
panqiuyao 1 gün önce
ebeveyn
işleme
7bb5154c13

+ 11 - 0
frontend/src/views/components/PictureEditor/mixin/edit/index.js

@@ -84,6 +84,17 @@ export default  {
   },
   watch: {
     editLayer(){
+      // 自动加载文字对象的字体
+      if (this.editLayer && (this.editLayer.type === 'textbox' || this.editLayer.type === 'text')) {
+        const fontFamily = this.editLayer.fontFamily
+        if (fontFamily && fontFamily !== 'Arial') {
+          // 如果是自定义字体,自动加载
+          this.setFontFamily(fontFamily, this.editLayer).catch(err => {
+            console.warn('自动加载字体失败:', err)
+          })
+        }
+      }
+
       this.fontFamilyStyle = this.editLayer.fontFamily
       this.opacityValue = (this.editLayer && typeof this.editLayer.opacity === 'number')
         ? this.editLayer.opacity

+ 150 - 7
frontend/src/views/components/marketingEdit/index.vue

@@ -3,6 +3,7 @@
 <script>
 import fabric from '../PictureEditor/js/fabric-adapter'
 import tpl from './tpl/index'
+import fontConfig from '../PictureEditor/mixin/edit/module/font.json'
 import viewMixins from '@/views/components/PictureEditor/mixin/view/index'
 import actionsMixins from '@/views/components/PictureEditor/mixin/actions/index'
 import layerMixins from '@/views/components/PictureEditor/mixin/layer/index'
@@ -261,19 +262,161 @@ export default {
         const jsonData = typeof this.this_canvas.canvas_json === 'string'
           ? this.this_canvas.canvas_json
           : JSON.stringify(this.this_canvas.canvas_json)
-        this.fcanvas.loadFromJSON(jsonData, () => {
-          // 如果是加载模板,调用专门的初始化函数
-          if(this.hasLoadedTpl){
-            this.setTpl()
-          }
-          this.fcanvas.renderAll()
-          hydrateCanvas()
+
+        // 先预加载字体,再加载模板
+        this.preloadFontsForCanvasJson(jsonData).then(() => {
+          this.fcanvas.loadFromJSON(jsonData, () => {
+            // 字体加载完成后,确保文字对象的字体属性正确设置
+            this.ensureTextObjectsFonts()
+
+            // 如果是加载模板,调用专门的初始化函数
+            if(this.hasLoadedTpl){
+              this.setTpl()
+            }
+
+            this.fcanvas.renderAll()
+            hydrateCanvas()
+          })
+        }).catch((error) => {
+          console.warn('字体预加载失败,继续加载模板:', error)
+          // 即使字体加载失败,也要继续加载模板
+          this.fcanvas.loadFromJSON(jsonData, () => {
+            if(this.hasLoadedTpl){
+              this.setTpl()
+            }
+            this.fcanvas.renderAll()
+            hydrateCanvas()
+          })
         })
       }else{
         hydrateCanvas()
       }
 
     },
+    // 预加载 canvas JSON 中的字体(在loadFromJSON之前调用)
+    async preloadFontsForCanvasJson(jsonData) {
+      try {
+        const json = typeof jsonData === 'string' ? JSON.parse(jsonData) : jsonData
+        const textObjects = (json.objects || []).filter(obj =>
+          (obj.type === 'textbox' || obj.type === 'text') && obj.fontFamily
+        )
+
+        const fontPromises = []
+        const fontSet = new Set()
+
+        for (const obj of textObjects) {
+          const fontFamily = obj.fontFamily
+          if (fontFamily && fontFamily !== 'Arial' && !fontSet.has(fontFamily)) {
+            fontSet.add(fontFamily)
+            // 检查字体是否在fontConfig中
+            const fontInfo = fontConfig.find(f => f.fontFamily === fontFamily || f.name === fontFamily)
+            if (fontInfo && fontInfo.src) {
+              console.log('预加载字体:', fontFamily, fontInfo.src)
+              fontPromises.push(this.loadFontForObject(fontFamily, fontInfo.src))
+            }
+          }
+        }
+
+        // 并发加载所有字体
+        if (fontPromises.length > 0) {
+          console.log(`开始预加载 ${fontPromises.length} 个字体...`)
+          await Promise.all(fontPromises)
+          console.log('Canvas JSON 字体预加载完成')
+        }
+      } catch (error) {
+        console.warn('预加载Canvas JSON字体失败:', error)
+        throw error
+      }
+    },
+
+    // 预加载模板中所有文字对象的字体(在loadFromJSON之后调用,用于处理动态添加的对象)
+    async preloadFontsForTemplate() {
+      if (!this.fcanvas) return
+
+      const textObjects = this.fcanvas.getObjects().filter(obj =>
+        (obj.type === 'textbox' || obj.type === 'text') && obj.fontFamily
+      )
+
+      const fontPromises = []
+      const fontSet = new Set()
+
+      for (const obj of textObjects) {
+        const fontFamily = obj.fontFamily
+        if (fontFamily && fontFamily !== 'Arial' && !fontSet.has(fontFamily)) {
+          fontSet.add(fontFamily)
+          // 检查字体是否在fontConfig中
+          const fontInfo = fontConfig.find(f => f.fontFamily === fontFamily || f.name === fontFamily)
+          if (fontInfo && fontInfo.src) {
+            fontPromises.push(this.loadFontForObject(fontFamily, fontInfo.src))
+          }
+        }
+      }
+
+      // 并发加载所有字体
+      try {
+        await Promise.all(fontPromises)
+        console.log('模板字体预加载完成')
+      } catch (error) {
+        console.warn('模板字体预加载失败:', error)
+      }
+    },
+    // 为单个对象加载字体
+    async loadFontForObject(fontFamily, fontUrl) {
+      return new Promise((resolve, reject) => {
+        if (this.checkFont(fontFamily)) {
+          return resolve(true)
+        }
+
+        const fontFace = new FontFace(fontFamily, `url(${fontUrl})`)
+        fontFace.load().then(loadedFace => {
+          document.fonts.add(loadedFace)
+          console.log('字体加载成功:', fontFamily)
+          resolve(true)
+        }).catch(error => {
+          console.warn('字体加载失败:', fontFamily, error)
+          reject(error)
+        })
+      })
+    },
+    // 检查字体是否已加载
+    checkFont(fontName) {
+      const values = document.fonts.values()
+      let item = values.next()
+      while (!item.done) {
+        const fontFace = item.value
+        if (fontFace.family === fontName) {
+          return true
+        }
+        item = values.next()
+      }
+      return false
+    },
+    // 确保文字对象的字体属性正确设置
+    ensureTextObjectsFonts() {
+      if (!this.fcanvas) return
+
+      const textObjects = this.fcanvas.getObjects().filter(obj =>
+        (obj.type === 'textbox' || obj.type === 'text') && obj.fontFamily
+      )
+
+      textObjects.forEach(obj => {
+        const fontFamily = obj.fontFamily
+        if (fontFamily && fontFamily !== 'Arial') {
+          // 重新设置字体,确保字体生效
+          obj.set({
+            fontFamily: fontFamily,
+            dirty: true // 标记为需要重新渲染
+          })
+        }
+
+        // 确保其他字体属性也正确设置
+        if (!obj.fontSize || obj.fontSize <= 0) obj.fontSize = 16
+        if (!obj.fill) obj.fill = '#000000'
+        if (obj.fontWeight === undefined) obj.fontWeight = 'normal'
+        if (obj.fontStyle === undefined) obj.fontStyle = 'normal'
+        if (obj.textAlign === undefined) obj.textAlign = 'left'
+      })
+    },
     addCanvas(){
 
       this.canvasForm.type = 'add'