Просмотр исходного кода

feat(marketingEdit): 添加字体缓存功能支持本地字体管理

- 集成IPC机制实现Electron环境下的字体下载和缓存功能
- 实现字体加载优化,优先从本地缓存加载字体文件
- 添加字体管理相关API接口,支持字体下载到本地缓存
- 在非Electron环境下回退到网络字体加载机制
- 添加字体缓存路径管理和字体文件哈希生成机制
- 实现字体加载失败时的错误处理和警告提示机制
panqiuyao 6 дней назад
Родитель
Сommit
b369211071

+ 122 - 0
electron/controller/utils.js

@@ -9,6 +9,7 @@ const path = require('path');
 const CoreWindow = require('ee-core/electron/window');
 const { BrowserWindow, Menu,app } = require('electron');
 const { spawn } = require('child_process');
+const Log = require('ee-core/log');
 
 const { readConfigFile } = require('../utils/config');
 const configDeault = readConfigFile();
@@ -388,6 +389,127 @@ class UtilsController extends Controller {
       pyPath:pyPath,
     }
   }
+  // 字体管理相关方法
+  async downloadFont(args, event) {
+    try {
+      console.log('🔤 IPC downloadFont called with args:', args, 'event:', event)
+      const { url, fontName } = args;
+      console.log('🔤 IPC downloadFont parsed:', { url, fontName })
+      Log.info('downloadFontdownloadFontdownloadFontdownloadFont:', url, fontName)
+      const https = require('https');
+      const http = require('http');
+      const fs = require('fs');
+      const path = require('path');
+      const crypto = require('crypto');
+
+      // 获取字体缓存目录
+      const userDataPath = app.getPath('userData');
+      const fontCacheDir = path.join(userDataPath, 'fonts');
+
+      // 确保字体缓存目录存在
+      if (!fs.existsSync(fontCacheDir)) {
+        fs.mkdirSync(fontCacheDir, { recursive: true });
+      }
+
+      // 生成字体文件名(使用URL的哈希值避免文件名冲突)
+      const urlHash = crypto.createHash('md5').update(url).digest('hex').substring(0, 8);
+      const fontFileName = `${fontName.replace(/[^a-zA-Z0-9]/g, '_')}_${urlHash}.ttf`;
+      const localFontPath = path.join(fontCacheDir, fontFileName);
+
+      // 检查本地是否已有字体文件
+      if (fs.existsSync(localFontPath)) {
+        console.log('字体已存在:', localFontPath);
+        return { success: true, path: localFontPath };
+      }
+
+      console.log('开始下载字体:', fontName, '到:', localFontPath);
+
+      // 下载字体文件
+      const protocol = url.startsWith('https:') ? https : http;
+
+      return new Promise((resolve, reject) => {
+        const request = protocol.get(url, (response) => {
+          if (response.statusCode !== 200) {
+            reject(new Error(`Failed to download font: ${response.statusCode}`));
+            return;
+          }
+
+          const fileStream = fs.createWriteStream(localFontPath);
+          response.pipe(fileStream);
+
+          fileStream.on('finish', () => {
+            fileStream.close();
+            console.log('字体下载完成:', localFontPath);
+            resolve({ success: true, path: localFontPath });
+          });
+
+          fileStream.on('error', (error) => {
+            fs.unlinkSync(localFontPath); // 删除失败的文件
+            reject(error);
+          });
+        });
+
+        request.on('error', (error) => {
+          reject(error);
+        });
+
+        // 设置超时
+        request.setTimeout(30000, () => {
+          request.abort();
+          reject(new Error('Font download timeout'));
+        });
+      });
+    } catch (error) {
+      Log.info('downloadFont Error:', error);
+      return { success: false, error: error.message };
+    }
+  }
+
+  async loadFontFromCache(args, event) {
+    try {
+      const { fontName, fontUrl } = args;
+      const fs = require('fs');
+      const path = require('path');
+      const crypto = require('crypto');
+
+      // 获取字体缓存目录
+      const userDataPath = app.getPath('userData');
+      const fontCacheDir = path.join(userDataPath, 'fonts');
+
+      // 生成字体文件名
+      const urlHash = crypto.createHash('md5').update(fontUrl).digest('hex').substring(0, 8);
+      const fontFileName = `${fontName.replace(/[^a-zA-Z0-9]/g, '_')}_${urlHash}.ttf`;
+      const localFontPath = path.join(fontCacheDir, fontFileName);
+
+      // 检查本地是否已有字体文件
+      if (fs.existsSync(localFontPath)) {
+        // 读取字体文件内容并转换为base64
+        const fontData = fs.readFileSync(localFontPath);
+        const base64Data = fontData.toString('base64');
+        const dataUrl = `data:font/ttf;base64,${base64Data}`;
+
+        return { success: true, dataUrl, path: localFontPath };
+      }
+
+      return { success: false, message: 'Font not found in cache' };
+    } catch (error) {
+      console.error('从缓存加载字体失败:', error);
+      return { success: false, error: error.message };
+    }
+  }
+
+  getFontCachePath(args, event) {
+    const path = require('path');
+    const userDataPath = app.getPath('userData');
+    return path.join(userDataPath, 'fonts');
+  }
+
+  // 测试方法
+  async testIPC(args, event) {
+    console.log('🧪 IPC test method called:', args)
+    return { success: true, message: 'IPC is working', args }
+  }
+
   async readFileImageForPath(filePath,maxWidth=1500){
 
     const getMimeType = (fileName)=>{

+ 5 - 1
frontend/src/utils/ipc.ts

@@ -23,7 +23,11 @@ const icpList = {
         openFile:"controller.utils.openFile",
         runExternalTool:"controller.utils.runExternalTool",
         saveGeneratedImages:"controller.utils.saveGeneratedImages",
-        closeAllWindows: 'controller.utils.closeAllWindows'
+        closeAllWindows: 'controller.utils.closeAllWindows',
+        downloadFont: 'controller.utils.downloadFont',
+        loadFontFromCache: 'controller.utils.loadFontFromCache',
+        getFontCachePath: 'controller.utils.getFontCachePath',
+        testIPC: 'controller.utils.testIPC'
     },
     setting:{
         getDeviceConfigDetail: 'controller.setting.getDeviceConfigDetail',

+ 95 - 8
frontend/src/views/components/marketingEdit/generateImagesRender.js

@@ -1,6 +1,8 @@
 import fabric from '../PictureEditor/js/fabric-adapter'
 import { buildRenderPlans, normalizeGoods } from './generateImagesPlan'
 import fontConfig from '../PictureEditor/mixin/edit/module/font.json'
+import useClientStore from '@/stores/modules/client'
+import icpList from '@/utils/ipc'
 
 /**
  * 根据渲染计划和拍平后的货号数据,真正生成图片(dataURL),按 canvasIndex 返回。
@@ -502,21 +504,51 @@ export async function renderImagesByPlans(plans, canvasList, skus) {
         const textPlaceholders = objs.filter((o) => o && o['data-key'] && (o['data-type'] === 'text' || o['type'] === "textbox"))
         console.log('=====textPlaceholders========', textPlaceholders)
 
-        // 字体加载工具函数
-        const loadFont = (_fontName, _fontUrl) => {
-          return new Promise((resolve, reject) => {
+        // 字体加载工具函数(支持本地缓存)
+        const loadFont = async (_fontName, _fontUrl) => {
+          return new Promise(async (resolve, reject) => {
             if (checkFont(_fontName)) {
               console.log('已有字体:', _fontName)
               return resolve(true)
             }
 
-            let prefont = new FontFace(
-              _fontName,
-              'url(' + _fontUrl + ')'
-            );
+            // 检查是否在Electron环境中
+            const win = window
+            const nodeRequire = win.require
+            if (!nodeRequire) {
+              // 非Electron环境,直接网络加载
+              let prefont = new FontFace(_fontName, 'url(' + _fontUrl + ')')
+              prefont.load().then(function (loaded_face) {
+                document.fonts.add(loaded_face)
+                console.log('字体加载成功', loaded_face, document.fonts)
+                return resolve(true)
+              }).catch(function (error) {
+                console.log('字体加载失败', error)
+                return reject(error)
+              })
+              return
+            }
+
+            try {
+              // 使用IPC从缓存加载字体
+              const cacheResult = await loadFontFromCacheIPC(_fontName, _fontUrl)
+              if (cacheResult) {
+                console.log('从本地缓存加载字体成功:', _fontName)
+                return resolve(true)
+              }
+            } catch (cacheError) {
+              console.warn('从缓存加载字体失败,使用网络加载:', cacheError)
+            }
 
+            // 使用IPC下载字体到缓存(异步)
+            downloadFontToCacheIPC(_fontName, _fontUrl).catch(err => {
+              console.warn('异步缓存字体失败:', err)
+            })
+
+            // 回退到网络加载
+            let prefont = new FontFace(_fontName, 'url(' + _fontUrl + ')')
             prefont.load().then(function (loaded_face) {
-              document.fonts.add(loaded_face);
+              document.fonts.add(loaded_face)
               console.log('字体加载成功', loaded_face, document.fonts)
               return resolve(true)
             }).catch(function (error) {
@@ -526,6 +558,61 @@ export async function renderImagesByPlans(plans, canvasList, skus) {
           })
         }
 
+        // 使用IPC从本地缓存加载字体
+        const loadFontFromCacheIPC = async (fontFamily, fontUrl) => {
+          try {
+            const clientStore = useClientStore()
+            if (!clientStore?.isClient) {
+              return false
+            }
+
+            const result = await clientStore.ipc.invoke(icpList.utils.loadFontFromCache, {
+              fontName: fontFamily,
+              fontUrl: fontUrl
+            })
+
+            if (result.success && result.dataUrl) {
+              const fontFace = new FontFace(fontFamily, `url(${result.dataUrl})`)
+              await fontFace.load()
+              document.fonts.add(fontFace)
+              return true
+            }
+            return false
+          } catch (error) {
+            console.warn('从缓存加载字体失败:', error)
+            return false
+          }
+        }
+
+        // 使用IPC下载字体到本地缓存
+        const downloadFontToCacheIPC = async (fontFamily, fontUrl) => {
+          try {
+            console.log('🔧 generateImagesRender IPC call:', { fontFamily, fontUrl })
+            const clientStore = useClientStore()
+            if (!clientStore?.isClient) {
+              console.log('❌ Not in Electron environment (generateImagesRender)')
+              return false
+            }
+
+            const result = await clientStore.ipc.invoke(icpList.utils.downloadFont, {
+              url: fontUrl,
+              fontName: fontFamily
+            })
+
+            console.log('📥 generateImagesRender IPC result:', result)
+
+            if (result.success) {
+              console.log('字体下载到缓存成功:', fontFamily)
+              return true
+            }
+            return false
+          } catch (error) {
+            console.error('❌ generateImagesRender IPC call failed:', error)
+            console.warn('下载字体到缓存失败:', error)
+            return false
+          }
+        }
+
         const checkFont = (name) => {
           let values = document.fonts.values();
           let isHave = false;

+ 110 - 3
frontend/src/views/components/marketingEdit/index.vue

@@ -28,6 +28,13 @@ const FIXED_CANVAS_WIDTH = 395
 export default {
   name: 'marketingImageEditor',
   mixins: [ viewMixins,actionsMixins,layerMixins,colorMixins,editMixins],
+  setup() {
+    // 在Vue 3的Options API中,我们需要在setup函数中调用组合式函数
+    const clientStore = useClientStore()
+    return {
+      clientStore
+    }
+  },
 
   props: {
     data: {
@@ -365,17 +372,37 @@ export default {
         console.warn('模板字体预加载失败:', error)
       }
     },
-    // 为单个对象加载字体
+    // 为单个对象加载字体(使用IPC进行字体缓存管理)
     async loadFontForObject(fontFamily, fontUrl) {
-      return new Promise((resolve, reject) => {
+      return new Promise(async (resolve, reject) => {
+        // 先检查浏览器是否已有此字体
         if (this.checkFont(fontFamily)) {
+          console.log('字体已在浏览器中:', fontFamily)
           return resolve(true)
         }
 
+        try {
+          // 尝试从本地缓存加载字体(使用IPC)
+          const cachedResult = await this.loadFontFromCacheIPC(fontFamily, fontUrl)
+          if (cachedResult) {
+            console.log('从本地缓存加载字体成功:', fontFamily)
+            return resolve(true)
+          }
+        } catch (cacheError) {
+          console.warn('从缓存加载字体失败,使用网络加载:', cacheError)
+        }
+
+        // 回退到网络加载
         const fontFace = new FontFace(fontFamily, `url(${fontUrl})`)
         fontFace.load().then(loadedFace => {
           document.fonts.add(loadedFace)
-          console.log('字体加载成功:', fontFamily)
+          console.log('从网络加载字体成功:', fontFamily)
+
+          // 异步下载到本地缓存(不阻塞当前加载)
+          this.downloadFontToCacheIPC(fontFamily, fontUrl).catch(err => {
+            console.warn('异步缓存字体失败:', err)
+          })
+
           resolve(true)
         }).catch(error => {
           console.warn('字体加载失败:', fontFamily, error)
@@ -383,6 +410,86 @@ export default {
         })
       })
     },
+
+    // 使用IPC从本地缓存加载字体
+    async loadFontFromCacheIPC(fontFamily, fontUrl) {
+      try {
+        // 检查是否在Electron环境中
+        if (!this.$store?.state?.client?.isClient) {
+          return false
+        }
+
+        // 调用IPC方法从缓存加载字体
+        const result = await this.clientStore.ipc.invoke(icpList.utils.loadFontFromCache, {
+          fontName: fontFamily,
+          fontUrl: fontUrl
+        })
+
+        if (result.success && result.dataUrl) {
+          // 创建FontFace并加载字体
+          const fontFace = new FontFace(fontFamily, `url(${result.dataUrl})`)
+          await fontFace.load()
+          document.fonts.add(fontFace)
+
+          console.log('从本地缓存加载字体成功:', fontFamily)
+          return true
+        }
+
+        return false
+      } catch (error) {
+        console.warn('从缓存加载字体失败:', error)
+        return false
+      }
+    },
+
+    // 使用IPC下载字体到本地缓存
+    async downloadFontToCacheIPC(fontFamily, fontUrl) {
+      try {
+        console.log('🎨 downloadFontToCacheIPC called:', { fontFamily, fontUrl })
+
+        // 使用从setup返回的clientStore
+        if (!this.clientStore?.isClient) {
+          console.log('❌ Not in Electron environment')
+          return false
+        }
+
+        // 先测试IPC是否工作
+        console.log('🧪 Testing IPC connection...')
+        try {
+          const testResult = await this.clientStore.ipc.invoke(icpList.utils.testIPC, {
+            test: 'hello from frontend'
+          })
+          console.log('✅ IPC test successful:', testResult)
+        } catch (testError) {
+          console.error('❌ IPC test failed:', testError)
+          return false
+        }
+
+        console.log('🔧 Invoking IPC:', icpList.utils.downloadFont, { url: fontUrl, fontName: fontFamily })
+        try {
+          // 调用IPC方法下载字体
+          const result = await this.clientStore.ipc.invoke(icpList.utils.downloadFont, {
+            url: fontUrl,
+            fontName: fontFamily
+          })
+          console.log('📥 IPC result:', result)
+        } catch (ipcError) {
+          console.error('❌ IPC call failed:', ipcError)
+          throw ipcError
+        }
+
+        if (result.success) {
+          console.log('字体下载到缓存成功:', fontFamily)
+          return true
+        }
+
+        console.warn('字体下载到缓存失败:', result.error)
+        return false
+      } catch (error) {
+        console.warn('下载字体到缓存失败:', error)
+        return false
+      }
+    },
     // 检查字体是否已加载
     checkFont(fontName) {
       const values = document.fonts.values()