Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<style>` tag in svg content which imports all styles of current html page, and do not add computed styles to every node any more(this make svg content so large that Firefox throw errors while load svg as image).
This will make the svg content much smaller, to resolve problems which caused by html that has large amount of sub nodes.


Defaults to `false`

## Browsers

Expand Down
12 changes: 6 additions & 6 deletions src/clone-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ function cloneCSSStyle<T extends HTMLElement>(nativeNode: T, clonedNode: T) {
) {
value = 'block'
}

if (name === 'd' && clonedNode.getAttribute('d')) {
value = `path(${clonedNode.getAttribute('d')})`
}

targetStyle.setProperty(
name,
value,
Expand Down Expand Up @@ -170,10 +170,10 @@ function cloneSelectValue<T extends HTMLElement>(nativeNode: T, clonedNode: T) {
}
}

function decorate<T extends HTMLElement>(nativeNode: T, clonedNode: T): T {
function decorate<T extends HTMLElement>(nativeNode: T, clonedNode: T, usePageCss?: boolean): T {
if (isInstanceOfElement(clonedNode, Element)) {
cloneCSSStyle(nativeNode, clonedNode)
clonePseudoElements(nativeNode, clonedNode)
if (!usePageCss) cloneCSSStyle(nativeNode, clonedNode)
if (!usePageCss) clonePseudoElements(nativeNode, clonedNode)
cloneInputValue(nativeNode, clonedNode)
cloneSelectValue(nativeNode, clonedNode)
}
Expand Down Expand Up @@ -240,6 +240,6 @@ export async function cloneNode<T extends HTMLElement>(
return Promise.resolve(node)
.then((clonedNode) => cloneSingleNode(clonedNode, options) as Promise<T>)
.then((clonedNode) => cloneChildren(node, clonedNode, options))
.then((clonedNode) => decorate(node, clonedNode))
.then((clonedNode) => decorate(node, clonedNode, options.usePageCss))
.then((clonedNode) => ensureSVGSymbols(clonedNode, options))
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,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)
const datauri = await nodeToDataURL(clonedNode, width, height, options.usePageCss)
return datauri
}

Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,9 @@ export interface Options {
*
*/
fetchRequestInit?: RequestInit
/*
* 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
}
34 changes: 32 additions & 2 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,16 @@ export async function nodeToDataURL(
node: HTMLElement,
width: number,
height: number,
usePageCss?: boolean,
): Promise<string> {
const xmlns = 'http://www.w3.org/2000/svg'
const svg = document.createElementNS(xmlns, 'svg')
const foreignObject = document.createElementNS(xmlns, 'foreignObject')

svg.setAttribute('width', `${width}`)
svg.setAttribute('height', `${height}`)
// fix: if ratio=2 and style.border='1px', in html it is actually rendered to 1px, but in <img src="svg"> it is rendered to 2px. Then height is different and the bottom 1px is lost, 10 nodes will lost 10px.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This image is missing a text alternative. This is a problem for people using screen readers.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This image is missing a text alternative. This is a problem for people using screen readers.

var ratio = self.devicePixelRatio;
svg.setAttribute('width', `${width / ratio}`)
svg.setAttribute('height', `${height / ratio}`)
svg.setAttribute('viewBox', `0 0 ${width} ${height}`)

foreignObject.setAttribute('width', '100%')
Expand All @@ -220,6 +223,12 @@ export async function nodeToDataURL(

svg.appendChild(foreignObject)
foreignObject.appendChild(node)
if (usePageCss) {
const style = document.createElementNS(xmlns, 'style')
style.innerHTML = await getStyles()
svg.insertBefore(style, foreignObject)
}

return svgToDataURL(svg)
}

Expand All @@ -240,3 +249,24 @@ export const isInstanceOfElement = <
isInstanceOfElement(nodePrototype, instance)
)
}

export function getStyles() {
const styles = document.querySelectorAll('style,link[rel="stylesheet"]')
const ps: Array<Promise<string>> = []
toArray(styles).forEach((el) => {
const e = el as Element
if (e.tagName === 'LINK') {
const href = e.getAttribute('href')
if (href) ps.push(getCssText(href).catch(() => ''))
} else {
ps.push(Promise.resolve(e.innerHTML))
}
})
return Promise.all(ps).then((arr) => {
return arr.join('\n\n')
})

function getCssText(url: string) {
return fetch(url).then((r) => r.text())
}
}
Loading