| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- 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<ArrayBuffer> {
- 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<ArrayBuffer> {
- 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<ArrayBuffer> {
- // 创建 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<ImageRun | undefined> {
- // 判断图片类型
- 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<ImageRun | undefined> {
- 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<Paragraph | undefined> => {
- 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: {} // 无配置
- }
|