Compare commits

...

7 Commits

15 changed files with 404 additions and 100 deletions

View File

@ -2,6 +2,26 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.1.3](https://github.com/niklasvh/html2canvas/compare/v1.1.2...v1.1.3) (2021-07-14)
### feat
* allow access to reference element in onclone (#2584) ([58b4591](https://github.com/niklasvh/html2canvas/commit/58b45911741c0dbbccd462b2976560bb3999eaef)), closes [#2584](https://github.com/niklasvh/html2canvas/issues/2584)
* support for custom and slot elements (#2581) ([acb4cd2](https://github.com/niklasvh/html2canvas/commit/acb4cd24b85527908c02a60794768949578678f0)), closes [#2581](https://github.com/niklasvh/html2canvas/issues/2581)
### fix
* iframe load to ensure images are loaded (#2577) ([eeb5a3e](https://github.com/niklasvh/html2canvas/commit/eeb5a3ea1d6c94e0f6dcfd40695eb88ebb3e0041)), closes [#2577](https://github.com/niklasvh/html2canvas/issues/2577)
* image blob rendering ([1acdc82](https://github.com/niklasvh/html2canvas/commit/1acdc827a4e05933c2f7c9558405c66b7cd82f58))
* responsive svg images (#2583) ([92fa448](https://github.com/niklasvh/html2canvas/commit/92fa448913192d5e4e82bfe14f6644b669d4e6ef)), closes [#2583](https://github.com/niklasvh/html2canvas/issues/2583)
### test
* add test cases for text-stroke and textarea from (#1540 and #2132) (#2585) ([1d00bfe](https://github.com/niklasvh/html2canvas/commit/1d00bfe175d51e663d0bae88b6dbd10a266a71f1)), closes [#1540](https://github.com/niklasvh/html2canvas/issues/1540) [#2132](https://github.com/niklasvh/html2canvas/issues/2132) [#2585](https://github.com/niklasvh/html2canvas/issues/2585)
## [1.1.2](https://github.com/niklasvh/html2canvas/compare/v1.1.1...v1.1.2) (2021-07-13)

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "html2canvas",
"version": "1.1.2",
"version": "1.1.3",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -6,7 +6,7 @@
"module": "dist/html2canvas.esm.js",
"typings": "dist/types/index.d.ts",
"browser": "dist/html2canvas.js",
"version": "1.1.2",
"version": "1.1.3",
"author": {
"name": "Niklas von Hertzen",
"email": "niklasvh@gmail.com",

View File

@ -108,10 +108,18 @@ export class Cache {
const useProxy =
!isInlineImage(key) &&
!isSameOrigin &&
!isBlobImage(key) &&
typeof this._options.proxy === 'string' &&
FEATURES.SUPPORT_CORS_XHR &&
!useCORS;
if (!isSameOrigin && this._options.allowTaint === false && !isInlineImage(key) && !useProxy && !useCORS) {
if (
!isSameOrigin &&
this._options.allowTaint === false &&
!isInlineImage(key) &&
!isBlobImage(key) &&
!useProxy &&
!useCORS
) {
return;
}

View File

@ -24,7 +24,7 @@ import {getQuote} from '../css/property-descriptors/quotes';
export interface CloneOptions {
id: string;
ignoreElements?: (element: Element) => boolean;
onclone?: (document: Document) => void;
onclone?: (document: Document, element: HTMLElement) => void;
}
export type CloneConfigurations = CloneOptions & {
@ -89,7 +89,9 @@ export class DocumentCloner {
const onclone = this.options.onclone;
if (typeof this.clonedReferenceElement === 'undefined') {
const referenceElement = this.clonedReferenceElement;
if (typeof referenceElement === 'undefined') {
return Promise.reject(`Error finding the ${this.referenceElement.nodeName} in the cloned document`);
}
@ -97,9 +99,13 @@ export class DocumentCloner {
await documentClone.fonts.ready;
}
if (/(AppleWebKit)/g.test(navigator.userAgent)) {
await imagesReady(documentClone);
}
if (typeof onclone === 'function') {
return Promise.resolve()
.then(() => onclone(documentClone))
.then(() => onclone(documentClone, referenceElement))
.then(() => iframe);
}
@ -120,11 +126,7 @@ export class DocumentCloner {
if (isCanvasElement(node)) {
return this.createCanvasClone(node);
}
/*
if (isIFrameElement(node)) {
return this.createIFrameClone(node);
}
*/
if (isStyleElement(node)) {
return this.createStyleClone(node);
}
@ -191,67 +193,7 @@ export class DocumentCloner {
return clonedCanvas;
}
/*
createIFrameClone(iframe: HTMLIFrameElement) {
const tempIframe = <HTMLIFrameElement>iframe.cloneNode(false);
const iframeKey = generateIframeKey();
tempIframe.setAttribute('data-html2canvas-internal-iframe-key', iframeKey);
const {width, height} = parseBounds(iframe);
this.resourceLoader.cache[iframeKey] = getIframeDocumentElement(iframe, this.options)
.then(documentElement => {
return this.renderer(
documentElement,
{
allowTaint: this.options.allowTaint,
backgroundColor: '#ffffff',
canvas: null,
imageTimeout: this.options.imageTimeout,
logging: this.options.logging,
proxy: this.options.proxy,
removeContainer: this.options.removeContainer,
scale: this.options.scale,
foreignObjectRendering: this.options.foreignObjectRendering,
useCORS: this.options.useCORS,
target: new CanvasRenderer(),
width,
height,
x: 0,
y: 0,
windowWidth: documentElement.ownerDocument.defaultView.innerWidth,
windowHeight: documentElement.ownerDocument.defaultView.innerHeight,
scrollX: documentElement.ownerDocument.defaultView.pageXOffset,
scrollY: documentElement.ownerDocument.defaultView.pageYOffset
},
);
})
.then(
(canvas: HTMLCanvasElement) =>
new Promise((resolve, reject) => {
const iframeCanvas = document.createElement('img');
iframeCanvas.onload = () => resolve(canvas);
iframeCanvas.onerror = (event) => {
// Empty iframes may result in empty "data:," URLs, which are invalid from the <img>'s point of view
// and instead of `onload` cause `onerror` and unhandled rejection warnings
// https://github.com/niklasvh/html2canvas/issues/1502
iframeCanvas.src == 'data:,' ? resolve(canvas) : reject(event);
};
iframeCanvas.src = canvas.toDataURL();
if (tempIframe.parentNode && iframe.ownerDocument && iframe.ownerDocument.defaultView) {
tempIframe.parentNode.replaceChild(
copyCSSStyles(
iframe.ownerDocument.defaultView.getComputedStyle(iframe),
iframeCanvas
),
tempIframe
);
}
})
);
return tempIframe;
}
*/
cloneNode(node: Node): Node {
if (isTextNode(node)) {
return document.createTextNode(node.data);
@ -308,8 +250,6 @@ export class DocumentCloner {
copyCSSStyles(style, clone);
}
//this.inlineAllImages(clone);
if (node.scrollTop !== 0 || node.scrollLeft !== 0) {
this.scrolledElements.push([clone, node.scrollLeft, node.scrollTop]);
}
@ -462,6 +402,25 @@ const createIFrameContainer = (ownerDocument: Document, bounds: Bounds): HTMLIFr
return cloneIframeContainer;
};
const imageReady = (img: HTMLImageElement): Promise<Event | void | string> => {
return new Promise((resolve) => {
if (img.complete) {
resolve();
return;
}
if (!img.src) {
resolve();
return;
}
img.onload = resolve;
img.onerror = resolve;
});
};
const imagesReady = (document: HTMLDocument): Promise<unknown[]> => {
return Promise.all([].slice.call(document.images, 0).map(imageReady));
};
const iframeLoader = (iframe: HTMLIFrameElement): Promise<HTMLIFrameElement> => {
return new Promise((resolve, reject) => {
const cloneWindow = iframe.contentWindow;
@ -472,18 +431,15 @@ const iframeLoader = (iframe: HTMLIFrameElement): Promise<HTMLIFrameElement> =>
const documentClone = cloneWindow.document;
cloneWindow.onload =
iframe.onload =
documentClone.onreadystatechange =
() => {
cloneWindow.onload = iframe.onload = documentClone.onreadystatechange = null;
const interval = setInterval(() => {
if (documentClone.body.childNodes.length > 0 && documentClone.readyState === 'complete') {
clearInterval(interval);
resolve(iframe);
}
}, 50);
};
cloneWindow.onload = iframe.onload = () => {
cloneWindow.onload = iframe.onload = null;
const interval = setInterval(() => {
if (documentClone.body.childNodes.length > 0 && documentClone.readyState === 'complete') {
clearInterval(interval);
resolve(iframe);
}
}, 50);
};
});
};

View File

@ -20,21 +20,32 @@ const parseNodeTree = (node: Node, parent: ElementContainer, root: ElementContai
if (isTextNode(childNode) && childNode.data.trim().length > 0) {
parent.textNodes.push(new TextContainer(childNode, parent.styles));
} else if (isElementNode(childNode)) {
const container = createContainer(childNode);
if (container.styles.isVisible()) {
if (createsRealStackingContext(childNode, container, root)) {
container.flags |= FLAGS.CREATES_REAL_STACKING_CONTEXT;
} else if (createsStackingContext(container.styles)) {
container.flags |= FLAGS.CREATES_STACKING_CONTEXT;
}
if (isSlotElement(childNode) && childNode.assignedNodes) {
childNode.assignedNodes().forEach((childNode) => parseNodeTree(childNode, parent, root));
} else {
const container = createContainer(childNode);
if (container.styles.isVisible()) {
if (createsRealStackingContext(childNode, container, root)) {
container.flags |= FLAGS.CREATES_REAL_STACKING_CONTEXT;
} else if (createsStackingContext(container.styles)) {
container.flags |= FLAGS.CREATES_STACKING_CONTEXT;
}
if (LIST_OWNERS.indexOf(childNode.tagName) !== -1) {
container.flags |= FLAGS.IS_LIST_OWNER;
}
if (LIST_OWNERS.indexOf(childNode.tagName) !== -1) {
container.flags |= FLAGS.IS_LIST_OWNER;
}
parent.elements.push(container);
if (!isTextareaElement(childNode) && !isSVGElement(childNode) && !isSelectElement(childNode)) {
parseNodeTree(childNode, container, root);
parent.elements.push(container);
childNode.slot;
if (childNode.shadowRoot) {
parseNodeTree(childNode.shadowRoot, container, root);
} else if (
!isTextareaElement(childNode) &&
!isSVGElement(childNode) &&
!isSelectElement(childNode)
) {
parseNodeTree(childNode, container, root);
}
}
}
}
@ -118,3 +129,4 @@ export const isStyleElement = (node: Element): node is HTMLStyleElement => node.
export const isScriptElement = (node: Element): node is HTMLScriptElement => node.tagName === 'SCRIPT';
export const isTextareaElement = (node: Element): node is HTMLTextAreaElement => node.tagName === 'TEXTAREA';
export const isSelectElement = (node: Element): node is HTMLSelectElement => node.tagName === 'SELECT';
export const isSlotElement = (node: Element): node is HTMLSlotElement => node.tagName === 'SLOT';

View File

@ -1,5 +1,6 @@
import {ElementContainer} from '../element-container';
import {CacheStorage} from '../../core/cache-storage';
import {parseBounds} from '../../css/layout/bounds';
export class SVGElementContainer extends ElementContainer {
svg: string;
@ -9,6 +10,10 @@ export class SVGElementContainer extends ElementContainer {
constructor(img: SVGSVGElement) {
super(img);
const s = new XMLSerializer();
const bounds = parseBounds(img);
img.setAttribute('width', `${bounds.width}px`);
img.setAttribute('height', `${bounds.height}px`);
this.svg = `data:image/svg+xml,${encodeURIComponent(s.serializeToString(img))}`;
this.intrinsicWidth = img.width.baseVal.value;
this.intrinsicHeight = img.height.baseVal.value;

View File

@ -32,7 +32,7 @@ if (typeof window !== 'undefined') {
let instanceCount = 1;
const renderElement = async (element: HTMLElement, opts: Partial<Options>): Promise<HTMLCanvasElement> => {
if (typeof element !== 'object') {
if (!element || typeof element !== 'object') {
return Promise.reject('Invalid element provided as first argument');
}
const ownerDocument = element.ownerDocument;

View File

@ -24,5 +24,19 @@
<img src="../../assets/image.jpg" style="width:0px;height:0px;border:1px solid black" />
<img src="../../assets/image.jpg" style="width:0px;height:0px;" />
<script>
const base64 = "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";
const bytes = atob(base64);
const uInt8Array = new Uint8Array(bytes.length);
for (let i = 0; i < bytes.length; ++i) {
uInt8Array[i] = bytes.charCodeAt(i);
}
const blob = new Blob([uInt8Array], { type: "image/jpeg" });
const img = document.createElement("img");
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
</script>
</body>
</html>

View File

@ -34,7 +34,7 @@
style="fill:#40aa54;fill-opacity:1;stroke:#20552a;stroke-width:7.99999952;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
</g>
</svg>
<svg width="240" height="160" viewBox="0 0 120 80" xmlns="http://www.w3.org/2000/svg">
<svg width="50%" height="75%" viewBox="0 0 120 80" xmlns="http://www.w3.org/2000/svg">
<text x="40" y="20" class="html">html</text>
<text x="55" y="25" class="two">2</text>
<text x="65" y="35" class="canvas">canvas</text>

View File

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Text stroke tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
div span:first-child {
font-size: 2em;
}
div span:nth-child(2) {
font-size: 5em;
}
.stroke1 {
-webkit-text-stroke-width: .09em;
-webkit-text-stroke-color: red;
font-size: 1em;
}
.stroke2 {
-webkit-text-stroke-width: .12em;
-webkit-text-stroke-color: green;
font-size: 2em;
}
.stroke3 {
-webkit-text-stroke-width: .19em;
-webkit-text-stroke-color: blue;
font-size: 3em;
}
.ordered {
paint-order: stroke fill;
}
body {
font-family: Arial;
}
</style>
</head>
<body>
<div class="stroke1">
Some text <span> with bigger text </span> that should have a stroke
<strong>Bolder stroke</strong> that makes things pretty
</div>
<div class="stroke2">
Some text <span> with bigger text </span> that should have a stroke
<strong>Bolder stroke</strong> that makes things pretty
</div>
<div class="stroke3">
Some text <span> with bigger text </span> that should have a stroke
<strong>Bolder stroke</strong> that makes things pretty
</div>
<div class="stroke1 ordered">
Some text <span> with bigger text </span> that should have a stroke
<strong>Bolder stroke</strong> that makes things pretty
</div>
<div class="stroke2 ordered">
Some text <span> with bigger text </span> that should have a stroke
<strong>Bolder stroke</strong> that makes things pretty
</div>
<div class="stroke3 ordered">
Some text <span> with bigger text </span> that should have a stroke
<strong>Bolder stroke</strong> that makes things pretty
</div>
<div class="stroke4">
Some text <span> with bigger text </span> that should have no stroke
<strong>Bolder text</strong> that makes things pretty
</div>
</body>
</html>

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<title>textarea</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
textarea{
font-family: Arial;
font-size: 14px;
width: 10em;
height: 90px;
}
div {
display: inline-block;
}
</style>
</head>
<body>
<div>
<p>1. word wrap</p>
<textarea> A long text with many words that should be wrapped on various lines.
</textarea>
<p>2. padding</p>
<textarea style="padding: 1em">A long text with many words that should be wrapped on various lines.
</textarea>
<p>3. line height</p>
<textarea style="line-height: 25px">A long text with many words.
</textarea>
<p>4. letter spacing</p>
<textarea style="letter-spacing: 5px"> A long text with many words that should be wrapped on various lines.</textarea>
</div>
<div>
<p>5. multiple spaces</p>
<textarea>A long text with many words that should be wrapped on various lines.
</textarea>
<p>6. newlines</p>
<textarea>A long text with many
words that
should be wrapped on various lines.
</textarea>
<p>7. long word</p>
<textarea>Donaudampfschifffahrtsgesellschaftskapitänskochmütze</textarea>
<p>8. hyphen</p>
<textarea>A text with many-manymany manys.</textarea>
</div>
<div>
<p>9. kerning</p>
<textarea>AVAVAVAVAVAVAVAV TeTeTeTeTeTeTeTeTe YaYaYaYaYaYaYaYaY</textarea>
<p>10. kerning with letter spacing</p>
<textarea style="letter-spacing: 1px">AVAVAVAVAVAVAV TeTeTeTeTeTeTeTe YaYaYaYaYaYaYaY</textarea>
<p>11. kerning with letter spacing</p>
<textarea style="letter-spacing: 5px">AVAVAVAVAV TeTeTeTeTeT YaYaYaYaYa</textarea>
</div>
</body>
</html>

View File

@ -0,0 +1,45 @@
class AutonomousCustomElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('span');
wrapper.setAttribute('class', 'wrapper');
const info = document.createElement('span');
info.setAttribute('class', 'info');
info.textContent = this.getAttribute('text');
const img = document.createElement('img');
img.src = this.getAttribute('img');
// Create some CSS to apply to the shadow dom
const style = document.createElement('style');
style.textContent = `
.wrapper {
position: relative;
}
.info {
font-size: 0.8rem;
width: 200px;
display: inline-block;
border: 1px solid black;
padding: 10px;
background: white;
border-radius: 10px;
}
img {
width: 100px;
}
`;
shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(img);
wrapper.appendChild(info);
}
}
customElements.define('autonomous-custom-element', AutonomousCustomElement);

View File

@ -0,0 +1,51 @@
customElements.define('summary-display',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('summary-display-template');
const templateContent = template.content;
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(templateContent.cloneNode(true));
const items = Array.from(this.querySelectorAll('li'));
const descriptions = Array.from(this.querySelectorAll('p'));
items.forEach(item => {
handleClick(item);
});
descriptions.forEach(description => {
updateDisplay(description, items[1]);
});
function handleClick(item) {
item.addEventListener('click', function() {
items.forEach(item => {
item.style.backgroundColor = 'white';
});
descriptions.forEach(description => {
updateDisplay(description, item);
});
});
}
function updateDisplay(description, item) {
description.removeAttribute('slot');
if(description.getAttribute('data-name') === item.textContent) {
description.setAttribute('slot', 'choice');
item.style.backgroundColor = '#bad0e4';
}
}
const slots = this.shadowRoot.querySelectorAll('slot');
slots[1].addEventListener('slotchange', function(e) {
const nodes = slots[1].assignedNodes();
console.log(`Element in Slot "${slots[1].name}" changed to "${nodes[0].outerHTML}".`);
});
}
}
);

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<title>Web components tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
</style>
</head>
<body>
<div>
<h1>Autonomous custom element</h1>
<autonomous-custom-element img="../../assets/image.jpg" text="This is text"></autonomous-custom-element>
<h1>Slot element</h1>
<summary-display>
<ul slot="master-list">
<li>Apples</li>
<li>Pears</li>
<li>Bananas</li>
<li>Oranges</li>
<li>Peaches</li>
<li>Strawberries</li>
<li>Blueberries</li>
</ul>
<p data-name="Apples">A common, sweet, crunchy fruit, usually green or yellow in color.</p>
<p data-name="Pears">A fairly common, sweet, usually green fruit, usually softer than Apples.</p>
<p data-name="Bananas">A long, curved, yellow fruit, with a fairly gentle flavor.</p>
<p data-name="Oranges">Orange in color, usually sweet but can be sharp, often contains pips.</p>
<p data-name="Peaches">An orange fruit with big stone in the middle, and sweet, juicy flesh.</p>
<p data-name="Strawberries">A red fruit with yellow seeds on the outside; has a sweet flavor and a pretty shape.</p>
<p data-name="Blueberries">They are berries and they are blue; sweet in flavor, small in size, round.</p>
</summary-display>
<template id="summary-display-template">
<article>
<div>
<slot name="master-list"></slot>
</div>
<div>
<slot name="choice"></slot>
</div>
</article>
</template>
<script src="autonomous-custom-element.js"></script>
<script src="slot-element.js"></script>
</div>
</body>
</html>