From d5c17e1cf1cdf4afd1ad2b560fbe8d467d608947 Mon Sep 17 00:00:00 2001 From: hzsrc Date: Fri, 16 Aug 2024 15:31:51 +0800 Subject: [PATCH 01/19] feat: add option to resolve problems which caused by html that has large amount of sub nodes --- README.md | 9 +++++++++ src/clone-node.ts | 12 ++++++------ src/index.ts | 2 +- src/types.ts | 5 +++++ src/util.ts | 27 +++++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 782d2525..1b1e0a8b 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,15 @@ A string indicating the image format. The default type is image/png; that type i When supplied, the toCanvas function will return a blob matching the given image type and quality. Defaults to `image/png` + + +### usePageCss + +Use `true` to add a `\n' + node.outerHTML + '' +} + export async function toImage( node: T, options: Options = {}, diff --git a/src/util.ts b/src/util.ts index f782d062..055bf962 100644 --- a/src/util.ts +++ b/src/util.ts @@ -311,22 +311,23 @@ export function createImage(url: string): Promise { export async function svgToDataURL(svg: SVGElement): Promise { return Promise.resolve() .then(async function() { - // svg中的图片地址无法离线下载,需要先转成dataUrl - const imgs = svg.querySelectorAll('img') - return Promise.all( - [].slice.call(imgs, 0).map((img: HTMLImageElement) => { - return urlToDataUrl(img.src).then((dataUrl) => (img.src = dataUrl)) - }), - ).then(() => { - const xml = new XMLSerializer().serializeToString(svg) - // open('about:blank').document.write('' + xml); //for debug - return xml - }) + const xml = new XMLSerializer().serializeToString(svg) + // open('about:blank').document.write('<plaintext>' + xml); //for debug + return xml }) .then(encodeURIComponent) .then((html) => `data:image/svg+xml;charset=utf-8,${html}`) } +export function setImgDataUrl(el: Element) { + const imgs = el.querySelectorAll('img') + return Promise.all( + [].slice.call(imgs, 0).map((img: HTMLImageElement) => { + return urlToDataUrl(img.src).then((dataUrl) => (img.src = dataUrl)) + }), + ) +} + const TailColor = 'fefffd' const TailHeight = 60 @@ -398,13 +399,13 @@ export function getStyles() { promises.push( fetch(href) .then((r) => r.text()) - .then((tx) => transRelPath(href, tx)) + .then((tx) => srcToDataUrl(href, tx)) .catch(() => ''), ) } else { promises.push( Promise.resolve( - transRelPath(window.location.href, (e as HTMLStyleElement).innerText), + srcToDataUrl(window.location.href, (e as HTMLStyleElement).innerText), ), ) } @@ -414,7 +415,7 @@ export function getStyles() { }) } -function transRelPath(cssPath: string, cssTextIn: string): Promise<string> { +function srcToDataUrl(cssPath: string, cssTextIn: string): Promise<string> { const cssText = cssTextIn.replace(/\/\*[\s\S]*?\*\//g, '') const quotReg = /^\s*(['"])(.+?)\1/ const map: { [url: string]: string } = {} From 39128702f57d8c95a1748a44d29b9ecf7e26b07e Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Wed, 20 Nov 2024 15:49:52 +0800 Subject: [PATCH 15/19] willReadFrequently: true --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9da3224e..8678d32a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,7 +64,7 @@ export async function toCanvas<T extends HTMLElement>( const img = await toImage(node, options) const { width, height } = getImageSize(node, options, img) const canvas = document.createElement('canvas') - const context = canvas.getContext('2d')! + const context = canvas.getContext('2d', { willReadFrequently: true })! const ratio = options.pixelRatio || getPixelRatio() const canvasWidth = options.canvasWidth || width const canvasHeight = options.canvasHeight || height @@ -107,7 +107,7 @@ export async function toCanvasList<T extends HTMLElement>( const scale = canvasWidth / img.width for (let curY = 0; curY < canvasHeight; curY += dimensionLimit) { const canvas = document.createElement('canvas') - const context = canvas.getContext('2d')! + const context = canvas.getContext('2d', { willReadFrequently: true })! const height1 = Math.min(canvasHeight - curY, dimensionLimit) canvas.width = canvasWidth canvas.height = height1 From d71ce44c34235f5ce0076fd8bb9a1da5aa36d208 Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Wed, 20 Nov 2024 15:59:39 +0800 Subject: [PATCH 16/19] await --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 8678d32a..13edf996 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,9 +44,10 @@ async function prepareNode(node: HTMLElement, options: Options = {}) { export async function toOfflineHtml(node: HTMLElement, options: Options = {}) { var node = await prepareNode(node, options) + var style = await getStyles() return '<!DOCTYPE html><html>' + '<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">' + - '<body><style>' + getStyles() + '</style>\n' + node.outerHTML + '</body></html>' + '<body><style>' + style + '</style>\n' + node.outerHTML + '</body></html>' } export async function toImage<T extends HTMLElement>( From da1953dea1a83d4f0bb4059401eb83c103ad46aa Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Mon, 13 Jan 2025 15:32:55 +0800 Subject: [PATCH 17/19] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=AB=98=E5=BA=A6?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/util.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9901851b..db356873 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-image-big", - "version": "1.11.12", + "version": "1.11.13", "description": "Generates an image from a DOM node using HTML5 canvas and SVG. Support big html page.", "main": "lib/index.js", "module": "es/index.js", diff --git a/src/util.ts b/src/util.ts index 055bf962..bc4c93e4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -341,7 +341,8 @@ export async function nodeToDataURL( const svg = document.createElementNS(xmlns, 'svg') const foreignObject = document.createElementNS(xmlns, 'foreignObject') // add a tail for check ending - const heightWithTail = height + TailHeight * 2 + let heightWithTail = height + if (opt.checkTail) heightWithTail += TailHeight * 2 // fix: if ratio=2 and style.border='1px', in html it is actually rendered to 1px, but in <img src="svg" alt="i"> it is rendered to 2px. Then height is different and the bottom 1px is lost, 10 nodes will lost 10px. const ratio = getPixelRatio() svg.setAttribute('width', `${width / ratio}`) From 705e6aaca9bc252e4f2c5849e8f2a54b70fbcdcf Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Mon, 13 Jan 2025 15:55:06 +0800 Subject: [PATCH 18/19] lint --- src/index.ts | 20 ++++++++------------ src/types.ts | 2 +- src/util.ts | 21 ++++++++++++--------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/index.ts b/src/index.ts index 13edf996..dfca4e51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,6 @@ import { setImgDataUrl, } from './util' - export async function toSvg<T extends HTMLElement>( node: T, options: Options = {}, @@ -25,12 +24,7 @@ export async function toSvg<T extends HTMLElement>( // await embedWebFonts(clonedNode, options) // await embedImages(clonedNode, options) applyStyle(clonedNode, options) - const datauri = await nodeToDataURL( - clonedNode, - width, - height, - options, - ) + const datauri = await nodeToDataURL(clonedNode, width, height, options) return datauri } @@ -43,11 +37,13 @@ async function prepareNode(node: HTMLElement, options: Options = {}) { } export async function toOfflineHtml(node: HTMLElement, options: Options = {}) { - var node = await prepareNode(node, options) - var style = await getStyles() - return '<!DOCTYPE html><html>' + - '<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">' + - '<body><style>' + style + '</style>\n' + node.outerHTML + '</body></html>' + const node1 = await prepareNode(node, options) + const style = await getStyles() + return ( + `<!DOCTYPE html><html>` + + `<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">` + + `<body><style>${style}</style>\n${node1.outerHTML}</body></html>` + ) } export async function toImage<T extends HTMLElement>( diff --git a/src/types.ts b/src/types.ts index 2b4c20fd..9cf7f745 100644 --- a/src/types.ts +++ b/src/types.ts @@ -95,7 +95,7 @@ export interface Options { * Use a <style> in svg to import all styles of current html page, and do not add computed styles to every node any more. * This will make the svg content very small, to resolve problems when html has large amount of sub nodes. * */ - usePageCss?: boolean, + usePageCss?: boolean /* * Check whether the svg tail is integrated. * This will fix some problems that the last page of exported pdf is truncated. diff --git a/src/util.ts b/src/util.ts index bc4c93e4..aa3965d6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -236,8 +236,8 @@ export function svgUrlToImg(urlIn: string, opt: Options = {}) { // 原因诸如:4k屏的1px在html中为0.51px,而在svg中为1px;又如 overflow-y 在svg中失效;background定位不兼容等。 // 为了避免图像底部不完整的情况,这里每次额外增加60px高度,并寻找是否存在底部标志颜色(TailColor),直到已存在,说明已经到达底部。 function checkImg(i: number): Promise<HTMLImageElement> { - let url = replaceHeight(urlIn, TailHeight * i) - return createImage(url).then(function(img) { + const url = replaceHeight(urlIn, TailHeight * i) + return createImage(url).then(function (img) { const prePx = 3 const canvasHeight = (TailHeight * 2) / deviceRatio + prePx const ctx = get2dCtx(1, canvasHeight) @@ -269,7 +269,7 @@ export function svgUrlToImg(urlIn: string, opt: Options = {}) { if (color !== TailColor) { // 分界点位置 const posY = -(canvasHeight - j / 4) * deviceRatio - var url1 = replaceHeight(url, posY) + const url1 = replaceHeight(url, posY) return createImage(url1) } } @@ -281,11 +281,11 @@ export function svgUrlToImg(urlIn: string, opt: Options = {}) { return url .replace( /(viewBox%3D%220%200%20[\d.]+%20)([\d.]+)%22/, - function(_, m1, vpHeight) { + function (_, m1, vpHeight) { return `${m1 + (+vpHeight + delta)}%22` }, ) - .replace(/(%20height%3D%22)([\d.]+)%22/, function(_, m1, height) { + .replace(/(%20height%3D%22)([\d.]+)%22/, function (_, m1, height) { return `${m1 + (+height + delta / deviceRatio)}%22` }) } @@ -310,7 +310,7 @@ export function createImage(url: string): Promise<HTMLImageElement> { export async function svgToDataURL(svg: SVGElement): Promise<string> { return Promise.resolve() - .then(async function() { + .then(async function () { const xml = new XMLSerializer().serializeToString(svg) // open('about:blank').document.write('<plaintext>' + xml); //for debug return xml @@ -360,7 +360,9 @@ export async function nodeToDataURL( if (opt.checkTail) { foreignObject.insertAdjacentHTML( 'beforeend', - `<div style="background: #${TailColor};height:${TailHeight * 2}px"></div>`, + `<div style="background: #${TailColor};height:${ + TailHeight * 2 + }px"></div>`, ) } if (opt.usePageCss) { @@ -372,8 +374,9 @@ export async function nodeToDataURL( return svgToDataURL(svg) } -export const isInstanceOfElement = <T extends typeof Element | typeof HTMLElement | typeof SVGImageElement, - >( +export const isInstanceOfElement = < + T extends typeof Element | typeof HTMLElement | typeof SVGImageElement, +>( node: Element | HTMLElement | SVGImageElement, instance: T, ): node is T['prototype'] => { From e43e2e619292a3c1a6678fc6f592f1c7478bd451 Mon Sep 17 00:00:00 2001 From: hzsrc <hz8889999> Date: Tue, 21 Jan 2025 15:08:45 +0800 Subject: [PATCH 19/19] =?UTF-8?q?Image=E5=B0=9A=E6=9C=AA=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/index.ts | 2 +- src/util.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index db356873..427a13e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-image-big", - "version": "1.11.13", + "version": "1.11.15", "description": "Generates an image from a DOM node using HTML5 canvas and SVG. Support big html page.", "main": "lib/index.js", "module": "es/index.js", diff --git a/src/index.ts b/src/index.ts index dfca4e51..000ef6bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,7 @@ async function prepareNode(node: HTMLElement, options: Options = {}) { if (!('usePageCss' in options)) options.usePageCss = true const clonedNode = (await cloneNode(node, options, true)) as HTMLElement // svg中的图片地址无法离线下载,需要先转成dataUrl - setImgDataUrl(clonedNode) + await setImgDataUrl(clonedNode) return clonedNode } diff --git a/src/util.ts b/src/util.ts index aa3965d6..7f11db6f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -323,6 +323,7 @@ export function setImgDataUrl(el: Element) { const imgs = el.querySelectorAll('img') return Promise.all( [].slice.call(imgs, 0).map((img: HTMLImageElement) => { + if (/^data:/.test(img.src)) return Promise.resolve() return urlToDataUrl(img.src).then((dataUrl) => (img.src = dataUrl)) }), )