import { Paragraph, ImageRun } from "docx" function getImageTypeFromURL(url: string): "jpg" | "png" | "gif" | "bmp" | "svg" | "webp" | "unknown" { // 1. 尝试解析 data URL if (url.startsWith("data:")) { const mimeMatch = url.match(/^data:image\/(.*?);/) if (mimeMatch) { return normalizeImageType(mimeMatch[1]) } } // 2. 尝试解析扩展名 const extMatch = url.split("?")[0].match(/\.([a-zA-Z0-9]+)$/) if (extMatch) { return normalizeImageType(extMatch[1]) } return "unknown" } function normalizeImageType(type: string): any { const lower = type.toLowerCase() switch (lower) { case "jpeg": case "jpg": return "jpg" case "png": return "png" case "gif": return "gif" case "bmp": return "bmp" case "svg": case "svg+xml": return "svg" case "webp": return "webp" default: return "unknown" } } // Canvas格式转换成PNG async function convertCanvasToPngArrayBuffer(canvas: HTMLCanvasElement): Promise { return new Promise((resolve, reject) => { canvas.toBlob((blob) => { if (!blob) { reject(new Error("Canvas toBlob failed")) return } blob.arrayBuffer().then(resolve).catch(reject) }, "image/png") }) } // WebP格式转换成PNG async function convertWebPToPngArrayBuffer(url: string): Promise { const img = new Image() img.src = url await img.decode() const canvas = document.createElement("canvas") canvas.width = img.width canvas.height = img.height const ctx = canvas.getContext("2d")! ctx.drawImage(img, 0, 0) return await convertCanvasToPngArrayBuffer(canvas) } async function svgToPngArrayBuffer(svgText: string): Promise { // 创建 DOMParser 解析 SVG const parser = new DOMParser() const doc = parser.parseFromString(svgText, "image/svg+xml") const svgEl = doc.querySelector("svg") if (!svgEl) throw new Error("无效的 SVG 文本") // 获取尺寸(尝试读取 width/height 或 viewBox) let width = parseInt(svgEl.getAttribute("width") || "") let height = parseInt(svgEl.getAttribute("height") || "") if (!(width && height)) { const viewBox = svgEl.getAttribute("viewBox") // 格式:"0 0 300 200" if (viewBox) { const parts = viewBox.split(" ") width = parseInt(parts[2]) height = parseInt(parts[3]) } else { width = 300 height = 150 } } return new Promise((resolve, reject) => { const svgBlob = new Blob([svgText], { type: "image/svg+xml" }) const url = URL.createObjectURL(svgBlob) const img = new Image() img.onload = () => { URL.revokeObjectURL(url) const canvas = document.createElement("canvas") canvas.width = width canvas.height = height const ctx = canvas.getContext("2d")! ctx.drawImage(img, 0, 0, width, height) canvas.toBlob((blob) => { if (blob) { blob.arrayBuffer().then(resolve) } else { reject(new Error("canvas.toBlob failed")) } }, "image/png") } img.onerror = reject img.src = url }) } async function convertDomImgToDocxImg(imgEl: HTMLImageElement): Promise { // 判断图片类型 const imgType = getImageTypeFromURL(imgEl.src) if (imgType === 'unknown') { console.error("未知图片类型:", imgEl.src) return } if (imgType === 'webp') { const arrayBuffer = await convertWebPToPngArrayBuffer(imgEl.src) return new ImageRun({ type: 'png', data: arrayBuffer, transformation: { width: imgEl.naturalWidth / 2, height: imgEl.naturalHeight / 2, }, }) } else if (imgType === 'svg') { const imageBlob = await fetch(imgEl.src).then(res => res.blob()) const arrayBuffer = await imageBlob.arrayBuffer() const decoder = new TextDecoder('utf-8'); const fallbackBuffer = await svgToPngArrayBuffer(decoder.decode(new Uint8Array(arrayBuffer))) return new ImageRun({ type: 'svg', data: arrayBuffer, fallback: { type: 'png', data: fallbackBuffer, }, transformation: { width: imgEl.naturalWidth / 2, height: imgEl.naturalHeight / 2, }, }) } else { const imageBlob = await fetch(imgEl.src).then(res => res.blob()) const arrayBuffer = await imageBlob.arrayBuffer() return new ImageRun({ type: imgType, data: arrayBuffer, transformation: { width: imgEl.naturalWidth / 2, height: imgEl.naturalHeight / 2, }, }) } } async function convertDomCanvasToDocxImg(imgEl: HTMLCanvasElement): Promise { const arrayBuffer = await convertCanvasToPngArrayBuffer(imgEl) return new ImageRun({ type: 'png', data: arrayBuffer, transformation: { width: imgEl.width, height: imgEl.height, }, }) } const handleImage = async (el: Element): Promise => { const tag = el.tagName.toLowerCase() if (tag === "img") { if (el instanceof HTMLImageElement) { const imageRun = await convertDomImgToDocxImg(el) if (imageRun) { return new Paragraph({ children: [imageRun], spacing: { after: 200 }, }) } } } else if (tag === "canvas") { if (el instanceof HTMLCanvasElement) { const imageRun = await convertDomCanvasToDocxImg(el) if (imageRun) { return new Paragraph({ children: [imageRun], spacing: { after: 200 }, }) } } } } export default { handleImage, config: {} // 无配置 }