Skip to content

Commit

Permalink
Merge branch 'feature/HTC-0010_PseudoContent' of git://github.com/vnm…
Browse files Browse the repository at this point in the history
…c/html2canvas into vnmc-feature/HTC-0010_PseudoContent
  • Loading branch information
niklasvh committed Dec 24, 2017
2 parents 3881e3c + 6d0cd2d commit afa5d7c
Show file tree
Hide file tree
Showing 6 changed files with 616 additions and 26 deletions.
2 changes: 1 addition & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Below is a list of all the supported CSS properties and values.
- border-width
- bottom
- box-sizing
- content (**Does not support `attr()`**)
- content
- color
- display
- flex
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"homepage": "https://html2canvas.hertzen.com",
"license": "MIT",
"dependencies": {
"punycode": "2.1.0"
"punycode": "2.1.0",
"liststyletype-formatter": "latest"
}
}
83 changes: 59 additions & 24 deletions src/Clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
'use strict';
import type {Bounds} from './Bounds';
import type {Options} from './index';
import type {PseudoContentData, PseudoContentItem} from './PseudoNodeContent';
import type Logger from './Logger';

import {parseBounds} from './Bounds';
Expand All @@ -10,6 +11,12 @@ import ResourceLoader from './ResourceLoader';
import {copyCSSStyles} from './Util';
import {parseBackgroundImage} from './parsing/background';
import CanvasRenderer from './renderer/CanvasRenderer';
import {
parseCounterReset,
popCounters,
resolvePseudoContent,
PSEUDO_CONTENT_ITEM_TYPE
} from './PseudoNodeContent';

const IGNORE_ATTRIBUTE = 'data-html2canvas-ignore';

Expand All @@ -24,6 +31,7 @@ export class DocumentCloner {
inlineImages: boolean;
copyStyles: boolean;
renderer: (element: HTMLElement, options: Options, logger: Logger) => Promise<*>;
pseudoContentData: PseudoContentData;

constructor(
element: HTMLElement,
Expand All @@ -40,6 +48,10 @@ export class DocumentCloner {
this.options = options;
this.renderer = renderer;
this.resourceLoader = new ResourceLoader(options, logger, window);
this.pseudoContentData = {
counters: {},
quoteDepth: 0
};
// $FlowFixMe
this.documentElement = this.cloneNode(element.ownerDocument.documentElement);
}
Expand Down Expand Up @@ -227,6 +239,11 @@ export class DocumentCloner {
: this.createElementClone(node);

const window = node.ownerDocument.defaultView;
const style = node instanceof window.HTMLElement ? window.getComputedStyle(node) : null;
const styleBefore =
node instanceof window.HTMLElement ? window.getComputedStyle(node, ':before') : null;
const styleAfter =
node instanceof window.HTMLElement ? window.getComputedStyle(node, ':after') : null;

if (this.referenceElement === node && clone instanceof window.HTMLElement) {
this.clonedReferenceElement = clone;
Expand All @@ -236,6 +253,9 @@ export class DocumentCloner {
createPseudoHideStyles(clone);
}

const counters = parseCounterReset(style, this.pseudoContentData);
const contentBefore = resolvePseudoContent(node, styleBefore, this.pseudoContentData);

for (let child = node.firstChild; child; child = child.nextSibling) {
if (
child.nodeType !== Node.ELEMENT_NODE ||
Expand All @@ -247,11 +267,23 @@ export class DocumentCloner {
}
}
}

const contentAfter = resolvePseudoContent(node, styleAfter, this.pseudoContentData);
popCounters(counters, this.pseudoContentData);

if (node instanceof window.HTMLElement && clone instanceof window.HTMLElement) {
this.inlineAllImages(inlinePseudoElement(node, clone, PSEUDO_BEFORE));
this.inlineAllImages(inlinePseudoElement(node, clone, PSEUDO_AFTER));
if (this.copyStyles && !(node instanceof HTMLIFrameElement)) {
copyCSSStyles(node.ownerDocument.defaultView.getComputedStyle(node), clone);
if (styleBefore) {
this.inlineAllImages(
inlinePseudoElement(node, clone, styleBefore, contentBefore, PSEUDO_BEFORE)
);
}
if (styleAfter) {
this.inlineAllImages(
inlinePseudoElement(node, clone, styleAfter, contentAfter, PSEUDO_AFTER)
);
}
if (style && this.copyStyles && !(node instanceof HTMLIFrameElement)) {
copyCSSStyles(style, clone);
}
this.inlineAllImages(clone);
if (node.scrollTop !== 0 || node.scrollLeft !== 0) {
Expand Down Expand Up @@ -362,9 +394,10 @@ const cloneCanvasContents = (canvas: HTMLCanvasElement, clonedCanvas: HTMLCanvas
const inlinePseudoElement = (
node: HTMLElement,
clone: HTMLElement,
style: CSSStyleDeclaration,
contentItems: ?Array<PseudoContentItem>,
pseudoElt: ':before' | ':after'
): ?HTMLElement => {
const style = node.ownerDocument.defaultView.getComputedStyle(node, pseudoElt);
if (
!style ||
!style.content ||
Expand All @@ -375,20 +408,29 @@ const inlinePseudoElement = (
return;
}

const content = stripQuotes(style.content);
const image = content.match(URL_REGEXP);
const anonymousReplacedElement = clone.ownerDocument.createElement(
image ? 'img' : 'html2canvaspseudoelement'
);
if (image) {
// $FlowFixMe
anonymousReplacedElement.src = stripQuotes(image[1]);
} else {
anonymousReplacedElement.textContent = content;
}

const anonymousReplacedElement = clone.ownerDocument.createElement('html2canvaspseudoelement');
copyCSSStyles(style, anonymousReplacedElement);

if (contentItems) {
const len = contentItems.length;
for (var i = 0; i < len; i++) {
const item = contentItems[i];
switch (item.type) {
case PSEUDO_CONTENT_ITEM_TYPE.IMAGE:
const img = clone.ownerDocument.createElement('img');
img.src = parseBackgroundImage(`url(${item.value})`)[0].args[0];
img.style.opacity = '1';
anonymousReplacedElement.appendChild(img);
break;
case PSEUDO_CONTENT_ITEM_TYPE.TEXT:
anonymousReplacedElement.appendChild(
clone.ownerDocument.createTextNode(item.value)
);
break;
}
}
}

anonymousReplacedElement.className = `${PSEUDO_HIDE_ELEMENT_CLASS_BEFORE} ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`;
clone.className +=
pseudoElt === PSEUDO_BEFORE
Expand All @@ -403,13 +445,6 @@ const inlinePseudoElement = (
return anonymousReplacedElement;
};

const stripQuotes = (content: string): string => {
const first = content.substr(0, 1);
return first === content.substr(content.length - 1) && first.match(/['"]/)
? content.substr(1, content.length - 2)
: content;
};

const URL_REGEXP = /^url\((.+)\)$/i;
const PSEUDO_BEFORE = ':before';
const PSEUDO_AFTER = ':after';
Expand Down
Loading

0 comments on commit afa5d7c

Please sign in to comment.