Compare commits

..

2 Commits

Author SHA1 Message Date
7b7cbc6672 bump deps 2021-08-10 17:58:56 +08:00
9629afd7d6 feat: correctly split graphemes 2021-08-10 01:01:25 +08:00
13 changed files with 109 additions and 84 deletions

View File

@ -117,6 +117,9 @@ jobs:
name: iOS Simulator Safari 15
targetBrowser: Safari_IOS_15
xcode: /Applications/Xcode_13.0.app
- os: windows-latest
name: Windows Internet Explorer 9 (Emulated)
targetBrowser: IE_9
- os: windows-latest
name: Windows Internet Explorer 10 (Emulated)
targetBrowser: IE_10

View File

@ -3,7 +3,7 @@ html2canvas
[Homepage](https://html2canvas.hertzen.com) | [Downloads](https://github.com/niklasvh/html2canvas/releases) | [Questions](https://github.com/niklasvh/html2canvas/discussions/categories/q-a)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
![CI](https://github.com/niklasvh/html2canvas/workflows/CI/badge.svg?branch=master)
[![NPM Downloads](https://img.shields.io/npm/dm/html2canvas.svg)](https://www.npmjs.org/package/html2canvas)
[![NPM Version](https://img.shields.io/npm/v/html2canvas.svg)](https://www.npmjs.org/package/html2canvas)
@ -28,8 +28,7 @@ The library should work fine on the following browsers (with `Promise` polyfill)
* Firefox 3.5+
* Google Chrome
* Opera 12+
* IE10+
* Edge
* IE9+
* Safari 6+
As each CSS property needs to be manually built to be supported, there are a number of properties that are not yet supported.

View File

@ -5,28 +5,28 @@ nextUrl: "/getting-started"
nextTitle: "Getting Started"
---
Before you get started with the script, there are a few things that are good to know regarding the
Before you get started with the script, there are a few things that are good to know regarding the
script and some of its limitations.
## Introduction
The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser.
The screenshot is based on the DOM and as such may not be 100% accurate to the real representation
as it does not make an actual screenshot, but builds the screenshot based on the information
The screenshot is based on the DOM and as such may not be 100% accurate to the real representation
as it does not make an actual screenshot, but builds the screenshot based on the information
available on the page.
## How it works
The script traverses through the DOM of the page it is loaded on. It gathers information on all the elements
there, which it then uses to build a representation of the page. In other words, it does not actually take a
screenshot of the page, but builds a representation of it based on the properties it reads from the DOM.
As a result, it is only able to render correctly properties that it understands, meaning there are many
CSS properties which do not work. For a full list of supported CSS properties, check out the
As a result, it is only able to render correctly properties that it understands, meaning there are many
CSS properties which do not work. For a full list of supported CSS properties, check out the
[supported features](/features/) page.
## Limitations
All the images that the script uses need to reside under the [same origin](http://en.wikipedia.org/wiki/Same_origin_policy)
for it to be able to read them without the assistance of a [proxy](/proxy/). Similarly, if you have other `canvas`
All the images that the script uses need to reside under the [same origin](http://en.wikipedia.org/wiki/Same_origin_policy)
for it to be able to read them without the assistance of a [proxy](/proxy/). Similarly, if you have other `canvas`
elements on the page, which have been tainted with cross-origin content, they will become dirty and no longer readable by html2canvas.
The script doesn't render plugin content such as Flash or Java applets.
@ -37,6 +37,6 @@ The library should work fine on the following browsers (with `Promise` polyfill)
- Firefox 3.5+
- Google Chrome
- Opera 12+
- IE10+
- IE9+
- Edge
- Safari 6+

View File

@ -48,6 +48,12 @@ module.exports = function(config) {
platform: 'iOS',
sdk: '15.0'
},
SauceLabs_IE9: {
base: 'SauceLabs',
browserName: 'internet explorer',
version: '9.0',
platform: 'Windows 7'
},
SauceLabs_IE10: {
base: 'SauceLabs',
browserName: 'internet explorer',
@ -87,6 +93,11 @@ module.exports = function(config) {
version: '9.3',
device: 'iPhone 6 Plus Simulator'
},
IE_9: {
base: 'IE',
'x-ua-compatible': 'IE=EmulateIE9',
flags: ['-extoff']
},
IE_10: {
base: 'IE',
'x-ua-compatible': 'IE=EmulateIE10',

66
package-lock.json generated
View File

@ -5,10 +5,11 @@
"requires": true,
"packages": {
"": {
"version": "1.1.5",
"version": "1.2.1",
"license": "MIT",
"dependencies": {
"css-line-break": "2.0.1"
"css-line-break": "2.0.1",
"text-segmentation": "^1.0.2"
},
"devDependencies": {
"@babel/cli": "^7.4.3",
@ -36,7 +37,7 @@
"babel-loader": "^8.0.5",
"babel-plugin-add-module-exports": "^1.0.2",
"babel-plugin-dev-expression": "^0.2.1",
"base64-arraybuffer": "0.2.0",
"base64-arraybuffer": "1.0.1",
"body-parser": "^1.19.0",
"chai": "4.1.1",
"chromeless": "^1.5.2",
@ -5784,9 +5785,9 @@
}
},
"node_modules/base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
"integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==",
"engines": {
"node": ">= 0.6.0"
}
@ -8637,6 +8638,14 @@
"base64-arraybuffer": "^0.2.0"
}
},
"node_modules/css-line-break/node_modules/base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/cssom": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
@ -23441,6 +23450,14 @@
"node": ">=0.10"
}
},
"node_modules/text-segmentation": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.2.tgz",
"integrity": "sha512-uTqvLxdBrVnx/CFQOtnf8tfzSXFm+1Qxau7Xi54j4OPTZokuDOX8qncQzrg2G8ZicAMOM8TgzFAYTb+AqNO4Cw==",
"dependencies": {
"utrie": "^1.0.1"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -24517,6 +24534,14 @@
"node": ">= 0.4.0"
}
},
"node_modules/utrie": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.1.tgz",
"integrity": "sha512-JPaDXF3vzgZxfeEwutdGzlrNoVFL5UvZcbO6Qo9D4GoahrieUPoMU8GCpVpR7MQqcKhmShIh8VlbEN3PLM3EBg==",
"dependencies": {
"base64-arraybuffer": "^1.0.1"
}
},
"node_modules/uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
@ -30457,9 +30482,9 @@
}
},
"base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
"integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA=="
},
"base64-js": {
"version": "1.3.0",
@ -32816,6 +32841,13 @@
"integrity": "sha512-gwKYIMUn7xodIcb346wgUhE2Dt5O1Kmrc16PWi8sL4FTfyDj8P5095rzH7+O8CTZudJr+uw2GCI/hwEkDJFI2w==",
"requires": {
"base64-arraybuffer": "^0.2.0"
},
"dependencies": {
"base64-arraybuffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
"integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ=="
}
}
},
"cssom": {
@ -44509,6 +44541,14 @@
"integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
"dev": true
},
"text-segmentation": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.2.tgz",
"integrity": "sha512-uTqvLxdBrVnx/CFQOtnf8tfzSXFm+1Qxau7Xi54j4OPTZokuDOX8qncQzrg2G8ZicAMOM8TgzFAYTb+AqNO4Cw==",
"requires": {
"utrie": "^1.0.1"
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -45332,6 +45372,14 @@
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
"dev": true
},
"utrie": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.1.tgz",
"integrity": "sha512-JPaDXF3vzgZxfeEwutdGzlrNoVFL5UvZcbO6Qo9D4GoahrieUPoMU8GCpVpR7MQqcKhmShIh8VlbEN3PLM3EBg==",
"requires": {
"base64-arraybuffer": "^1.0.1"
}
},
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",

