Skip to content

Commit

Permalink
feat: add includeStyleProperties option
Browse files Browse the repository at this point in the history
* the full list of computedStyle properties can be cached
* users can now manually specify which style properties are included
  • Loading branch information
Justineo committed Sep 22, 2023
1 parent 05a2712 commit 27cd25b
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/apply-style.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Options } from './types'
import type { Options } from './types'

export function applyStyle<T extends HTMLElement>(
node: T,
Expand Down
37 changes: 25 additions & 12 deletions src/clone-node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { Options } from './types'
import { clonePseudoElements } from './clone-pseudos'
import { createImage, toArray, isInstanceOfElement } from './util'
import {
createImage,
toArray,
isInstanceOfElement,
getStyleProperties,
} from './util'
import { getMimeType } from './mimes'
import { resourceToDataURL } from './dataurl'

Expand Down Expand Up @@ -29,12 +34,12 @@ async function cloneVideoElement(video: HTMLVideoElement, options: Options) {
return createImage(dataURL)
}

async function cloneIFrameElement(iframe: HTMLIFrameElement) {
async function cloneIFrameElement(iframe: HTMLIFrameElement, options: Options) {
try {
if (iframe?.contentDocument?.body) {
return (await cloneNode(
iframe.contentDocument.body,
{},
options,
true,
)) as HTMLBodyElement
}
Expand All @@ -58,7 +63,7 @@ async function cloneSingleNode<T extends HTMLElement>(
}

if (isInstanceOfElement(node, HTMLIFrameElement)) {
return cloneIFrameElement(node)
return cloneIFrameElement(node, options)
}

return node.cloneNode(false) as T
Expand Down Expand Up @@ -107,7 +112,11 @@ async function cloneChildren<T extends HTMLElement>(
return clonedNode
}

function cloneCSSStyle<T extends HTMLElement>(nativeNode: T, clonedNode: T) {
function cloneCSSStyle<T extends HTMLElement>(
nativeNode: T,
clonedNode: T,
options: Options,
) {
const targetStyle = clonedNode.style
if (!targetStyle) {
return
Expand All @@ -118,7 +127,7 @@ function cloneCSSStyle<T extends HTMLElement>(nativeNode: T, clonedNode: T) {
targetStyle.cssText = sourceStyle.cssText
targetStyle.transformOrigin = sourceStyle.transformOrigin
} else {
toArray<string>(sourceStyle).forEach((name) => {
getStyleProperties(options).forEach((name) => {
let value = sourceStyle.getPropertyValue(name)
if (name === 'font-size' && value.endsWith('px')) {
const reducedFont =
Expand All @@ -133,11 +142,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 +179,14 @@ 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,
options: Options,
): T {
if (isInstanceOfElement(clonedNode, Element)) {
cloneCSSStyle(nativeNode, clonedNode)
clonePseudoElements(nativeNode, clonedNode)
cloneCSSStyle(nativeNode, clonedNode, options)
clonePseudoElements(nativeNode, clonedNode, options)
cloneInputValue(nativeNode, clonedNode)
cloneSelectValue(nativeNode, clonedNode)
}
Expand Down Expand Up @@ -240,6 +253,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))
.then((clonedNode) => ensureSVGSymbols(clonedNode, options))
}
20 changes: 13 additions & 7 deletions src/clone-pseudos.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { uuid, toArray } from './util'
import type { Options } from './types'
import { uuid, getStyleProperties } from './util'

type Pseudo = ':before' | ':after'

Expand All @@ -7,8 +8,8 @@ function formatCSSText(style: CSSStyleDeclaration) {
return `${style.cssText} content: '${content.replace(/'|"/g, '')}';`
}

function formatCSSProperties(style: CSSStyleDeclaration) {
return toArray<string>(style)
function formatCSSProperties(style: CSSStyleDeclaration, options: Options) {
return getStyleProperties(options)
.map((name) => {
const value = style.getPropertyValue(name)
const priority = style.getPropertyPriority(name)
Expand All @@ -22,11 +23,12 @@ function getPseudoElementStyle(
className: string,
pseudo: Pseudo,
style: CSSStyleDeclaration,
options: Options,
): Text {
const selector = `.${className}:${pseudo}`
const cssText = style.cssText
? formatCSSText(style)
: formatCSSProperties(style)
: formatCSSProperties(style, options)

return document.createTextNode(`${selector}{${cssText}}`)
}
Expand All @@ -35,6 +37,7 @@ function clonePseudoElement<T extends HTMLElement>(
nativeNode: T,
clonedNode: T,
pseudo: Pseudo,
options: Options,
) {
const style = window.getComputedStyle(nativeNode, pseudo)
const content = style.getPropertyValue('content')
Expand All @@ -50,14 +53,17 @@ function clonePseudoElement<T extends HTMLElement>(
}

const styleElement = document.createElement('style')
styleElement.appendChild(getPseudoElementStyle(className, pseudo, style))
styleElement.appendChild(
getPseudoElementStyle(className, pseudo, style, options),
)
clonedNode.appendChild(styleElement)
}

export function clonePseudoElements<T extends HTMLElement>(
nativeNode: T,
clonedNode: T,
options: Options,
) {
clonePseudoElement(nativeNode, clonedNode, ':before')
clonePseudoElement(nativeNode, clonedNode, ':after')
clonePseudoElement(nativeNode, clonedNode, ':before', options)
clonePseudoElement(nativeNode, clonedNode, ':after', options)
}
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export interface Options {
* An object whose properties to be copied to node's style before rendering.
*/
style?: Partial<CSSStyleDeclaration>
/**
* An array of style properties to be copied to node's style before rendering.
* For performance-critical scenarios, users may want to specify only the
* required properties instead of all styles.
*/
includeStyleProperties?: string[]
/**
* A function taking DOM node as argument. Should return `true` if passed
* node should be included in the output. Excluding node means excluding
Expand Down
16 changes: 16 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,22 @@ export function toArray<T>(arrayLike: any): T[] {
return arr
}

let styleProps: string[] | null = null
export function getStyleProperties(options: Options = {}): string[] {
if (styleProps) {
return styleProps
}

if (options.includeStyleProperties) {
styleProps = options.includeStyleProperties
return styleProps
}

styleProps = toArray(window.getComputedStyle(document.documentElement))

return styleProps
}

function px(node: HTMLElement, styleProperty: string) {
const win = node.ownerDocument.defaultView || window
const val = win.getComputedStyle(node).getPropertyValue(styleProperty)
Expand Down
1 change: 1 addition & 0 deletions test/resources/style/image-include-style
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAXJJREFUeF7t3LENwDAMxEBp/6FlJFNcQU9AkHio897MTY8xsAVhWvwgBbF6FATrUZCCaAYwnm5IQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcnbl+lIOiFASK8aEUpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgPsePHnRi+YEEAAAAASUVORK5CYII=
12 changes: 12 additions & 0 deletions test/spec/options.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ describe('work with options', () => {
.catch(done)
})

it('should only clone specified style properties when includeStyleProperties is provided', (done) => {
bootstrap('style/node.html', 'style/style.css', 'style/image-include-style')
.then((node) => {
return toPng(node, {
includeStyleProperties: ['width', 'height'],
})
})
.then(check)
.then(done)
.catch(done)
})

it('should combine dimensions and style', (done) => {
bootstrap('scale/node.html', 'scale/style.css', 'scale/image')
.then((node) => {
Expand Down

0 comments on commit 27cd25b

Please sign in to comment.