|
|
|
|
|
|
|
|
|
|
|
|
|
const getHtml2Canvas = async () => { |
|
const { default: html2canvas } = await import('html2canvas'); |
|
return html2canvas; |
|
}; |
|
|
|
|
|
interface RenderOptions { |
|
scale?: number; |
|
backgroundColor?: string | null; |
|
useCORS?: boolean; |
|
timeout?: number; |
|
format?: 'png' | 'jpeg' | 'webp'; |
|
quality?: number; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function renderElementToCanvas( |
|
element: HTMLElement, |
|
options: RenderOptions = {} |
|
): Promise<HTMLCanvasElement> { |
|
const { |
|
scale = 1, |
|
backgroundColor = null, |
|
useCORS = true, |
|
timeout = 5000 |
|
} = options; |
|
|
|
return new Promise(async (resolve, reject) => { |
|
const timeoutId = setTimeout(() => { |
|
reject(new Error('Canvas rendering timeout')); |
|
}, timeout); |
|
|
|
try { |
|
|
|
const html2canvas = await getHtml2Canvas(); |
|
|
|
|
|
html2canvas(element, { |
|
scale, |
|
backgroundColor, |
|
useCORS, |
|
allowTaint: !useCORS, |
|
foreignObjectRendering: true, |
|
logging: false, |
|
width: element.offsetWidth, |
|
height: element.offsetHeight, |
|
windowWidth: element.offsetWidth, |
|
windowHeight: element.offsetHeight |
|
}).then((canvas) => { |
|
clearTimeout(timeoutId); |
|
resolve(canvas); |
|
}).catch((error) => { |
|
clearTimeout(timeoutId); |
|
reject(new Error(`html2canvas rendering failed: ${error.message}`)); |
|
}); |
|
} catch (error) { |
|
clearTimeout(timeoutId); |
|
reject(error); |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function renderSVGToCanvas( |
|
svgElement: SVGElement, |
|
options: RenderOptions = {} |
|
): Promise<HTMLCanvasElement> { |
|
|
|
|
|
const { |
|
scale = 1, |
|
backgroundColor = null, |
|
timeout = 5000 |
|
} = options; |
|
|
|
return new Promise((resolve, reject) => { |
|
const timeoutId = setTimeout(() => { |
|
reject(new Error('SVG Canvas rendering timeout')); |
|
}, timeout); |
|
|
|
try { |
|
|
|
const rect = svgElement.getBoundingClientRect(); |
|
const width = rect.width || svgElement.clientWidth || 300; |
|
const height = rect.height || svgElement.clientHeight || 200; |
|
|
|
|
|
const canvas = document.createElement('canvas'); |
|
const ctx = canvas.getContext('2d'); |
|
|
|
if (!ctx) { |
|
clearTimeout(timeoutId); |
|
reject(new Error('Failed to get 2D context')); |
|
return; |
|
} |
|
|
|
canvas.width = width * scale; |
|
canvas.height = height * scale; |
|
ctx.scale(scale, scale); |
|
|
|
|
|
if (backgroundColor) { |
|
ctx.fillStyle = backgroundColor; |
|
ctx.fillRect(0, 0, width, height); |
|
} |
|
|
|
|
|
const svgData = new XMLSerializer().serializeToString(svgElement); |
|
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' }); |
|
const url = URL.createObjectURL(svgBlob); |
|
|
|
const img = new Image(); |
|
img.onload = () => { |
|
try { |
|
ctx.drawImage(img, 0, 0, width, height); |
|
URL.revokeObjectURL(url); |
|
clearTimeout(timeoutId); |
|
resolve(canvas); |
|
} catch (error) { |
|
URL.revokeObjectURL(url); |
|
clearTimeout(timeoutId); |
|
reject(new Error(`Failed to draw SVG to Canvas: ${error}`)); |
|
} |
|
}; |
|
|
|
img.onerror = () => { |
|
URL.revokeObjectURL(url); |
|
clearTimeout(timeoutId); |
|
reject(new Error('SVG image loading failed')); |
|
}; |
|
|
|
img.src = url; |
|
} catch (error) { |
|
clearTimeout(timeoutId); |
|
reject(new Error(`SVG serialization failed: ${error}`)); |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function canvasToBase64( |
|
canvas: HTMLCanvasElement, |
|
options: RenderOptions = {} |
|
): string { |
|
const { format = 'png', quality = 0.95 } = options; |
|
|
|
try { |
|
if (format === 'jpeg') { |
|
return canvas.toDataURL('image/jpeg', quality); |
|
} else if (format === 'webp') { |
|
return canvas.toDataURL('image/webp', quality); |
|
} else { |
|
return canvas.toDataURL('image/png'); |
|
} |
|
} catch (error) { |
|
throw new Error(`Canvas to Base64 conversion failed: ${error}`); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function renderElementToBase64( |
|
element: HTMLElement, |
|
options: RenderOptions = {} |
|
): Promise<string> { |
|
try { |
|
console.log('Starting element to Base64 conversion:', { |
|
element: element.tagName, |
|
className: element.className, |
|
options |
|
}); |
|
|
|
const canvas = await renderElementToCanvas(element, options); |
|
const base64 = canvasToBase64(canvas, options); |
|
|
|
console.log('Element to Base64 conversion completed:', { |
|
canvasSize: `${canvas.width}x${canvas.height}`, |
|
base64Length: base64.length, |
|
preview: base64.substring(0, 100) + '...' |
|
}); |
|
|
|
return base64; |
|
} catch (error) { |
|
console.error('Element to Base64 conversion failed:', error); |
|
throw new Error(`Element rendering failed: ${error}`); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function isCanvasRenderSupported(): boolean { |
|
try { |
|
const canvas = document.createElement('canvas'); |
|
const ctx = canvas.getContext('2d'); |
|
return !!(ctx && typeof ctx.drawImage === 'function' && typeof window !== 'undefined'); |
|
} catch { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getElementDimensions(element: HTMLElement) { |
|
const rect = element.getBoundingClientRect(); |
|
const computedStyle = window.getComputedStyle(element); |
|
|
|
return { |
|
width: rect.width || element.offsetWidth || parseFloat(computedStyle.width) || 0, |
|
height: rect.height || element.offsetHeight || parseFloat(computedStyle.height) || 0, |
|
clientWidth: element.clientWidth, |
|
clientHeight: element.clientHeight, |
|
offsetWidth: element.offsetWidth, |
|
offsetHeight: element.offsetHeight, |
|
boundingRect: { |
|
width: rect.width, |
|
height: rect.height, |
|
top: rect.top, |
|
left: rect.left |
|
} |
|
}; |
|
} |