View File

@ -48,7 +48,7 @@
"babel-loader": "^8.0.5",
"babel-plugin-add-module-exports": "^1.0.2",
"babel-plugin-dev-expression": "^0.2.1",
"base64-arraybuffer": "0.2.0",
"base64-arraybuffer": "1.0.1",
"body-parser": "^1.19.0",
"chai": "4.1.1",
"chromeless": "^1.5.2",
@ -118,6 +118,7 @@
"homepage": "https://html2canvas.hertzen.com",
"license": "MIT",
"dependencies": {
"css-line-break": "2.0.1"
"css-line-break": "2.0.1",
"text-segmentation": "^1.0.2"
}
}

View File

@ -12,6 +12,7 @@ export class CacheStorage {
}
link.href = url;
link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
return link.protocol + link.hostname + link.port;
}

View File

@ -1,4 +1,4 @@
import {fromCodePoint, toCodePoints} from 'css-line-break';
import {fromCodePoint, toCodePoints} from 'text-segmentation';
const testRangeBounds = (document: Document) => {
const TEST_HEIGHT = 123;

View File

@ -1,6 +1,5 @@
import {OVERFLOW_WRAP} from '../property-descriptors/overflow-wrap';
import {CSSParsedDeclaration} from '../index';
import {fromCodePoint, LineBreaker, toCodePoints} from 'css-line-break';
import {splitGraphemes} from 'text-segmentation';
import {Bounds, parseBounds} from './bounds';
import {FEATURES} from '../../core/features';
import {Context} from '../../core/context';
@ -21,7 +20,7 @@ export const parseTextBounds = (
styles: CSSParsedDeclaration,
node: Text
): TextBounds[] => {
const textList = breakText(value, styles);
const textList = splitGraphemes(value);
const textBounds: TextBounds[] = [];
let offset = 0;
textList.forEach((text) => {
@ -84,45 +83,3 @@ const createRange = (node: Text, offset: number, length: number): Range => {
const getRangeBounds = (context: Context, node: Text, offset: number, length: number): Bounds => {
return Bounds.fromClientRect(context, createRange(node, offset, length).getBoundingClientRect());
};
const breakText = (value: string, styles: CSSParsedDeclaration): string[] => {
return styles.letterSpacing !== 0 ? toCodePoints(value).map((i) => fromCodePoint(i)) : breakWords(value, styles);
};
// https://drafts.csswg.org/css-text/#word-separator
const wordSeparators = [0x0020, 0x00a0, 0x1361, 0x10100, 0x10101, 0x1039, 0x1091];
const breakWords = (str: string, styles: CSSParsedDeclaration): string[] => {
const breaker = LineBreaker(str, {
lineBreak: styles.lineBreak,
wordBreak: styles.overflowWrap === OVERFLOW_WRAP.BREAK_WORD ? 'break-word' : styles.wordBreak
});
const words = [];
let bk;
while (!(bk = breaker.next()).done) {
if (bk.value) {
const value = bk.value.slice();
const codePoints = toCodePoints(value);
let word = '';
codePoints.forEach((codePoint) => {
if (wordSeparators.indexOf(codePoint) === -1) {
word += fromCodePoint(codePoint);
} else {
if (word.length) {
words.push(word);
}
words.push(fromCodePoint(codePoint));
word = '';
}
});
if (word.length) {
words.push(word);
}
}
}
return words;
};

View File

@ -1,6 +1,6 @@
// https://www.w3.org/TR/css-syntax-3
import {fromCodePoint, toCodePoints} from 'css-line-break';
import {fromCodePoint, toCodePoints} from 'text-segmentation';
export enum TokenType {
STRING_TOKEN,

View File

@ -1,5 +1,5 @@
import {LIST_STYLE_TYPE} from '../../property-descriptors/list-style-type';
import {fromCodePoint} from 'css-line-break';
import {fromCodePoint} from 'text-segmentation';
import {contains} from '../../../core/bitwise';
import {CSSParsedCounterDeclaration} from '../../index';

View File

@ -19,7 +19,7 @@ import {
import {calculateBackgroundRendering, getBackgroundValueForIndex} from '../background';
import {isDimensionToken} from '../../css/syntax/parser';
import {TextBounds} from '../../css/layout/text';
import {fromCodePoint, toCodePoints} from 'css-line-break';
import {fromCodePoint, toCodePoints} from 'text-segmentation';
import {ImageElementContainer} from '../../dom/replaced-elements/image-element-container';
import {contentBox} from '../box-sizing';
import {CanvasElementContainer} from '../../dom/replaced-elements/canvas-element-container';

View File

@ -8,14 +8,15 @@ import {ScreenshotRequest} from './types';
// @ts-ignore
window.Promise = Promise;
const testRunnerUrl = location.href;
const hasHistoryApi = typeof window.history !== 'undefined' && typeof window.history.replaceState !== 'undefined';
const uploadResults = (canvas: HTMLCanvasElement, url: string) => {
// @ts-ignore
return new Promise((resolve: () => void, reject: (error: any) => void) => {
const xhr = new XMLHttpRequest();
return new Promise((resolve: () => void, reject: (error: string) => void) => {
// @ts-ignore
const xhr = 'withCredentials' in new XMLHttpRequest() ? new XMLHttpRequest() : new XDomainRequest();
xhr.onload = () => {
if (xhr.status === 200) {
if (typeof xhr.status !== 'number' || xhr.status === 200) {
resolve();
} else {
reject(`Failed to send screenshot with status ${xhr.status}`);
@ -61,17 +62,21 @@ testList
testContainer.onload = () => done();
testContainer.src = url + '?selenium&run=false&reftest&' + Math.random();
// Chrome does not resolve relative background urls correctly inside of a nested iframe
try {
history.replaceState(null, '', url);
} catch (e) {}
if (hasHistoryApi) {
// Chrome does not resolve relative background urls correctly inside of a nested iframe
try {
history.replaceState(null, '', url);
} catch (e) {}
}
document.body.appendChild(testContainer);
});
after(() => {
try {
history.replaceState(null, '', testRunnerUrl);
} catch (e) {}
if (hasHistoryApi) {
try {
history.replaceState(null, '', testRunnerUrl);
} catch (e) {}
}
document.body.removeChild(testContainer);
});