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

Merge branch 'beta' of https://git.cocorobo.cn/jack/PPT into beta

lsc 3 недель назад
Родитель
Сommit
e42d60c574
3 измененных файлов с 547 добавлено и 90 удалено
  1. 448 50
      package-lock.json
  2. 1 0
      package.json
  3. 98 40
      src/hooks/useImport.ts

Разница между файлами не показана из-за своего большого размера
+ 448 - 50
package-lock.json


+ 1 - 0
package.json

@@ -55,6 +55,7 @@
     "vue": "^3.5.17",
     "vuedraggable": "^4.1.0",
     "wangeditor": "^4.7.15",
+    "webcodecs-encoder": "^0.3.2",
     "y-websocket": "^3.0.0",
     "yjs": "^13.6.27"
   },

+ 98 - 40
src/hooks/useImport.ts

@@ -28,7 +28,8 @@ import type {
   Gradient,
 } from '@/types/slides'
 
-const convertFontSizePtToPx = (html: string, ratio: number) => {
+const convertFontSizePtToPx = (html: string, ratio: number, autoFit: any) => {
+  if (autoFit?.fontScale && autoFit?.type == "text") { ratio = ratio * autoFit.fontScale / 100; }
   // return html;
   return html.replace(/\s*([\d.]+)pt/g, (match, p1) => {
     return `${(parseFloat(p1) * ratio - 1) | 0}px `
@@ -403,6 +404,7 @@ export default () => {
   /**
    * 将 base64 字符串或 Blob 转换为 File 对象
    */
+  
   const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): File => {
     if (typeof data === 'string') {
       // 1. 通过 fetch 获取 Blob 数据
@@ -422,6 +424,62 @@ export default () => {
     }
     throw new Error('Unsupported data type')
   }
+  
+/*
+  // 你原有的 dataToFile 函数保持不变
+  const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): Promise<File> => {
+    if (typeof data === 'string') {
+      const response = await fetch(data);
+      if (!response.ok) {
+        throw new Error(`Failed to fetch blob: ${response.statusText}`);
+      }
+      const blob = await response.blob();
+      const mime = videoMimeType || blob.type;
+      return new File([blob], filename, { type: mime });
+    } else if (data instanceof Blob) {
+      return new File([data], filename, { type: data.type });
+    }
+    throw new Error('Unsupported data type');
+  };
+
+
+  const convertVideoToMP4 = async (
+    videoSource: string | Blob,
+    outputFilename: string = `video_${Date.now()}.mp4`
+  ): Promise<File> => {
+    // 1. 检查浏览器支持
+    const supported = await canEncode();
+    if (!supported) {
+      throw new Error('当前浏览器不支持 WebCodecs,请使用最新 Chrome/Edge 并确保 HTTPS');
+    }
+
+    // 2. 转为 File
+    const inputFile = await dataToFile(videoSource, 'input.mp4', 'video/mp4');
+
+    // 3. 创建 VideoFile 对象(webcodecs-encoder 的输入包装)
+    //const videoFile = new VideoFile(inputFile);
+    const videoFile = {
+      file: inputFile,
+      type: 'video/mp4'
+    };
+    // 4. 执行编码
+    const encodedData = await encode(videoFile, {
+      quality: 'high',
+      video: {
+        codec: 'av1',
+        bitrate: 2_000_000,
+        hardwareAcceleration: 'prefer-hardware'
+      },
+      audio: false,           // 显式禁用音频编码
+      container: 'mp4',
+      onProgress: (progress) => console.log(progress)
+    });
+
+    // 5. 返回 File
+    return new File([encodedData], outputFilename, { type: 'video/mp4' });
+  };
+*/
+
 
   /*
   const makeWhiteTransparent = async (
@@ -514,14 +572,14 @@ export default () => {
     options?: { tolerance?: number }
   ): Promise<File> => {
     const tolerance = options?.tolerance ?? 15
-  
+
     // ----- 辅助函数:将输入统一转换为 { blob, mime } -----
     async function getBlobAndMime(input: string | Blob): Promise<{ blob: Blob; mime: string }> {
       // 1. 已经是 Blob
       if (input instanceof Blob) {
         return { blob: input, mime: input.type }
       }
-  
+
       // 2. 处理字符串
       if (input.startsWith('data:')) {
         // data URL → 通过 fetch 获取 Blob(自动获得正确的 MIME 类型)
@@ -540,15 +598,15 @@ export default () => {
       return { blob, mime: 'image/png' }
       
     }
-  
+
     // 获取统一的 blob 和实际 MIME 类型
     const { blob, mime } = await getBlobAndMime(data)
-  
+
     // ----- 非 PNG 格式:直接返回原始文件(不处理透明)-----
     if (mime !== 'image/png') {
       return new File([blob], filename, { type: mime })
     }
-  
+
     // ----- PNG 格式:执行白色变透明处理 -----
     // 1. 创建对象 URL 用于加载图片
     const imageUrl = URL.createObjectURL(blob)
@@ -562,33 +620,33 @@ export default () => {
       // Blob URL 不需要设置 crossOrigin
       image.src = imageUrl
     })
-  
+
     const canvas = document.createElement('canvas')
     try {
       canvas.width = img.width
       canvas.height = img.height
       const ctx = canvas.getContext('2d')!
       ctx.drawImage(img, 0, 0)
-  
+
       // 3. 获取像素数据,将接近白色的像素设为透明
       const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
       const dataArray = imageData.data
-  
+
       for (let i = 0; i < dataArray.length; i += 4) {
         const r = dataArray[i]
         const g = dataArray[i + 1]
         const b = dataArray[i + 2]
-  
+
         const dr = r - 255
         const dg = g - 255
         const db = b - 255
         const dist = Math.sqrt(dr * dr + dg * dg + db * db)
-  
+
         if (dist <= tolerance) {
           dataArray[i + 3] = 0 // 完全透明
         }
       }
-  
+
       ctx.putImageData(imageData, 0, 0)
     }
     finally {
@@ -596,7 +654,7 @@ export default () => {
         URL.revokeObjectURL(imageUrl)
       }
     }
-  
+
     // 4. 导出为 PNG Blob
     const outputBlob = await new Promise<Blob>((resolve, reject) => {
       canvas.toBlob((blob) => {
@@ -604,7 +662,7 @@ export default () => {
         else reject(new Error('Canvas toBlob failed'))
       }, 'image/png')
     })
-  
+
     return new File([outputBlob], filename, { type: 'image/png' })
   }
 
@@ -1338,8 +1396,8 @@ export default () => {
                 rotate: el.rotate,
                 defaultFontName: theme.value.fontName,
                 defaultColor: theme.value.fontColor,
-                content: convertFontSizePtToPx(el.content, ratio),
-                style: getStyle(convertFontSizePtToPx(el.content, ratio)),
+                content: convertFontSizePtToPx(el.content, ratio, el.autoFit),
+                style: getStyle(convertFontSizePtToPx(el.content, ratio, el.autoFit)),
                 lineHeight: 1.15,
                 outline: {
                   color: el.borderColor,
@@ -1401,27 +1459,27 @@ export default () => {
                   range: [[0, 0], [100, 100]],
                 }
               }
-
-              // 如果 src 是 base64,触发上传
-              if (el.src && typeof el.src === 'string' && el.src.startsWith('data:')) {
-                const uploadTask = (async () => {
-                  try {
-                    const file = await makeWhiteTransparent(el.src, `image_${Date.now()}.png`)
-                    if (file) {
-                      const url = await uploadFileToS3(file)
-                      element.src = url // 替换为远程 URL
-                      const slidesStore = useSlidesStore()
-                      slidesStore.updateElement({ id: element.id, props: { src: url } })
-                    }
-                  }
-                  catch (error) {
-                    console.error('Image upload failed:', error)
-                    // 失败时保留原 base64(或可置空)
-                  }
-                })()
-                uploadTasks.push(uploadTask)
-              }
-
+              /*
+                            // 如果 src 是 base64,触发上传
+                            if (el.src && typeof el.src === 'string' && el.src.startsWith('data:')) {
+                              const uploadTask = (async () => {
+                                try {
+                                  const file = await makeWhiteTransparent(el.src, `image_${Date.now()}.png`)
+                                  if (file) {
+                                    const url = await uploadFileToS3(file)
+                                    element.src = url // 替换为远程 URL
+                                    const slidesStore = useSlidesStore()
+                                    slidesStore.updateElement({ id: element.id, props: { src: url } })
+                                  }
+                                }
+                                catch (error) {
+                                  console.error('Image upload failed:', error)
+                                  // 失败时保留原 base64(或可置空)
+                                }
+                              })()
+                              uploadTasks.push(uploadTask)
+                            }
+              */
               slide.elements.push(element)
             }
             else if (el.type === 'math') {
@@ -1518,7 +1576,7 @@ export default () => {
               if (localData) {
                 const uploadTask = (async () => {
                   try {
-                    const file = await dataToFile(localData, `video_${Date.now()}.mp4`, 'video/mp4')
+                    const file = await convertVideoToMP4(localData, `video_${Date.now()}.mp4`)
                     if (file) {
                       const url = await uploadFileToS3(file)
                       element.src = url
@@ -1566,7 +1624,7 @@ export default () => {
 
                 const pattern: string | undefined = el.fill?.type === 'image' ? el.fill.value.picBase64 : undefined
                 const fill = el.fill?.type === 'color' ? el.fill.value : ''
-                const style = getStyle(convertFontSizePtToPx(el.content, ratio)) + (el.pathBBox.pWidth ? ';width:' + (el.pathBBox.pWidth * ratio) + 'px;height:' + (el.pathBBox.pHeight * ratio) + 'px;' : '') // 设置字体的样式等,这里由于不支持的样式在里面会过滤
+                const style = getStyle(convertFontSizePtToPx(el.content, ratio, el.autoFit)) + (el.pathBBox.pWidth ? ';width:' + (el.pathBBox.pWidth * ratio) + 'px;height:' + (el.pathBBox.pHeight * ratio) + 'px;' : '') // 设置字体的样式等,这里由于不支持的样式在里面会过滤
                 const element: PPTShapeElement = {
                   type: 'shape',
                   id: nanoid(10),
@@ -1588,7 +1646,7 @@ export default () => {
                     style: el.borderType,
                   },
                   text: {
-                    content: convertFontSizePtToPx(el.content, ratio),
+                    content: convertFontSizePtToPx(el.content, ratio, el.autoFit),
                     style: style,
                     defaultFontName: theme.value.fontName,
                     defaultColor: theme.value.fontColor,

Некоторые файлы не были показаны из-за большого количества измененных файлов