mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
0515765788 | |||
0fd25f048d | |||
14ff672c6e | |||
38fad5ac17 | |||
a31de83368 | |||
1fb3b53fc0 | |||
4d465116da | |||
74ce2c5062 | |||
fbeb6e72f2 | |||
c097f11ce3 | |||
b6ebf2acf6 | |||
e9c3d9d332 | |||
c232da2595 | |||
c759600c06 | |||
5f45968154 | |||
feb2fd0a63 | |||
fb944d9381 | |||
564634ba97 | |||
dd7468c446 | |||
eb00650b02 | |||
1d03a5f9a4 | |||
ea7d6b485d | |||
fd4fd95429 | |||
10b40821e5 | |||
518dd702a2 | |||
056953f2c1 | |||
9a57a08c72 | |||
26a81da2f0 | |||
57028ab423 | |||
c9e2fc27c8 | |||
2777a3e079 | |||
02ab96dc5f | |||
65746bd2e3 | |||
16d3bef255 | |||
0277c34310 | |||
f35ef0fe6f | |||
2c8dd18d55 | |||
6b5f31eef0 | |||
832b9ee934 | |||
73698e8ceb | |||
37fbd3f90e | |||
5300f20b78 | |||
f0e234a1d8 | |||
30163ab16f | |||
407145da94 | |||
c5e6eaa849 | |||
2d39cd0719 | |||
ebd7828dc8 | |||
877367d499 | |||
fd888bde8d | |||
7d2e12c3dd | |||
a7d3e9c2a2 | |||
f49e147b2f | |||
a902f92a14 | |||
e1573f8aed | |||
655779743b | |||
1a30167f6a | |||
2c58c56fbe | |||
288b851d05 | |||
0afb0fae0e | |||
a4702423cc | |||
f4aef61e5a | |||
cb210b2e61 | |||
a80ef26c42 | |||
2580b48d16 | |||
87d7894d71 | |||
7842768707 | |||
403908c7da | |||
8c8128b80a | |||
0d4b6ba665 | |||
b91fd9bc87 | |||
62d27c20c3 | |||
e811effe2a | |||
6156e28721 | |||
2b000f0061 | |||
843db27f72 | |||
bbdfe8a035 | |||
9da3bd7769 | |||
85fa81ad95 | |||
a5d74bcfd9 | |||
cf735a9fa1 | |||
9b051b8749 | |||
822311ed0c | |||
c39225ceac | |||
763125ce6d | |||
5ac4b42e33 | |||
cacb9a468f | |||
8623e4014b | |||
2d95a761b0 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -5,13 +5,16 @@
|
|||||||
/tests/flashcanvas.html
|
/tests/flashcanvas.html
|
||||||
/lib/
|
/lib/
|
||||||
/bin/
|
/bin/
|
||||||
/build/
|
|
||||||
image.jpg
|
image.jpg
|
||||||
/.project
|
/.project
|
||||||
/.settings/
|
/.settings/
|
||||||
|
/tests/certificate.pem
|
||||||
node_modules/
|
node_modules/
|
||||||
.envrc
|
.envrc
|
||||||
server.js
|
server.js
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
chromedriver.log
|
chromedriver.log
|
||||||
*.baseline
|
*.baseline
|
||||||
|
*.iml
|
||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- '0.10'
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- secure: "eW41gIqOizwO4pTgWnAAbW75AP7F+CK9qfSed/fSh4sJ9HWMIY1YRIaY8gjr+6jV/f7XVHcXuym6ZxgINYSkVKbF1JKxBJNLOXtSgNbVHSic58pYFvUjwxIBI9aPig9uux1+DbnpWqXFDTcACJSevQZE0xwmjdrSkDLgB0G34v8="
|
||||||
|
- secure: "Y2Av+Gd3z9uQEB36GwdOOuGka0hx0/HeitASEo59z934O8RxnmN9eNTXS7dDT3XtKtwxIyLTOEpS7qlRdWahH28hr/dS4xJj6ao58C+1xMcDs6NAPGmDxUlcJWpcGEsnjmXjQCc3fBioSTdpIBrK/gdvgpNh77UKG74Sk7Z+YGk="
|
||||||
|
- secure: "YI+YbTOGf2x4fPMKW+KhJiZWswoXT6xOKGwLfsQsVwmFX1LerJouil5D5iYOQuL4FE3pNaoJSNakIsokJQuGKJMmnPc8rdhMZuBJBk6MRghurE2Xe9qBHfuUBPlfD61nARESm4WDcyMwM0QVYaOKeY6aIpZ91qbUbyc60EEx3C4="
|
||||||
|
before_script:
|
||||||
|
- npm install -g grunt-cli
|
||||||
|
- curl https://gist.github.com/niklasvh/6150144/raw/sauce_connect_setup.sh | bash
|
99
Gruntfile.js
Normal file
99
Gruntfile.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*global module:false*/
|
||||||
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
|
var meta = {
|
||||||
|
banner: '/*\n <%= pkg.title || pkg.name %> <%= pkg.version %>' +
|
||||||
|
'<%= pkg.homepage ? " <" + pkg.homepage + ">" : "" %>' + '\n' +
|
||||||
|
' Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>' +
|
||||||
|
'\n\n Released under <%= _.pluck(pkg.licenses, "type").join(", ") %> License\n*/\n',
|
||||||
|
pre: '\n(function(window, document, undefined){\n\n',
|
||||||
|
post: '\n})(window,document);'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Project configuration.
|
||||||
|
grunt.initConfig({
|
||||||
|
|
||||||
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
|
||||||
|
qunit: {
|
||||||
|
files: ['tests/qunit/index.html']
|
||||||
|
},
|
||||||
|
concat: {
|
||||||
|
dist: {
|
||||||
|
src: [
|
||||||
|
'src/Core.js',
|
||||||
|
'src/Font.js',
|
||||||
|
'src/Generate.js',
|
||||||
|
'src/Queue.js',
|
||||||
|
'src/Parse.js',
|
||||||
|
'src/Preload.js',
|
||||||
|
'src/Renderer.js',
|
||||||
|
'src/Support.js',
|
||||||
|
'src/Util.js',
|
||||||
|
'src/renderers/Canvas.js'
|
||||||
|
],
|
||||||
|
dest: 'build/<%= pkg.name %>.js'
|
||||||
|
},
|
||||||
|
options:{
|
||||||
|
banner: meta.banner + meta.pre,
|
||||||
|
footer: meta.post
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uglify: {
|
||||||
|
dist: {
|
||||||
|
src: ['<%= concat.dist.dest %>'],
|
||||||
|
dest: 'build/<%= pkg.name %>.min.js'
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
banner: meta.banner
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
files: 'src/*',
|
||||||
|
tasks: ['build', 'jshint']
|
||||||
|
},
|
||||||
|
jshint: {
|
||||||
|
all: ['<%= concat.dist.dest %>'],
|
||||||
|
options: {
|
||||||
|
curly: true,
|
||||||
|
eqeqeq: true,
|
||||||
|
immed: true,
|
||||||
|
latedef: true,
|
||||||
|
newcap: true,
|
||||||
|
noarg: true,
|
||||||
|
sub: true,
|
||||||
|
undef: true,
|
||||||
|
boss: true,
|
||||||
|
eqnull: true,
|
||||||
|
browser: true,
|
||||||
|
globals: {
|
||||||
|
jQuery: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.registerTask('webdriver', 'Browser render tests', function(arg1) {
|
||||||
|
var selenium = require("./tests/selenium.js");
|
||||||
|
var done = this.async();
|
||||||
|
|
||||||
|
if (arguments.length) {
|
||||||
|
selenium[arg1].apply(null, arguments);
|
||||||
|
} else {
|
||||||
|
selenium.tests();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load tasks
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-qunit');
|
||||||
|
|
||||||
|
// Default task.
|
||||||
|
grunt.registerTask('build', ['concat', 'uglify']);
|
||||||
|
grunt.registerTask('default', ['concat', 'jshint', 'qunit', 'uglify']);
|
||||||
|
grunt.registerTask('travis', ['concat', 'jshint', 'qunit', 'uglify', 'webdriver']);
|
||||||
|
|
||||||
|
};
|
10
bower.json
Normal file
10
bower.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "html2canvas",
|
||||||
|
"version": "0.4.1",
|
||||||
|
"description": "Screenshots with JavaScript",
|
||||||
|
"main": "build/html2canvas.js",
|
||||||
|
"ignore": [
|
||||||
|
"tests",
|
||||||
|
".travis.yml"
|
||||||
|
]
|
||||||
|
}
|
2868
build/html2canvas.js
Normal file
2868
build/html2canvas.js
Normal file
File diff suppressed because it is too large
Load Diff
8
build/html2canvas.min.js
vendored
Normal file
8
build/html2canvas.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,17 +1,6 @@
|
|||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script type="text/javascript" src="../tests/assets/jquery-1.6.2.js"></script>
|
|
||||||
<script type="text/javascript" src="../build/html2canvas.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() {
|
|
||||||
html2canvas( [ document.body ], {
|
|
||||||
onrendered: function(canvas) {
|
|
||||||
document.body.appendChild(canvas);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<title>
|
<title>
|
||||||
display/box/float/clear test
|
display/box/float/clear test
|
||||||
</title>
|
</title>
|
||||||
@ -182,5 +171,13 @@
|
|||||||
<p style="color: black; font-size: 1em; line-height: 1.3em; clear: both">
|
<p style="color: black; font-size: 1em; line-height: 1.3em; clear: both">
|
||||||
This is a nonsensical document, but syntactically valid HTML 4.0. All 100% conformant CSS1 agents should be able to render the document elements above this paragraph <b>indistinguishably</b> (to the pixel) from this reference rendering, (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the <A HREF="sec5526c.htm" style="text-decoration:none">parent page</A>.
|
This is a nonsensical document, but syntactically valid HTML 4.0. All 100% conformant CSS1 agents should be able to render the document elements above this paragraph <b>indistinguishably</b> (to the pixel) from this reference rendering, (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the <A HREF="sec5526c.htm" style="text-decoration:none">parent page</A>.
|
||||||
</p>
|
</p>
|
||||||
|
<script type="text/javascript" src="../build/html2canvas.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
html2canvas(document.body, {
|
||||||
|
onrendered: function(canvas) {
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -3,17 +3,6 @@
|
|||||||
<head>
|
<head>
|
||||||
<title></title>
|
<title></title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<script type="text/javascript" src="../tests/assets/jquery-1.6.2.js"></script>
|
|
||||||
<script type="text/javascript" src="../build/html2canvas.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() {
|
|
||||||
html2canvas( [ document.body ], {
|
|
||||||
onrendered: function(canvas) {
|
|
||||||
document.body.appendChild(canvas);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style>
|
<style>
|
||||||
.feedback-overlay-black{
|
.feedback-overlay-black{
|
||||||
background-color:#000;
|
background-color:#000;
|
||||||
@ -64,5 +53,13 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script type="text/javascript" src="../build/html2canvas.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
html2canvas(document.body, {
|
||||||
|
onrendered: function(canvas) {
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
73
grunt.js
73
grunt.js
@ -1,73 +0,0 @@
|
|||||||
/*global module:false*/
|
|
||||||
module.exports = function(grunt) {
|
|
||||||
|
|
||||||
// Project configuration.
|
|
||||||
grunt.initConfig({
|
|
||||||
pkg: '<json:package.json>',
|
|
||||||
meta: {
|
|
||||||
banner: '/*\n <%= pkg.title || pkg.name %> <%= pkg.version %>' +
|
|
||||||
'<%= pkg.homepage ? " <" + pkg.homepage + ">\n" : "" %>' +
|
|
||||||
' Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>' +
|
|
||||||
'\n\n Released under <%= _.pluck(pkg.licenses, "type").join(", ") %> License\n*/',
|
|
||||||
pre: '(function(window, document, undefined){',
|
|
||||||
post: '})(window,document);'
|
|
||||||
},
|
|
||||||
lint: {
|
|
||||||
files: ['build/<%= pkg.name %>.js']
|
|
||||||
},
|
|
||||||
qunit: {
|
|
||||||
files: ['tests/qunit/index.html']
|
|
||||||
},
|
|
||||||
concat: {
|
|
||||||
dist: {
|
|
||||||
src: ['<banner:meta.banner>', '<banner:meta.pre>','src/*.js', 'src/renderers/Canvas.js', '<banner:meta.post>'],
|
|
||||||
dest: 'build/<%= pkg.name %>.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
min: {
|
|
||||||
dist: {
|
|
||||||
src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
|
|
||||||
dest: 'build/<%= pkg.name %>.min.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
files: '<config:lint.files>',
|
|
||||||
tasks: 'lint qunit'
|
|
||||||
},
|
|
||||||
jshint: {
|
|
||||||
options: {
|
|
||||||
curly: true,
|
|
||||||
eqeqeq: true,
|
|
||||||
immed: true,
|
|
||||||
latedef: true,
|
|
||||||
newcap: true,
|
|
||||||
noarg: true,
|
|
||||||
sub: true,
|
|
||||||
undef: true,
|
|
||||||
boss: true,
|
|
||||||
eqnull: true,
|
|
||||||
browser: true
|
|
||||||
},
|
|
||||||
globals: {
|
|
||||||
jQuery: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
uglify: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
var selenium = require("./tests/selenium.js");
|
|
||||||
grunt.registerTask('webdriver', 'Browser render tests', function(arg1) {
|
|
||||||
|
|
||||||
var done = this.async();
|
|
||||||
|
|
||||||
if (arguments.length === 0) {
|
|
||||||
selenium.tests();
|
|
||||||
} else {
|
|
||||||
selenium[arg1].apply(null, arguments);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Default task.
|
|
||||||
grunt.registerTask('default', 'concat lint qunit min webdriver');
|
|
||||||
|
|
||||||
};
|
|
41
package.json
41
package.json
@ -2,18 +2,45 @@
|
|||||||
"title": "html2canvas",
|
"title": "html2canvas",
|
||||||
"name": "html2canvas",
|
"name": "html2canvas",
|
||||||
"description": "Screenshots with JavaScript",
|
"description": "Screenshots with JavaScript",
|
||||||
"version": "0.4.0",
|
"version": "0.4.1",
|
||||||
"author": {
|
"author": {
|
||||||
"name":"Niklas von Hertzen (@niklasvh)"
|
"name": "Niklas von Hertzen",
|
||||||
|
"email": "niklasvh@gmail.com",
|
||||||
|
"url": "http://hertzen.com"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git@github.com:niklasvh/html2canvas.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/niklasvh/html2canvas/issues"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"grunt": ">=0.4.0",
|
||||||
|
"grunt-contrib-concat": "*",
|
||||||
|
"grunt-contrib-uglify": "*",
|
||||||
|
"grunt-contrib-jshint": "*",
|
||||||
|
"grunt-contrib-qunit": "*",
|
||||||
|
"grunt-contrib-watch": "~0.5.1",
|
||||||
|
"googleapis": "~0.4.3",
|
||||||
|
"jwt-sign": "~0.1.0",
|
||||||
"base64-arraybuffer": ">= 0.1.0",
|
"base64-arraybuffer": ">= 0.1.0",
|
||||||
"png-js": ">= 0.1.1",
|
"png-js": ">= 0.1.1",
|
||||||
"webdriver.js": ">= 0.1.0"
|
"sync-webdriver": ">=0.1.1",
|
||||||
|
"express": "~3.2.3",
|
||||||
|
"baconjs": "~0.3.15"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "grunt travis --verbose"
|
||||||
},
|
},
|
||||||
"homepage": "http://html2canvas.hertzen.com",
|
"homepage": "http://html2canvas.hertzen.com",
|
||||||
"licenses": [{
|
"licenses": [
|
||||||
|
{
|
||||||
"type": "MIT"
|
"type": "MIT"
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
}
|
}
|
51
readme.md
51
readme.md
@ -1,16 +1,19 @@
|
|||||||
html2canvas
|
html2canvas
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
### Current build status ###
|
||||||
|
[](https://travis-ci.org/niklasvh/html2canvas)
|
||||||
|
|
||||||
#### JavaScript HTML renderer ####
|
#### JavaScript HTML renderer ####
|
||||||
|
|
||||||
This 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 available on the page.
|
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 available on the page.
|
||||||
|
|
||||||
|
|
||||||
###How does it work?###
|
###How does it work?###
|
||||||
The script renders the current page as a canvas image, by reading the DOM and the different styles applied to the elements.
|
The script renders the current page as a canvas image, by reading the DOM and the different styles applied to the elements.
|
||||||
|
|
||||||
It does <b>not require any rendering from the server</b>, as the whole image is created on the <b>clients browser</b>. However, as it is heavily dependent on the browser, this library is *not suitable* to be used on for example on node.js.
|
It does **not require any rendering from the server**, as the whole image is created on the **clients browser**. However, as it is heavily dependent on the browser, this library is *not suitable* to be used in nodejs.
|
||||||
It doesn't magically circumvent and browser content policy restrictions either, so rendering cross origin content will require a <a href="https://github.com/niklasvh/html2canvas/wiki/Proxies">proxy</a> to get the content to the <a href="http://en.wikipedia.org/wiki/Same_origin_policy">same origin</a>.
|
It doesn't magically circumvent any browser content policy restrictions either, so rendering cross-origin content will require a [proxy](https://github.com/niklasvh/html2canvas/wiki/Proxies) to get the content to the [same origin](http://en.wikipedia.org/wiki/Same_origin_policy).
|
||||||
|
|
||||||
The script is still in a **very experimental state**, so I don't recommend using it in a production environment nor start building applications with it yet, as there will be still major changes made.
|
The script is still in a **very experimental state**, so I don't recommend using it in a production environment nor start building applications with it yet, as there will be still major changes made.
|
||||||
|
|
||||||
@ -22,16 +25,17 @@ The script should work fine on the following browsers:
|
|||||||
* Google Chrome
|
* Google Chrome
|
||||||
* Opera 12+
|
* Opera 12+
|
||||||
* IE9+
|
* 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.
|
As each CSS property needs to be manually built to be supported, there are a number of properties that are not yet supported.
|
||||||
|
|
||||||
### Usage ###
|
### Usage ###
|
||||||
To render an `element` with html2canvas, simply call:
|
To render an `element` with html2canvas, simply call:
|
||||||
` html2canvas( [ element ], options);`
|
` html2canvas(element, options);`
|
||||||
|
|
||||||
To access the created canvas, provide the `onrendered` event in the options which returns the canvas element as the first argument, as such:
|
To access the created canvas, provide the `onrendered` event in the options which returns the canvas element as the first argument, as such:
|
||||||
|
|
||||||
html2canvas( [ document.body ], {
|
html2canvas(document.body, {
|
||||||
onrendered: function(canvas) {
|
onrendered: function(canvas) {
|
||||||
/* canvas is the actual canvas element,
|
/* canvas is the actual canvas element,
|
||||||
to append it to the page call for example
|
to append it to the page call for example
|
||||||
@ -42,7 +46,7 @@ To access the created canvas, provide the `onrendered` event in the options whic
|
|||||||
|
|
||||||
### Building ###
|
### Building ###
|
||||||
|
|
||||||
The library uses <a href="http://gruntjs.com/">grunt</a> for building. Alternatively, you can download ready builds from the <a href="https://github.com/niklasvh/html2canvas/downloads">downloads page</a>.
|
The library uses [grunt](http://gruntjs.com/) for building. Alternatively, you can download the latest build from [here](http://html2canvas.hertzen.com/build/html2canvas.js).
|
||||||
|
|
||||||
Run the full build process (including lint, qunit and webdriver tests):
|
Run the full build process (including lint, qunit and webdriver tests):
|
||||||
|
|
||||||
@ -50,14 +54,13 @@ Run the full build process (including lint, qunit and webdriver tests):
|
|||||||
|
|
||||||
Skip lint and tests and simply build from source:
|
Skip lint and tests and simply build from source:
|
||||||
|
|
||||||
$ grunt concat
|
$ grunt build
|
||||||
$ grunt min
|
|
||||||
|
|
||||||
### Running tests ###
|
### Running tests ###
|
||||||
|
|
||||||
The library has two sets of tests. The first set is a number of qunit tests that check that different values parsed by browsers are correctly converted in html2canvas. To run these tests with grunt you'll need <a href="http://phantomjs.org/">phantomjs</a>.
|
The library has two sets of tests. The first set is a number of qunit tests that check that different values parsed by browsers are correctly converted in html2canvas. To run these tests with grunt you'll need [phantomjs](http://phantomjs.org/).
|
||||||
|
|
||||||
The other set of tests run Firefox, Chrome and Internet Explorer with <a href="https://github.com/niklasvh/webdriver.js">webdriver</a>. The selenium standalone server (runs on Java) is required for these tests and can be downloaded from <a href="http://code.google.com/p/selenium/downloads/list">here</a>. They capture an actual screenshot from the test pages and compare the image to the screenshot created by html2canvas and calculate the percentage differences. These tests generally aren't expected to provide 100% matches, but while commiting changes, these should generally not go decrease from the baseline values.
|
The other set of tests run Firefox, Chrome and Internet Explorer with [webdriver](https://github.com/niklasvh/webdriver.js). The selenium standalone server (runs on Java) is required for these tests and can be downloaded from [here](http://code.google.com/p/selenium/downloads/list). They capture an actual screenshot from the test pages and compare the image to the screenshot created by html2canvas and calculate the percentage differences. These tests generally aren't expected to provide 100% matches, but while commiting changes, these should generally not go decrease from the baseline values.
|
||||||
|
|
||||||
Start by downloading the dependencies:
|
Start by downloading the dependencies:
|
||||||
|
|
||||||
@ -67,26 +70,24 @@ Run qunit tests:
|
|||||||
|
|
||||||
$ grunt test
|
$ grunt test
|
||||||
|
|
||||||
Run webdriver tests:
|
|
||||||
|
|
||||||
$ java -jar /path/to/selenium-server-standalone-2.xx.x.jar
|
|
||||||
$ grunt webdriver
|
|
||||||
|
|
||||||
Commiting improvements in baseline values:
|
|
||||||
|
|
||||||
$ grunt webdriver:baseline
|
|
||||||
|
|
||||||
### Examples ###
|
### Examples ###
|
||||||
|
|
||||||
For more information and examples, please visit the <a href="http://html2canvas.hertzen.com">homepage</a> or try the <a href="http://html2canvas.hertzen.com/screenshots.html">test console</a>.
|
For more information and examples, please visit the [homepage](http://html2canvas.hertzen.com) or try the [test console](http://html2canvas.hertzen.com/screenshots.html).
|
||||||
|
|
||||||
### Contributing ###
|
### Contributing ###
|
||||||
|
|
||||||
If you wish to contribute to the project, please send the pull requests to the develop branch. Before making any changes, make sure to run the webdriver tests to create the baseline results. If some CSS property isn't supported or is incomplete, please create appropriate tests for it as well before submitting any code changes.
|
If you wish to contribute to the project, please send the pull requests to the develop branch. Before submitting any changes, try and test that the changes work with all the support browsers. If some CSS property isn't supported or is incomplete, please create appropriate tests for it as well before submitting any code changes.
|
||||||
|
|
||||||
### Changelog ###
|
### Changelog ###
|
||||||
|
|
||||||
v0.40 -
|
v0.4.1 - 7.9.2013
|
||||||
|
* Added support for bower
|
||||||
|
* Improved z-index ordering
|
||||||
|
* Basic implementation for CSS transformations
|
||||||
|
* Fixed inline text in top element
|
||||||
|
* Basic implementation for text-shadow
|
||||||
|
|
||||||
|
v0.4.0 - 30.1.2013
|
||||||
* Added rendering tests with <a href="https://github.com/niklasvh/webdriver.js">webdriver</a>
|
* Added rendering tests with <a href="https://github.com/niklasvh/webdriver.js">webdriver</a>
|
||||||
* Switched to using grunt for building
|
* Switched to using grunt for building
|
||||||
* Removed support for IE<9, including any FlashCanvas bits
|
* Removed support for IE<9, including any FlashCanvas bits
|
||||||
@ -96,7 +97,7 @@ v0.40 -
|
|||||||
* Support for placeholder rendering
|
* Support for placeholder rendering
|
||||||
* Reformatted all tests to small units to test specific features
|
* Reformatted all tests to small units to test specific features
|
||||||
|
|
||||||
v0.34 - 26.6.2012
|
v0.3.4 - 26.6.2012
|
||||||
|
|
||||||
* Removed (last?) jQuery dependencies (<a href="https://github.com/niklasvh/html2canvas/commit/343b86705fe163766fcf735eb0217130e4bd5b17">niklasvh</a>)
|
* Removed (last?) jQuery dependencies (<a href="https://github.com/niklasvh/html2canvas/commit/343b86705fe163766fcf735eb0217130e4bd5b17">niklasvh</a>)
|
||||||
* SVG-powered rendering (<a href="https://github.com/niklasvh/html2canvas/commit/67d3e0d0f59a5a654caf71a2e3be6494ff146c75">niklasvh</a>)
|
* SVG-powered rendering (<a href="https://github.com/niklasvh/html2canvas/commit/67d3e0d0f59a5a654caf71a2e3be6494ff146c75">niklasvh</a>)
|
||||||
@ -104,7 +105,7 @@ v0.34 - 26.6.2012
|
|||||||
* Split renderers to their own objects (<a href="https://github.com/niklasvh/html2canvas/commit/94f2f799a457cd29a21cc56ef8c06f1697866739">niklasvh</a>)
|
* Split renderers to their own objects (<a href="https://github.com/niklasvh/html2canvas/commit/94f2f799a457cd29a21cc56ef8c06f1697866739">niklasvh</a>)
|
||||||
* Simplified API, cleaned up code (<a href="https://github.com/niklasvh/html2canvas/commit/c7d526c9eaa6a4abf4754d205fe1dee360c7660e">niklasvh</a>)
|
* Simplified API, cleaned up code (<a href="https://github.com/niklasvh/html2canvas/commit/c7d526c9eaa6a4abf4754d205fe1dee360c7660e">niklasvh</a>)
|
||||||
|
|
||||||
v0.33 - 2.3.2012
|
v0.3.3 - 2.3.2012
|
||||||
|
|
||||||
* SVG taint fix, and additional taint testing options for rendering (<a href="https://github.com/niklasvh/html2canvas/commit/2dc8b9385e656696cb019d615bdfa1d98b17d5d4">niklasvh</a>)
|
* SVG taint fix, and additional taint testing options for rendering (<a href="https://github.com/niklasvh/html2canvas/commit/2dc8b9385e656696cb019d615bdfa1d98b17d5d4">niklasvh</a>)
|
||||||
* Added support for CORS images and option to create canvas as tainted (<a href="https://github.com/niklasvh/html2canvas/commit/3ad49efa0032cde25c6ed32a39e35d1505d3b2ef">niklasvh</a>)
|
* Added support for CORS images and option to create canvas as tainted (<a href="https://github.com/niklasvh/html2canvas/commit/3ad49efa0032cde25c6ed32a39e35d1505d3b2ef">niklasvh</a>)
|
||||||
@ -112,7 +113,7 @@ v0.33 - 2.3.2012
|
|||||||
* Added integrated support for Flashcanvas (<a href="https://github.com/niklasvh/html2canvas/commit/e9257191519f67d74fd5e364d8dee3c0963ba5fc">niklasvh</a>)
|
* Added integrated support for Flashcanvas (<a href="https://github.com/niklasvh/html2canvas/commit/e9257191519f67d74fd5e364d8dee3c0963ba5fc">niklasvh</a>)
|
||||||
* Fixed a variety of legacy IE bugs (<a href="https://github.com/niklasvh/html2canvas/commit/b65357c55d0701017bafcd357bc654b54d458f8f">niklasvh</a>)
|
* Fixed a variety of legacy IE bugs (<a href="https://github.com/niklasvh/html2canvas/commit/b65357c55d0701017bafcd357bc654b54d458f8f">niklasvh</a>)
|
||||||
|
|
||||||
v0.32 - 20.2.2012
|
v0.3.2 - 20.2.2012
|
||||||
|
|
||||||
* Added changelog!
|
* Added changelog!
|
||||||
* Added bookmarklet (<a href="https://github.com/niklasvh/html2canvas/commit/b320dd306e1a2d32a3bc5a71b6ebf6d8c060cde5">cobexer</a>)
|
* Added bookmarklet (<a href="https://github.com/niklasvh/html2canvas/commit/b320dd306e1a2d32a3bc5a71b6ebf6d8c060cde5">cobexer</a>)
|
||||||
|
229
src/Core.js
229
src/Core.js
@ -5,20 +5,49 @@ previousElement,
|
|||||||
computedCSS,
|
computedCSS,
|
||||||
html2canvas;
|
html2canvas;
|
||||||
|
|
||||||
function h2clog(a) {
|
_html2canvas.Util = {};
|
||||||
|
|
||||||
|
_html2canvas.Util.log = function(a) {
|
||||||
if (_html2canvas.logging && window.console && window.console.log) {
|
if (_html2canvas.logging && window.console && window.console.log) {
|
||||||
window.console.log(a);
|
window.console.log(a);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_html2canvas.Util = {};
|
|
||||||
|
|
||||||
_html2canvas.Util.trimText = (function(isNative){
|
_html2canvas.Util.trimText = (function(isNative){
|
||||||
return function(input){
|
return function(input) {
|
||||||
if(isNative) { return isNative.apply( input ); }
|
return isNative ? isNative.apply(input) : ((input || '') + '').replace( /^\s+|\s+$/g , '' );
|
||||||
else { return ((input || '') + '').replace( /^\s+|\s+$/g , '' ); }
|
|
||||||
};
|
};
|
||||||
})( String.prototype.trim );
|
})(String.prototype.trim);
|
||||||
|
|
||||||
|
_html2canvas.Util.asFloat = function(v) {
|
||||||
|
return parseFloat(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
// TODO: support all possible length values
|
||||||
|
var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
|
||||||
|
var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
|
||||||
|
_html2canvas.Util.parseTextShadows = function (value) {
|
||||||
|
if (!value || value === 'none') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// find multiple shadow declarations
|
||||||
|
var shadows = value.match(TEXT_SHADOW_PROPERTY),
|
||||||
|
results = [];
|
||||||
|
for (var i = 0; shadows && (i < shadows.length); i++) {
|
||||||
|
var s = shadows[i].match(TEXT_SHADOW_VALUES);
|
||||||
|
results.push({
|
||||||
|
color: s[0],
|
||||||
|
offsetX: s[1] ? s[1].replace('px', '') : 0,
|
||||||
|
offsetY: s[2] ? s[2].replace('px', '') : 0,
|
||||||
|
blur: s[3] ? s[3].replace('px', '') : 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
_html2canvas.Util.parseBackgroundImage = function (value) {
|
_html2canvas.Util.parseBackgroundImage = function (value) {
|
||||||
var whitespace = ' \r\n\t',
|
var whitespace = ' \r\n\t',
|
||||||
@ -119,38 +148,42 @@ _html2canvas.Util.parseBackgroundImage = function (value) {
|
|||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
_html2canvas.Util.Bounds = function getBounds (el) {
|
_html2canvas.Util.Bounds = function (element) {
|
||||||
var clientRect,
|
var clientRect, bounds = {};
|
||||||
bounds = {};
|
|
||||||
|
|
||||||
if (el.getBoundingClientRect){
|
|
||||||
clientRect = el.getBoundingClientRect();
|
|
||||||
|
|
||||||
|
if (element.getBoundingClientRect){
|
||||||
|
clientRect = element.getBoundingClientRect();
|
||||||
|
|
||||||
// TODO add scroll position to bounds, so no scrolling of window necessary
|
// TODO add scroll position to bounds, so no scrolling of window necessary
|
||||||
bounds.top = clientRect.top;
|
bounds.top = clientRect.top;
|
||||||
bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
|
bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
|
||||||
bounds.left = clientRect.left;
|
bounds.left = clientRect.left;
|
||||||
|
|
||||||
// older IE doesn't have width/height, but top/bottom instead
|
bounds.width = element.offsetWidth;
|
||||||
bounds.width = clientRect.width || (clientRect.right - clientRect.left);
|
bounds.height = element.offsetHeight;
|
||||||
bounds.height = clientRect.height || (clientRect.bottom - clientRect.top);
|
}
|
||||||
|
|
||||||
return bounds;
|
return bounds;
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_html2canvas.Util.getCSS = function (el, attribute, index) {
|
// TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
|
||||||
// return $(el).css(attribute);
|
// but would require further work to calculate the correct positions for elements with offsetParents
|
||||||
|
_html2canvas.Util.OffsetBounds = function (element) {
|
||||||
|
var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {top: 0, left: 0};
|
||||||
|
|
||||||
var val,
|
return {
|
||||||
isBackgroundSizePosition = attribute.match( /^background(Size|Position)$/ );
|
top: element.offsetTop + parent.top,
|
||||||
|
bottom: element.offsetTop + element.offsetHeight + parent.top,
|
||||||
|
left: element.offsetLeft + parent.left,
|
||||||
|
width: element.offsetWidth,
|
||||||
|
height: element.offsetHeight
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
function toPX( attribute, val ) {
|
function toPX(element, attribute, value ) {
|
||||||
var rsLeft = el.runtimeStyle && el.runtimeStyle[ attribute ],
|
var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
|
||||||
left,
|
left,
|
||||||
style = el.style;
|
style = element.style;
|
||||||
|
|
||||||
// Check if we are not dealing with pixels, (Opera has issues with this)
|
// Check if we are not dealing with pixels, (Opera has issues with this)
|
||||||
// Ported from jQuery css.js
|
// Ported from jQuery css.js
|
||||||
@ -160,71 +193,76 @@ _html2canvas.Util.getCSS = function (el, attribute, index) {
|
|||||||
// If we're not dealing with a regular pixel number
|
// If we're not dealing with a regular pixel number
|
||||||
// but a number that has a weird ending, we need to convert it to pixels
|
// but a number that has a weird ending, we need to convert it to pixels
|
||||||
|
|
||||||
if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( val ) && /^-?\d/.test( val ) ) {
|
if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( value ) && /^-?\d/.test(value) ) {
|
||||||
|
|
||||||
// Remember the original values
|
// Remember the original values
|
||||||
left = style.left;
|
left = style.left;
|
||||||
|
|
||||||
// Put in the new values to get a computed value out
|
// Put in the new values to get a computed value out
|
||||||
if ( rsLeft ) {
|
if (rsLeft) {
|
||||||
el.runtimeStyle.left = el.currentStyle.left;
|
element.runtimeStyle.left = element.currentStyle.left;
|
||||||
}
|
}
|
||||||
style.left = attribute === "fontSize" ? "1em" : (val || 0);
|
style.left = attribute === "fontSize" ? "1em" : (value || 0);
|
||||||
val = style.pixelLeft + "px";
|
value = style.pixelLeft + "px";
|
||||||
|
|
||||||
// Revert the changed values
|
// Revert the changed values
|
||||||
style.left = left;
|
style.left = left;
|
||||||
if ( rsLeft ) {
|
if (rsLeft) {
|
||||||
el.runtimeStyle.left = rsLeft;
|
element.runtimeStyle.left = rsLeft;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!/^(thin|medium|thick)$/i.test(value)) {
|
||||||
|
return Math.round(parseFloat(value)) + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/^(thin|medium|thick)$/i.test( val )) {
|
return value;
|
||||||
return Math.round(parseFloat( val )) + "px";
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return val;
|
function asInt(val) {
|
||||||
}
|
return parseInt(val, 10);
|
||||||
|
}
|
||||||
|
|
||||||
if (previousElement !== el) {
|
function parseBackgroundSizePosition(value, element, attribute, index) {
|
||||||
computedCSS = document.defaultView.getComputedStyle(el, null);
|
value = (value || '').split(',');
|
||||||
}
|
value = value[index || 0] || value[0] || 'auto';
|
||||||
val = computedCSS[attribute];
|
value = _html2canvas.Util.trimText(value).split(' ');
|
||||||
|
|
||||||
if (isBackgroundSizePosition) {
|
if(attribute === 'backgroundSize' && (!value[0] || value[0].match(/cover|contain|auto/))) {
|
||||||
val = (val || '').split( ',' );
|
|
||||||
val = val[index || 0] || val[0] || 'auto';
|
|
||||||
val = _html2canvas.Util.trimText(val).split(' ');
|
|
||||||
|
|
||||||
if(attribute === 'backgroundSize' && (!val[ 0 ] || val[ 0 ].match( /cover|contain|auto/ ))) {
|
|
||||||
//these values will be handled in the parent function
|
//these values will be handled in the parent function
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
val[ 0 ] = ( val[ 0 ].indexOf( "%" ) === -1 ) ? toPX( attribute + "X", val[ 0 ] ) : val[ 0 ];
|
value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
|
||||||
if(val[ 1 ] === undefined) {
|
if(value[1] === undefined) {
|
||||||
if(attribute === 'backgroundSize') {
|
if(attribute === 'backgroundSize') {
|
||||||
val[ 1 ] = 'auto';
|
value[1] = 'auto';
|
||||||
return val;
|
return value;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// IE 9 doesn't return double digit always
|
// IE 9 doesn't return double digit always
|
||||||
val[ 1 ] = val[ 0 ];
|
value[1] = value[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val[ 1 ] = ( val[ 1 ].indexOf( "%" ) === -1 ) ? toPX( attribute + "Y", val[ 1 ] ) : val[ 1 ];
|
value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
|
||||||
}
|
}
|
||||||
} else if ( /border(Top|Bottom)(Left|Right)Radius/.test( attribute) ) {
|
return value;
|
||||||
var arr = val.split(" ");
|
}
|
||||||
if ( arr.length <= 1 ) {
|
|
||||||
arr[ 1 ] = arr[ 0 ];
|
_html2canvas.Util.getCSS = function (element, attribute, index) {
|
||||||
}
|
if (previousElement !== element) {
|
||||||
arr[ 0 ] = parseInt( arr[ 0 ], 10 );
|
computedCSS = document.defaultView.getComputedStyle(element, null);
|
||||||
arr[ 1 ] = parseInt( arr[ 1 ], 10 );
|
|
||||||
val = arr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return val;
|
var value = computedCSS[attribute];
|
||||||
|
|
||||||
|
if (/^background(Size|Position)$/.test(attribute)) {
|
||||||
|
return parseBackgroundSizePosition(value, element, attribute, index);
|
||||||
|
} else if (/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) {
|
||||||
|
var arr = value.split(" ");
|
||||||
|
if (arr.length <= 1) {
|
||||||
|
arr[1] = arr[0];
|
||||||
|
}
|
||||||
|
return arr.map(asInt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
_html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){
|
_html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){
|
||||||
@ -235,18 +273,18 @@ _html2canvas.Util.resizeBounds = function( current_width, current_height, target
|
|||||||
if(!stretch_mode || stretch_mode === 'auto') {
|
if(!stretch_mode || stretch_mode === 'auto') {
|
||||||
output_width = target_width;
|
output_width = target_width;
|
||||||
output_height = target_height;
|
output_height = target_height;
|
||||||
|
} else if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
|
||||||
} else {
|
|
||||||
if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
|
|
||||||
output_height = target_height;
|
output_height = target_height;
|
||||||
output_width = target_height * current_ratio;
|
output_width = target_height * current_ratio;
|
||||||
} else {
|
} else {
|
||||||
output_width = target_width;
|
output_width = target_width;
|
||||||
output_height = target_width / current_ratio;
|
output_height = target_width / current_ratio;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return { width: output_width, height: output_height };
|
return {
|
||||||
|
width: output_width,
|
||||||
|
height: output_height
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function backgroundBoundsFactory( prop, el, bounds, image, imageIndex, backgroundSize ) {
|
function backgroundBoundsFactory( prop, el, bounds, image, imageIndex, backgroundSize ) {
|
||||||
@ -271,24 +309,21 @@ function backgroundBoundsFactory( prop, el, bounds, image, imageIndex, backgroun
|
|||||||
if(prop !== 'backgroundSize') {
|
if(prop !== 'backgroundSize') {
|
||||||
left -= (backgroundSize || image).width*percentage;
|
left -= (backgroundSize || image).width*percentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if(prop === 'backgroundSize') {
|
if(prop === 'backgroundSize') {
|
||||||
if(bgposition[0] === 'auto') {
|
if(bgposition[0] === 'auto') {
|
||||||
left = image.width;
|
left = image.width;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if(bgposition[0].match(/contain|cover/)) {
|
if (/contain|cover/.test(bgposition[0])) {
|
||||||
var resized = _html2canvas.Util.resizeBounds( image.width, image.height, bounds.width, bounds.height, bgposition[0] );
|
var resized = _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, bgposition[0]);
|
||||||
left = resized.width;
|
left = resized.width;
|
||||||
topPos = resized.height;
|
topPos = resized.height;
|
||||||
} else {
|
} else {
|
||||||
left = parseInt (bgposition[0], 10 );
|
left = parseInt(bgposition[0], 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
left = parseInt( bgposition[0], 10 );
|
left = parseInt( bgposition[0], 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,6 +348,7 @@ _html2canvas.Util.BackgroundPosition = function( el, bounds, image, imageIndex,
|
|||||||
var result = backgroundBoundsFactory( 'backgroundPosition', el, bounds, image, imageIndex, backgroundSize );
|
var result = backgroundBoundsFactory( 'backgroundPosition', el, bounds, image, imageIndex, backgroundSize );
|
||||||
return { left: result[0], top: result[1] };
|
return { left: result[0], top: result[1] };
|
||||||
};
|
};
|
||||||
|
|
||||||
_html2canvas.Util.BackgroundSize = function( el, bounds, image, imageIndex ) {
|
_html2canvas.Util.BackgroundSize = function( el, bounds, image, imageIndex ) {
|
||||||
var result = backgroundBoundsFactory( 'backgroundSize', el, bounds, image, imageIndex );
|
var result = backgroundBoundsFactory( 'backgroundSize', el, bounds, image, imageIndex );
|
||||||
return { width: result[0], height: result[1] };
|
return { width: result[0], height: result[1] };
|
||||||
@ -335,45 +371,40 @@ _html2canvas.Util.Extend = function (options, defaults) {
|
|||||||
* http://jquery.org/license
|
* http://jquery.org/license
|
||||||
*/
|
*/
|
||||||
_html2canvas.Util.Children = function( elem ) {
|
_html2canvas.Util.Children = function( elem ) {
|
||||||
|
|
||||||
|
|
||||||
var children;
|
var children;
|
||||||
try {
|
try {
|
||||||
|
children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(array) {
|
||||||
children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ?
|
|
||||||
elem.contentDocument || elem.contentWindow.document : (function( array ){
|
|
||||||
var ret = [];
|
var ret = [];
|
||||||
|
if (array !== null) {
|
||||||
if ( array !== null ) {
|
(function(first, second ) {
|
||||||
|
|
||||||
(function( first, second ) {
|
|
||||||
var i = first.length,
|
var i = first.length,
|
||||||
j = 0;
|
j = 0;
|
||||||
|
|
||||||
if ( typeof second.length === "number" ) {
|
if (typeof second.length === "number") {
|
||||||
for ( var l = second.length; j < l; j++ ) {
|
for (var l = second.length; j < l; j++) {
|
||||||
first[ i++ ] = second[ j ];
|
first[i++] = second[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
while ( second[j] !== undefined ) {
|
while (second[j] !== undefined) {
|
||||||
first[ i++ ] = second[ j++ ];
|
first[i++] = second[j++];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
first.length = i;
|
first.length = i;
|
||||||
|
|
||||||
return first;
|
return first;
|
||||||
})( ret, array );
|
})(ret, array);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
})( elem.childNodes );
|
})(elem.childNodes);
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
h2clog("html2canvas.Util.Children failed with exception: " + ex.message);
|
_html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
|
||||||
children = [];
|
children = [];
|
||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_html2canvas.Util.isTransparent = function(backgroundColor) {
|
||||||
|
return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
|
||||||
|
};
|
@ -1,6 +1,8 @@
|
|||||||
(function(){
|
(function(){
|
||||||
|
var Util = _html2canvas.Util,
|
||||||
|
Generate = {};
|
||||||
|
|
||||||
_html2canvas.Generate = {};
|
_html2canvas.Generate = Generate;
|
||||||
|
|
||||||
var reGradients = [
|
var reGradients = [
|
||||||
/^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
|
/^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
|
||||||
@ -18,7 +20,7 @@
|
|||||||
* TODO: Add old Webkit -webkit-gradient(radial, ...) support
|
* TODO: Add old Webkit -webkit-gradient(radial, ...) support
|
||||||
* TODO: Maybe some RegExp optimizations are possible ;o)
|
* TODO: Maybe some RegExp optimizations are possible ;o)
|
||||||
*/
|
*/
|
||||||
_html2canvas.Generate.parseGradient = function(css, bounds) {
|
Generate.parseGradient = function(css, bounds) {
|
||||||
var gradient, i, len = reGradients.length, m1, stop, m2, m2Len, step, m3, tl,tr,br,bl;
|
var gradient, i, len = reGradients.length, m1, stop, m2, m2Len, step, m3, tl,tr,br,bl;
|
||||||
|
|
||||||
for(i = 0; i < len; i+=1){
|
for(i = 0; i < len; i+=1){
|
||||||
@ -320,14 +322,25 @@
|
|||||||
return gradient;
|
return gradient;
|
||||||
};
|
};
|
||||||
|
|
||||||
_html2canvas.Generate.Gradient = function(src, bounds) {
|
function addScrollStops(grad) {
|
||||||
|
return function(colorStop) {
|
||||||
|
try {
|
||||||
|
grad.addColorStop(colorStop.stop, colorStop.color);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Generate.Gradient = function(src, bounds) {
|
||||||
if(bounds.width === 0 || bounds.height === 0) {
|
if(bounds.width === 0 || bounds.height === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var canvas = document.createElement('canvas'),
|
var canvas = document.createElement('canvas'),
|
||||||
ctx = canvas.getContext('2d'),
|
ctx = canvas.getContext('2d'),
|
||||||
gradient, grad, i, len;
|
gradient, grad;
|
||||||
|
|
||||||
canvas.width = bounds.width;
|
canvas.width = bounds.width;
|
||||||
canvas.height = bounds.height;
|
canvas.height = bounds.height;
|
||||||
@ -336,72 +349,46 @@
|
|||||||
gradient = _html2canvas.Generate.parseGradient(src, bounds);
|
gradient = _html2canvas.Generate.parseGradient(src, bounds);
|
||||||
|
|
||||||
if(gradient) {
|
if(gradient) {
|
||||||
if(gradient.type === 'linear') {
|
switch(gradient.type) {
|
||||||
|
case 'linear':
|
||||||
grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
|
grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
|
||||||
|
gradient.colorStops.forEach(addScrollStops(grad));
|
||||||
for (i = 0, len = gradient.colorStops.length; i < len; i+=1) {
|
|
||||||
try {
|
|
||||||
grad.addColorStop(gradient.colorStops[i].stop, gradient.colorStops[i].color);
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
h2clog(['failed to add color stop: ', e, '; tried to add: ', gradient.colorStops[i], '; stop: ', i, '; in: ', src]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fillStyle = grad;
|
ctx.fillStyle = grad;
|
||||||
ctx.fillRect(0, 0, bounds.width, bounds.height);
|
ctx.fillRect(0, 0, bounds.width, bounds.height);
|
||||||
|
break;
|
||||||
|
|
||||||
} else if(gradient.type === 'circle') {
|
case 'circle':
|
||||||
|
|
||||||
grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
|
grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
|
||||||
|
gradient.colorStops.forEach(addScrollStops(grad));
|
||||||
for (i = 0, len = gradient.colorStops.length; i < len; i+=1) {
|
|
||||||
try {
|
|
||||||
grad.addColorStop(gradient.colorStops[i].stop, gradient.colorStops[i].color);
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
h2clog(['failed to add color stop: ', e, '; tried to add: ', gradient.colorStops[i], '; stop: ', i, '; in: ', src]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fillStyle = grad;
|
ctx.fillStyle = grad;
|
||||||
ctx.fillRect(0, 0, bounds.width, bounds.height);
|
ctx.fillRect(0, 0, bounds.width, bounds.height);
|
||||||
|
break;
|
||||||
|
|
||||||
} else if(gradient.type === 'ellipse') {
|
case 'ellipse':
|
||||||
|
|
||||||
// draw circle
|
|
||||||
var canvasRadial = document.createElement('canvas'),
|
var canvasRadial = document.createElement('canvas'),
|
||||||
ctxRadial = canvasRadial.getContext('2d'),
|
ctxRadial = canvasRadial.getContext('2d'),
|
||||||
ri = Math.max(gradient.rx, gradient.ry),
|
ri = Math.max(gradient.rx, gradient.ry),
|
||||||
di = ri * 2, imgRadial;
|
di = ri * 2;
|
||||||
|
|
||||||
canvasRadial.width = canvasRadial.height = di;
|
canvasRadial.width = canvasRadial.height = di;
|
||||||
|
|
||||||
grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
|
grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
|
||||||
|
gradient.colorStops.forEach(addScrollStops(grad));
|
||||||
for (i = 0, len = gradient.colorStops.length; i < len; i+=1) {
|
|
||||||
try {
|
|
||||||
grad.addColorStop(gradient.colorStops[i].stop, gradient.colorStops[i].color);
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
h2clog(['failed to add color stop: ', e, '; tried to add: ', gradient.colorStops[i], '; stop: ', i, '; in: ', src]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxRadial.fillStyle = grad;
|
ctxRadial.fillStyle = grad;
|
||||||
ctxRadial.fillRect(0, 0, di, di);
|
ctxRadial.fillRect(0, 0, di, di);
|
||||||
|
|
||||||
ctx.fillStyle = gradient.colorStops[i - 1].color;
|
ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
|
ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
};
|
};
|
||||||
|
|
||||||
_html2canvas.Generate.ListAlpha = function(number) {
|
Generate.ListAlpha = function(number) {
|
||||||
var tmp = "",
|
var tmp = "",
|
||||||
modulus;
|
modulus;
|
||||||
|
|
||||||
@ -414,7 +401,7 @@
|
|||||||
return tmp;
|
return tmp;
|
||||||
};
|
};
|
||||||
|
|
||||||
_html2canvas.Generate.ListRoman = function(number) {
|
Generate.ListRoman = function(number) {
|
||||||
var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"],
|
var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"],
|
||||||
decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
|
decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
|
||||||
roman = "",
|
roman = "",
|
||||||
@ -433,7 +420,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return roman;
|
return roman;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
315
src/Parse.js
315
src/Parse.js
@ -4,10 +4,11 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
|
var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
|
||||||
numDraws = 0,
|
numDraws = 0,
|
||||||
doc = element.ownerDocument,
|
doc = element.ownerDocument,
|
||||||
support = _html2canvas.Util.Support(options, doc),
|
Util = _html2canvas.Util,
|
||||||
|
support = Util.Support(options, doc),
|
||||||
ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"),
|
ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"),
|
||||||
body = doc.body,
|
body = doc.body,
|
||||||
getCSS = _html2canvas.Util.getCSS,
|
getCSS = Util.getCSS,
|
||||||
pseudoHide = "___html2canvas___pseudoelement",
|
pseudoHide = "___html2canvas___pseudoelement",
|
||||||
hidePseudoElements = doc.createElement('style');
|
hidePseudoElements = doc.createElement('style');
|
||||||
|
|
||||||
@ -47,16 +48,18 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function capitalize(m, p1, p2) {
|
||||||
|
if (m.length > 0) {
|
||||||
|
return p1 + p2.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function textTransform (text, transform) {
|
function textTransform (text, transform) {
|
||||||
switch(transform){
|
switch(transform){
|
||||||
case "lowercase":
|
case "lowercase":
|
||||||
return text.toLowerCase();
|
return text.toLowerCase();
|
||||||
case "capitalize":
|
case "capitalize":
|
||||||
return text.replace( /(^|\s|:|-|\(|\))([a-z])/g , function (m, p1, p2) {
|
return text.replace( /(^|\s|:|-|\(|\))([a-z])/g, capitalize);
|
||||||
if (m.length > 0) {
|
|
||||||
return p1 + p2.toUpperCase();
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
case "uppercase":
|
case "uppercase":
|
||||||
return text.toUpperCase();
|
return text.toUpperCase();
|
||||||
default:
|
default:
|
||||||
@ -69,7 +72,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawText(currentText, x, y, ctx){
|
function drawText(currentText, x, y, ctx){
|
||||||
if (currentText !== null && _html2canvas.Util.trimText(currentText).length > 0) {
|
if (currentText !== null && Util.trimText(currentText).length > 0) {
|
||||||
ctx.fillText(currentText, x, y);
|
ctx.fillText(currentText, x, y);
|
||||||
numDraws+=1;
|
numDraws+=1;
|
||||||
}
|
}
|
||||||
@ -79,7 +82,8 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
var align = false,
|
var align = false,
|
||||||
bold = getCSS(el, "fontWeight"),
|
bold = getCSS(el, "fontWeight"),
|
||||||
family = getCSS(el, "fontFamily"),
|
family = getCSS(el, "fontFamily"),
|
||||||
size = getCSS(el, "fontSize");
|
size = getCSS(el, "fontSize"),
|
||||||
|
shadows = Util.parseTextShadows(getCSS(el, "textShadow"));
|
||||||
|
|
||||||
switch(parseInt(bold, 10)){
|
switch(parseInt(bold, 10)){
|
||||||
case 401:
|
case 401:
|
||||||
@ -94,8 +98,17 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" "));
|
ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" "));
|
||||||
ctx.setVariable("textAlign", (align) ? "right" : "left");
|
ctx.setVariable("textAlign", (align) ? "right" : "left");
|
||||||
|
|
||||||
|
if (shadows.length) {
|
||||||
|
// TODO: support multiple text shadows
|
||||||
|
// apply the first text shadow
|
||||||
|
ctx.setVariable("shadowColor", shadows[0].color);
|
||||||
|
ctx.setVariable("shadowOffsetX", shadows[0].offsetX);
|
||||||
|
ctx.setVariable("shadowOffsetY", shadows[0].offsetY);
|
||||||
|
ctx.setVariable("shadowBlur", shadows[0].blur);
|
||||||
|
}
|
||||||
|
|
||||||
if (text_decoration !== "none"){
|
if (text_decoration !== "none"){
|
||||||
return _html2canvas.Util.Font(family, size, doc);
|
return Util.Font(family, size, doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,16 +129,16 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTextBounds(state, text, textDecoration, isLast) {
|
function getTextBounds(state, text, textDecoration, isLast, transform) {
|
||||||
var bounds;
|
var bounds;
|
||||||
if (support.rangeBounds) {
|
if (support.rangeBounds && !transform) {
|
||||||
if (textDecoration !== "none" || _html2canvas.Util.trimText(text).length !== 0) {
|
if (textDecoration !== "none" || Util.trimText(text).length !== 0) {
|
||||||
bounds = textRangeBounds(text, state.node, state.textOffset);
|
bounds = textRangeBounds(text, state.node, state.textOffset);
|
||||||
}
|
}
|
||||||
state.textOffset += text.length;
|
state.textOffset += text.length;
|
||||||
} else if (state.node && typeof state.node.nodeValue === "string" ){
|
} else if (state.node && typeof state.node.nodeValue === "string" ){
|
||||||
var newTextNode = (isLast) ? state.node.splitText(text.length) : null;
|
var newTextNode = (isLast) ? state.node.splitText(text.length) : null;
|
||||||
bounds = textWrapperBounds(state.node);
|
bounds = textWrapperBounds(state.node, transform);
|
||||||
state.node = newTextNode;
|
state.node = newTextNode;
|
||||||
}
|
}
|
||||||
return bounds;
|
return bounds;
|
||||||
@ -138,7 +151,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
return range.getBoundingClientRect();
|
return range.getBoundingClientRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function textWrapperBounds(oldTextNode) {
|
function textWrapperBounds(oldTextNode, transform) {
|
||||||
var parent = oldTextNode.parentNode,
|
var parent = oldTextNode.parentNode,
|
||||||
wrapElement = doc.createElement('wrapper'),
|
wrapElement = doc.createElement('wrapper'),
|
||||||
backupText = oldTextNode.cloneNode(true);
|
backupText = oldTextNode.cloneNode(true);
|
||||||
@ -146,7 +159,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
wrapElement.appendChild(oldTextNode.cloneNode(true));
|
wrapElement.appendChild(oldTextNode.cloneNode(true));
|
||||||
parent.replaceChild(wrapElement, oldTextNode);
|
parent.replaceChild(wrapElement, oldTextNode);
|
||||||
|
|
||||||
var bounds = _html2canvas.Util.Bounds(wrapElement);
|
var bounds = transform ? Util.OffsetBounds(wrapElement) : Util.Bounds(wrapElement);
|
||||||
parent.replaceChild(backupText, wrapElement);
|
parent.replaceChild(backupText, wrapElement);
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
@ -163,7 +176,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
textOffset: 0
|
textOffset: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_html2canvas.Util.trimText(textNode.nodeValue).length > 0) {
|
if (Util.trimText(textNode.nodeValue).length > 0) {
|
||||||
textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
|
textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
|
||||||
textAlign = textAlign.replace(["-webkit-auto"],["auto"]);
|
textAlign = textAlign.replace(["-webkit-auto"],["auto"]);
|
||||||
|
|
||||||
@ -184,7 +197,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textList.forEach(function(text, index) {
|
textList.forEach(function(text, index) {
|
||||||
var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1));
|
var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
drawText(text, bounds.left, bounds.bottom, ctx);
|
drawText(text, bounds.left, bounds.bottom, ctx);
|
||||||
renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
|
renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
|
||||||
@ -207,20 +220,20 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
|
|
||||||
element.insertBefore(boundElement, element.firstChild);
|
element.insertBefore(boundElement, element.firstChild);
|
||||||
|
|
||||||
bounds = _html2canvas.Util.Bounds(boundElement);
|
bounds = Util.Bounds(boundElement);
|
||||||
element.removeChild(boundElement);
|
element.removeChild(boundElement);
|
||||||
element.style.listStyleType = originalType;
|
element.style.listStyleType = originalType;
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
function elementIndex( el ) {
|
function elementIndex(el) {
|
||||||
var i = -1,
|
var i = -1,
|
||||||
count = 1,
|
count = 1,
|
||||||
childs = el.parentNode.childNodes;
|
childs = el.parentNode.childNodes;
|
||||||
|
|
||||||
if (el.parentNode) {
|
if (el.parentNode) {
|
||||||
while( childs[ ++i ] !== el ) {
|
while(childs[++i] !== el) {
|
||||||
if ( childs[ i ].nodeType === 1 ) {
|
if (childs[i].nodeType === 1) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,8 +244,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listItemText(element, type) {
|
function listItemText(element, type) {
|
||||||
var currentIndex = elementIndex(element),
|
var currentIndex = elementIndex(element), text;
|
||||||
text;
|
|
||||||
switch(type){
|
switch(type){
|
||||||
case "decimal":
|
case "decimal":
|
||||||
text = currentIndex;
|
text = currentIndex;
|
||||||
@ -254,8 +266,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
text += ". ";
|
return text + ". ";
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderListItem(element, stack, elBounds) {
|
function renderListItem(element, stack, elBounds) {
|
||||||
@ -283,11 +294,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
|
|
||||||
function loadImage (src){
|
function loadImage (src){
|
||||||
var img = images[src];
|
var img = images[src];
|
||||||
if (img && img.succeeded === true) {
|
return (img && img.succeeded === true) ? img.img : false;
|
||||||
return img.img;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clipBounds(src, dst){
|
function clipBounds(src, dst){
|
||||||
@ -304,22 +311,29 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setZ(zIndex, parentZ){
|
function setZ(element, stack, parentStack){
|
||||||
// TODO fix static elements overlapping relative/absolute elements under same stack, if they are defined after them
|
var newContext,
|
||||||
var newContext;
|
isPositioned = stack.cssPosition !== 'static',
|
||||||
if (!parentZ){
|
zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto',
|
||||||
newContext = h2czContext(0);
|
opacity = getCSS(element, 'opacity'),
|
||||||
return newContext;
|
isFloated = getCSS(element, 'cssFloat') !== 'none';
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
|
||||||
|
// When a new stacking context should be created:
|
||||||
|
// the root element (HTML),
|
||||||
|
// positioned (absolutely or relatively) with a z-index value other than "auto",
|
||||||
|
// elements with an opacity value less than 1. (See the specification for opacity),
|
||||||
|
// on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post)
|
||||||
|
|
||||||
|
stack.zIndex = newContext = h2czContext(zIndex);
|
||||||
|
newContext.isPositioned = isPositioned;
|
||||||
|
newContext.isFloated = isFloated;
|
||||||
|
newContext.opacity = opacity;
|
||||||
|
newContext.ownStacking = (zIndex !== 'auto' || opacity < 1);
|
||||||
|
|
||||||
|
if (parentStack) {
|
||||||
|
parentStack.zIndex.children.push(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zIndex !== "auto"){
|
|
||||||
newContext = h2czContext(zIndex);
|
|
||||||
parentZ.children.push(newContext);
|
|
||||||
return newContext;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return parentZ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderImage(ctx, element, image, bounds, borders) {
|
function renderImage(ctx, element, image, bounds, borders) {
|
||||||
@ -509,8 +523,8 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
tlv = borderRadius[0][1],
|
tlv = borderRadius[0][1],
|
||||||
trh = borderRadius[1][0],
|
trh = borderRadius[1][0],
|
||||||
trv = borderRadius[1][1],
|
trv = borderRadius[1][1],
|
||||||
brv = borderRadius[2][0],
|
brh = borderRadius[2][0],
|
||||||
brh = borderRadius[2][1],
|
brv = borderRadius[2][1],
|
||||||
blh = borderRadius[3][0],
|
blh = borderRadius[3][0],
|
||||||
blv = borderRadius[3][1],
|
blv = borderRadius[3][1],
|
||||||
|
|
||||||
@ -664,7 +678,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
c1: [bx + bw, by + bh],
|
c1: [bx + bw, by + bh],
|
||||||
c2: [bx, by + bh],
|
c2: [bx, by + bh],
|
||||||
c3: [bx + borders[3].width, by],
|
c3: [bx + borders[3].width, by],
|
||||||
c4: [bx + bw - borders[2].width, by]
|
c4: [bx + bw - borders[3].width, by]
|
||||||
}, borderRadius[2], borderRadius[3],
|
}, borderRadius[2], borderRadius[3],
|
||||||
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
|
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
|
||||||
break;
|
break;
|
||||||
@ -722,7 +736,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
valueWrap.style[property] = getCSS(el, property);
|
valueWrap.style[property] = getCSS(el, property);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
// Older IE has issues with "border"
|
// Older IE has issues with "border"
|
||||||
h2clog("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
|
Util.log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -759,7 +773,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
|
|
||||||
function getPseudoElement(el, which) {
|
function getPseudoElement(el, which) {
|
||||||
var elStyle = window.getComputedStyle(el, which);
|
var elStyle = window.getComputedStyle(el, which);
|
||||||
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content") {
|
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var content = elStyle.content + '',
|
var content = elStyle.content + '',
|
||||||
@ -775,11 +789,16 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
elps.className = pseudoHide + "-before " + pseudoHide + "-after";
|
elps.className = pseudoHide + "-before " + pseudoHide + "-after";
|
||||||
|
|
||||||
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
|
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
|
||||||
|
// Prevent assigning of read only CSS Rules, ex. length, parentRule
|
||||||
|
try {
|
||||||
elps.style[prop] = elStyle[prop];
|
elps.style[prop] = elStyle[prop];
|
||||||
|
} catch (e) {
|
||||||
|
Util.log(['Tried to assign readonly property ', prop, 'Error:', e]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(isImage) {
|
if(isImage) {
|
||||||
elps.src = _html2canvas.Util.parseBackgroundImage(content)[0].args[0];
|
elps.src = Util.parseBackgroundImage(content)[0].args[0];
|
||||||
} else {
|
} else {
|
||||||
elps.innerHTML = content;
|
elps.innerHTML = content;
|
||||||
}
|
}
|
||||||
@ -850,11 +869,9 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) {
|
function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) {
|
||||||
var backgroundSize = _html2canvas.Util.BackgroundSize(el, bounds, image, imageIndex),
|
var backgroundSize = Util.BackgroundSize(el, bounds, image, imageIndex),
|
||||||
backgroundPosition = _html2canvas.Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
|
backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
|
||||||
backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(function(value) {
|
backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(Util.trimText);
|
||||||
return value.trim();
|
|
||||||
});
|
|
||||||
|
|
||||||
image = resizeImage(image, backgroundSize);
|
image = resizeImage(image, backgroundSize);
|
||||||
|
|
||||||
@ -889,7 +906,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
|
|
||||||
function renderBackgroundImage(element, bounds, ctx) {
|
function renderBackgroundImage(element, bounds, ctx) {
|
||||||
var backgroundImage = getCSS(element, "backgroundImage"),
|
var backgroundImage = getCSS(element, "backgroundImage"),
|
||||||
backgroundImages = _html2canvas.Util.parseBackgroundImage(backgroundImage),
|
backgroundImages = Util.parseBackgroundImage(backgroundImage),
|
||||||
image,
|
image,
|
||||||
imageIndex = backgroundImages.length;
|
imageIndex = backgroundImages.length;
|
||||||
|
|
||||||
@ -910,7 +927,7 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
if (image) {
|
if (image) {
|
||||||
renderBackgroundRepeating(element, bounds, ctx, image, imageIndex);
|
renderBackgroundRepeating(element, bounds, ctx, image, imageIndex);
|
||||||
} else {
|
} else {
|
||||||
h2clog("html2canvas: Error loading background:", backgroundImage);
|
Util.log("html2canvas: Error loading background:", backgroundImage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -929,30 +946,57 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setOpacity(ctx, element, parentStack) {
|
function setOpacity(ctx, element, parentStack) {
|
||||||
var opacity = getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1);
|
return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1));
|
||||||
ctx.setVariable("globalAlpha", opacity);
|
|
||||||
return opacity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStack(element, parentStack, bounds) {
|
function removePx(str) {
|
||||||
|
return str.replace("px", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
var transformRegExp = /(matrix)\((.+)\)/;
|
||||||
|
|
||||||
|
function getTransform(element, parentStack) {
|
||||||
|
var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
|
||||||
|
var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";
|
||||||
|
|
||||||
|
transformOrigin = transformOrigin.split(" ").map(removePx).map(Util.asFloat);
|
||||||
|
|
||||||
|
var matrix;
|
||||||
|
if (transform && transform !== "none") {
|
||||||
|
var match = transform.match(transformRegExp);
|
||||||
|
if (match) {
|
||||||
|
switch(match[1]) {
|
||||||
|
case "matrix":
|
||||||
|
matrix = match[2].split(",").map(Util.trimText).map(Util.asFloat);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
origin: transformOrigin,
|
||||||
|
matrix: matrix
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStack(element, parentStack, bounds, transform) {
|
||||||
var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height),
|
var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height),
|
||||||
stack = {
|
stack = {
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
zIndex: setZ(getCSS(element, "zIndex"), (parentStack) ? parentStack.zIndex : null),
|
|
||||||
opacity: setOpacity(ctx, element, parentStack),
|
opacity: setOpacity(ctx, element, parentStack),
|
||||||
cssPosition: getCSS(element, "position"),
|
cssPosition: getCSS(element, "position"),
|
||||||
borders: getBorderData(element),
|
borders: getBorderData(element),
|
||||||
clip: (parentStack && parentStack.clip) ? _html2canvas.Util.Extend( {}, parentStack.clip ) : null
|
transform: transform,
|
||||||
|
clip: (parentStack && parentStack.clip) ? Util.Extend( {}, parentStack.clip ) : null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setZ(element, stack, parentStack);
|
||||||
|
|
||||||
// TODO correct overflow for absolute content residing under a static position
|
// TODO correct overflow for absolute content residing under a static position
|
||||||
if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){
|
if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){
|
||||||
stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds;
|
stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.zIndex.children.push(stack);
|
|
||||||
|
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -971,24 +1015,35 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
return backgroundBounds;
|
return backgroundBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderElement(element, parentStack, pseudoElement){
|
function getBounds(element, transform) {
|
||||||
var bounds = _html2canvas.Util.Bounds(element),
|
var bounds = (transform.matrix) ? Util.OffsetBounds(element) : Util.Bounds(element);
|
||||||
|
transform.origin[0] += bounds.left;
|
||||||
|
transform.origin[1] += bounds.top;
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderElement(element, parentStack, pseudoElement, ignoreBackground) {
|
||||||
|
var transform = getTransform(element, parentStack),
|
||||||
|
bounds = getBounds(element, transform),
|
||||||
image,
|
image,
|
||||||
bgcolor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor"),
|
stack = createStack(element, parentStack, bounds, transform),
|
||||||
stack = createStack(element, parentStack, bounds),
|
|
||||||
borders = stack.borders,
|
borders = stack.borders,
|
||||||
ctx = stack.ctx,
|
ctx = stack.ctx,
|
||||||
backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
|
backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
|
||||||
borderData = parseBorders(element, bounds, borders);
|
borderData = parseBorders(element, bounds, borders),
|
||||||
|
backgroundColor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor");
|
||||||
|
|
||||||
|
|
||||||
createShape(ctx, borderData.clip);
|
createShape(ctx, borderData.clip);
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
|
|
||||||
if (backgroundBounds.height > 0 && backgroundBounds.width > 0){
|
if (backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) {
|
||||||
renderBackgroundColor(ctx, bounds, bgcolor);
|
renderBackgroundColor(ctx, bounds, backgroundColor);
|
||||||
renderBackgroundImage(element, backgroundBounds, ctx);
|
renderBackgroundImage(element, backgroundBounds, ctx);
|
||||||
|
} else if (ignoreBackground) {
|
||||||
|
stack.backgroundColor = backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
@ -1006,13 +1061,13 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
if ((image = loadImage(element.getAttribute('src')))) {
|
if ((image = loadImage(element.getAttribute('src')))) {
|
||||||
renderImage(ctx, element, image, bounds, borders);
|
renderImage(ctx, element, image, bounds, borders);
|
||||||
} else {
|
} else {
|
||||||
h2clog("html2canvas: Error loading <img>:" + element.getAttribute('src'));
|
Util.log("html2canvas: Error loading <img>:" + element.getAttribute('src'));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "INPUT":
|
case "INPUT":
|
||||||
// TODO add all relevant type's, i.e. HTML5 new stuff
|
// TODO add all relevant type's, i.e. HTML5 new stuff
|
||||||
// todo add support for placeholder attribute for browsers which support it
|
// todo add support for placeholder attribute for browsers which support it
|
||||||
if (/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder).length > 0){
|
if (/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder || "").length > 0){
|
||||||
renderFormValue(element, bounds, stack);
|
renderFormValue(element, bounds, stack);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -1041,102 +1096,40 @@ _html2canvas.Parse = function (images, options) {
|
|||||||
return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
|
return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseElement (el, stack, pseudoElement) {
|
function parseElement (element, stack, pseudoElement) {
|
||||||
|
if (isElementVisible(element)) {
|
||||||
|
stack = renderElement(element, stack, pseudoElement, false) || stack;
|
||||||
|
if (!ignoreElementsRegExp.test(element.nodeName)) {
|
||||||
|
parseChildren(element, stack, pseudoElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isElementVisible(el)) {
|
function parseChildren(element, stack, pseudoElement) {
|
||||||
stack = renderElement(el, stack, pseudoElement) || stack;
|
Util.Children(element).forEach(function(node) {
|
||||||
if (!ignoreElementsRegExp.test(el.nodeName)) {
|
if (node.nodeType === node.ELEMENT_NODE) {
|
||||||
_html2canvas.Util.Children(el).forEach(function(node) {
|
|
||||||
if (node.nodeType === 1) {
|
|
||||||
parseElement(node, stack, pseudoElement);
|
parseElement(node, stack, pseudoElement);
|
||||||
} else if (node.nodeType === 3) {
|
} else if (node.nodeType === node.TEXT_NODE) {
|
||||||
renderText(el, node, stack);
|
renderText(element, node, stack);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function svgDOMRender(body, stack) {
|
|
||||||
var img = new Image(),
|
|
||||||
docWidth = documentWidth(),
|
|
||||||
docHeight = documentHeight(),
|
|
||||||
html = "";
|
|
||||||
|
|
||||||
function parseDOM(el) {
|
|
||||||
var children = _html2canvas.Util.Children( el ),
|
|
||||||
len = children.length,
|
|
||||||
attr,
|
|
||||||
a,
|
|
||||||
alen,
|
|
||||||
elm,
|
|
||||||
i;
|
|
||||||
for ( i = 0; i < len; i+=1 ) {
|
|
||||||
elm = children[ i ];
|
|
||||||
if ( elm.nodeType === 3 ) {
|
|
||||||
// Text node
|
|
||||||
html += elm.nodeValue.replace(/</g,"<").replace(/>/g,">");
|
|
||||||
} else if ( elm.nodeType === 1 ) {
|
|
||||||
// Element
|
|
||||||
if ( !/^(script|meta|title)$/.test(elm.nodeName.toLowerCase()) ) {
|
|
||||||
|
|
||||||
html += "<" + elm.nodeName.toLowerCase();
|
|
||||||
|
|
||||||
// add attributes
|
|
||||||
if ( elm.hasAttributes() ) {
|
|
||||||
attr = elm.attributes;
|
|
||||||
alen = attr.length;
|
|
||||||
for ( a = 0; a < alen; a+=1 ) {
|
|
||||||
html += " " + attr[ a ].name + '="' + attr[ a ].value + '"';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
html += '>';
|
|
||||||
|
|
||||||
parseDOM( elm );
|
|
||||||
|
|
||||||
|
|
||||||
html += "</" + elm.nodeName.toLowerCase() + ">";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
parseDOM(body);
|
|
||||||
img.src = [
|
|
||||||
"data:image/svg+xml,",
|
|
||||||
"<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='" + docWidth + "' height='" + docHeight + "'>",
|
|
||||||
"<foreignObject width='" + docWidth + "' height='" + docHeight + "'>",
|
|
||||||
"<html xmlns='http://www.w3.org/1999/xhtml' style='margin:0;'>",
|
|
||||||
html.replace(/\#/g,"%23"),
|
|
||||||
"</html>",
|
|
||||||
"</foreignObject>",
|
|
||||||
"</svg>"
|
|
||||||
].join("");
|
|
||||||
|
|
||||||
img.onload = function() {
|
|
||||||
stack.svgRender = img;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var stack = renderElement(element, null);
|
var background = getCSS(document.documentElement, "backgroundColor"),
|
||||||
|
transparentBackground = (Util.isTransparent(background) && element === document.body),
|
||||||
|
stack = renderElement(element, null, false, transparentBackground);
|
||||||
|
parseChildren(element, stack);
|
||||||
|
|
||||||
if (support.svgRendering) {
|
if (transparentBackground) {
|
||||||
svgDOMRender(document.documentElement, stack);
|
background = stack.backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.prototype.slice.call(element.children, 0).forEach(function(childElement) {
|
|
||||||
parseElement(childElement, stack);
|
|
||||||
});
|
|
||||||
|
|
||||||
stack.backgroundColor = getCSS(document.documentElement, "backgroundColor");
|
|
||||||
body.removeChild(hidePseudoElements);
|
body.removeChild(hidePseudoElements);
|
||||||
return stack;
|
return {
|
||||||
|
backgroundColor: background,
|
||||||
|
stack: stack
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return init();
|
return init();
|
||||||
|
@ -7,12 +7,13 @@ _html2canvas.Preload = function( options ) {
|
|||||||
cleanupDone: false
|
cleanupDone: false
|
||||||
},
|
},
|
||||||
pageOrigin,
|
pageOrigin,
|
||||||
|
Util = _html2canvas.Util,
|
||||||
methods,
|
methods,
|
||||||
i,
|
i,
|
||||||
count = 0,
|
count = 0,
|
||||||
element = options.elements[0] || document.body,
|
element = options.elements[0] || document.body,
|
||||||
doc = element.ownerDocument,
|
doc = element.ownerDocument,
|
||||||
domImages = doc.images, // TODO probably should limit it to images present in the element only
|
domImages = element.getElementsByTagName('img'), // Fetch images of the present element only
|
||||||
imgLen = domImages.length,
|
imgLen = domImages.length,
|
||||||
link = doc.createElement("a"),
|
link = doc.createElement("a"),
|
||||||
supportCORS = (function( img ){
|
supportCORS = (function( img ){
|
||||||
@ -31,9 +32,9 @@ _html2canvas.Preload = function( options ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function start(){
|
function start(){
|
||||||
h2clog("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")");
|
Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")");
|
||||||
if (!images.firstRun && images.numLoaded >= images.numTotal){
|
if (!images.firstRun && images.numLoaded >= images.numTotal){
|
||||||
h2clog("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");
|
Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");
|
||||||
|
|
||||||
if (typeof options.complete === "function"){
|
if (typeof options.complete === "function"){
|
||||||
options.complete(images);
|
options.complete(images);
|
||||||
@ -141,9 +142,7 @@ _html2canvas.Preload = function( options ) {
|
|||||||
|
|
||||||
// Firefox fails with permission denied on pages with iframes
|
// Firefox fails with permission denied on pages with iframes
|
||||||
try {
|
try {
|
||||||
_html2canvas.Util.Children(el).forEach(function(img) {
|
Util.Children(el).forEach(getImages);
|
||||||
getImages(img);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch( e ) {}
|
catch( e ) {}
|
||||||
|
|
||||||
@ -151,15 +150,15 @@ _html2canvas.Preload = function( options ) {
|
|||||||
elNodeType = el.nodeType;
|
elNodeType = el.nodeType;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
elNodeType = false;
|
elNodeType = false;
|
||||||
h2clog("html2canvas: failed to access some element's nodeType - Exception: " + ex.message);
|
Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elNodeType === 1 || elNodeType === undefined) {
|
if (elNodeType === 1 || elNodeType === undefined) {
|
||||||
loadPseudoElementImages(el);
|
loadPseudoElementImages(el);
|
||||||
try {
|
try {
|
||||||
loadBackgroundImages(_html2canvas.Util.getCSS(el, 'backgroundImage'), el);
|
loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
h2clog("html2canvas: failed to get background-image - Exception: " + e.message);
|
Util.log("html2canvas: failed to get background-image - Exception: " + e.message);
|
||||||
}
|
}
|
||||||
loadBackgroundImages(el);
|
loadBackgroundImages(el);
|
||||||
}
|
}
|
||||||
@ -231,17 +230,6 @@ _html2canvas.Preload = function( options ) {
|
|||||||
images.numTotal++;
|
images.numTotal++;
|
||||||
setImageLoadHandlers(img, imageObj);
|
setImageLoadHandlers(img, imageObj);
|
||||||
img.src = src;
|
img.src = src;
|
||||||
|
|
||||||
// work around for https://bugs.webkit.org/show_bug.cgi?id=80028
|
|
||||||
img.customComplete = function () {
|
|
||||||
if (!this.img.complete) {
|
|
||||||
this.timer = window.setTimeout(this.img.customComplete, 100);
|
|
||||||
} else {
|
|
||||||
this.img.onerror();
|
|
||||||
}
|
|
||||||
}.bind(imageObj);
|
|
||||||
img.customComplete();
|
|
||||||
|
|
||||||
} else if ( options.proxy ) {
|
} else if ( options.proxy ) {
|
||||||
imageObj = images[src] = {
|
imageObj = images[src] = {
|
||||||
img: img
|
img: img
|
||||||
@ -256,9 +244,9 @@ _html2canvas.Preload = function( options ) {
|
|||||||
var img, src;
|
var img, src;
|
||||||
if (!images.cleanupDone) {
|
if (!images.cleanupDone) {
|
||||||
if (cause && typeof cause === "string") {
|
if (cause && typeof cause === "string") {
|
||||||
h2clog("html2canvas: Cleanup because: " + cause);
|
Util.log("html2canvas: Cleanup because: " + cause);
|
||||||
} else {
|
} else {
|
||||||
h2clog("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
|
Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (src in images) {
|
for (src in images) {
|
||||||
@ -276,7 +264,7 @@ _html2canvas.Preload = function( options ) {
|
|||||||
}
|
}
|
||||||
images.numLoaded++;
|
images.numLoaded++;
|
||||||
images.numFailed++;
|
images.numFailed++;
|
||||||
h2clog("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal);
|
Util.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -308,23 +296,22 @@ _html2canvas.Preload = function( options ) {
|
|||||||
timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout);
|
timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2clog('html2canvas: Preload starts: finding background-images');
|
Util.log('html2canvas: Preload starts: finding background-images');
|
||||||
images.firstRun = true;
|
images.firstRun = true;
|
||||||
|
|
||||||
getImages(element);
|
getImages(element);
|
||||||
|
|
||||||
h2clog('html2canvas: Preload: Finding images');
|
Util.log('html2canvas: Preload: Finding images');
|
||||||
// load <img> images
|
// load <img> images
|
||||||
for (i = 0; i < imgLen; i+=1){
|
for (i = 0; i < imgLen; i+=1){
|
||||||
methods.loadImage( domImages[i].getAttribute( "src" ) );
|
methods.loadImage( domImages[i].getAttribute( "src" ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
images.firstRun = false;
|
images.firstRun = false;
|
||||||
h2clog('html2canvas: Preload: Done.');
|
Util.log('html2canvas: Preload: Done.');
|
||||||
if ( images.numTotal === images.numLoaded ) {
|
if (images.numTotal === images.numLoaded) {
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
return methods;
|
return methods;
|
||||||
|
|
||||||
};
|
};
|
@ -117,6 +117,7 @@ function h2cRenderContext(width, height) {
|
|||||||
name: variable,
|
name: variable,
|
||||||
'arguments': value
|
'arguments': value
|
||||||
});
|
});
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -1,38 +1,81 @@
|
|||||||
_html2canvas.Renderer = function(parseQueue, options){
|
_html2canvas.Renderer = function(parseQueue, options){
|
||||||
|
|
||||||
|
// http://www.w3.org/TR/CSS21/zindex.html
|
||||||
function createRenderQueue(parseQueue) {
|
function createRenderQueue(parseQueue) {
|
||||||
var queue = [];
|
var queue = [],
|
||||||
|
rootContext;
|
||||||
|
|
||||||
var sortZ = function(zStack){
|
rootContext = (function buildStackingContext(rootNode) {
|
||||||
var subStacks = [],
|
var rootContext = {};
|
||||||
stackValues = [];
|
function insert(context, node, specialParent) {
|
||||||
|
var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex),
|
||||||
|
contextForChildren = context, // the stacking context for children
|
||||||
|
isPositioned = node.zIndex.isPositioned,
|
||||||
|
isFloated = node.zIndex.isFloated,
|
||||||
|
stub = {node: node},
|
||||||
|
childrenDest = specialParent; // where children without z-index should be pushed into
|
||||||
|
|
||||||
zStack.children.forEach(function(stackChild) {
|
if (node.zIndex.ownStacking) {
|
||||||
if (stackChild.children && stackChild.children.length > 0){
|
// '!' comes before numbers in sorted array
|
||||||
subStacks.push(stackChild);
|
contextForChildren = stub.context = { '!': [{node:node, children: []}]};
|
||||||
stackValues.push(stackChild.zindex);
|
childrenDest = undefined;
|
||||||
|
} else if (isPositioned || isFloated) {
|
||||||
|
childrenDest = stub.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zi === 0 && specialParent) {
|
||||||
|
specialParent.push(stub);
|
||||||
} else {
|
} else {
|
||||||
queue.push(stackChild);
|
if (!context[zi]) { context[zi] = []; }
|
||||||
|
context[zi].push(stub);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.zIndex.children.forEach(function(childNode) {
|
||||||
|
insert(contextForChildren, childNode, childrenDest);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
insert(rootContext, rootNode);
|
||||||
|
return rootContext;
|
||||||
|
})(parseQueue);
|
||||||
|
|
||||||
|
function sortZ(context) {
|
||||||
|
Object.keys(context).sort().forEach(function(zi) {
|
||||||
|
var nonPositioned = [],
|
||||||
|
floated = [],
|
||||||
|
positioned = [],
|
||||||
|
list = [];
|
||||||
|
|
||||||
|
// positioned after static
|
||||||
|
context[zi].forEach(function(v) {
|
||||||
|
if (v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) {
|
||||||
|
// http://www.w3.org/TR/css3-color/#transparency
|
||||||
|
// non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’.
|
||||||
|
positioned.push(v);
|
||||||
|
} else if (v.node.zIndex.isFloated) {
|
||||||
|
floated.push(v);
|
||||||
|
} else {
|
||||||
|
nonPositioned.push(v);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stackValues.sort(function(a, b) {
|
(function walk(arr) {
|
||||||
return a - b;
|
arr.forEach(function(v) {
|
||||||
|
list.push(v);
|
||||||
|
if (v.children) { walk(v.children); }
|
||||||
});
|
});
|
||||||
|
})(nonPositioned.concat(floated, positioned));
|
||||||
|
|
||||||
stackValues.forEach(function(zValue) {
|
list.forEach(function(v) {
|
||||||
var index;
|
if (v.context) {
|
||||||
|
sortZ(v.context);
|
||||||
subStacks.some(function(stack, i){
|
} else {
|
||||||
index = i;
|
queue.push(v.node);
|
||||||
return (stack.zindex === zValue);
|
}
|
||||||
});
|
});
|
||||||
sortZ(subStacks.splice(index, 1)[0]);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
sortZ(parseQueue.zIndex);
|
sortZ(rootContext);
|
||||||
|
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
@ -54,5 +97,5 @@ _html2canvas.Renderer = function(parseQueue, options){
|
|||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue), _html2canvas);
|
return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue.stack), _html2canvas);
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,7 @@ _html2canvas.Util.Support = function (options, doc) {
|
|||||||
} catch(e) {
|
} catch(e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
h2clog('html2canvas: Parse: SVG powered rendering available');
|
_html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +71,11 @@ window.html2canvas = function(elements, opts) {
|
|||||||
preload: function( opts ) {
|
preload: function( opts ) {
|
||||||
return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) );
|
return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) );
|
||||||
},
|
},
|
||||||
log: h2clog
|
log: _html2canvas.Util.log
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
window.html2canvas.log = h2clog; // for renderers
|
window.html2canvas.log = _html2canvas.Util.log; // for renderers
|
||||||
window.html2canvas.Renderer = {
|
window.html2canvas.Renderer = {
|
||||||
Canvas: undefined // We are assuming this will be used
|
Canvas: undefined // We are assuming this will be used
|
||||||
};
|
};
|
@ -1,14 +1,13 @@
|
|||||||
_html2canvas.Renderer.Canvas = function(options) {
|
_html2canvas.Renderer.Canvas = function(options) {
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
var doc = document,
|
var doc = document,
|
||||||
safeImages = [],
|
safeImages = [],
|
||||||
testCanvas = document.createElement("canvas"),
|
testCanvas = document.createElement("canvas"),
|
||||||
testctx = testCanvas.getContext("2d"),
|
testctx = testCanvas.getContext("2d"),
|
||||||
|
Util = _html2canvas.Util,
|
||||||
canvas = options.canvas || doc.createElement('canvas');
|
canvas = options.canvas || doc.createElement('canvas');
|
||||||
|
|
||||||
|
|
||||||
function createShape(ctx, args) {
|
function createShape(ctx, args) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
args.forEach(function(arg) {
|
args.forEach(function(arg) {
|
||||||
@ -32,99 +31,90 @@ _html2canvas.Renderer.Canvas = function(options) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTransparent(backgroundColor) {
|
|
||||||
return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderItem(ctx, item) {
|
function renderItem(ctx, item) {
|
||||||
switch(item.type){
|
switch(item.type){
|
||||||
case "variable":
|
case "variable":
|
||||||
ctx[item.name] = item['arguments'];
|
ctx[item.name] = item['arguments'];
|
||||||
break;
|
break;
|
||||||
case "function":
|
case "function":
|
||||||
if (item.name === "createPattern") {
|
switch(item.name) {
|
||||||
|
case "createPattern":
|
||||||
if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
|
if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
|
||||||
try {
|
try {
|
||||||
ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
|
ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
h2clog("html2canvas: Renderer: Error creating pattern", e.message);
|
Util.log("html2canvas: Renderer: Error creating pattern", e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (item.name === "drawShape") {
|
break;
|
||||||
|
case "drawShape":
|
||||||
createShape(ctx, item['arguments']);
|
createShape(ctx, item['arguments']);
|
||||||
} else if (item.name === "drawImage") {
|
break;
|
||||||
|
case "drawImage":
|
||||||
if (item['arguments'][8] > 0 && item['arguments'][7] > 0) {
|
if (item['arguments'][8] > 0 && item['arguments'][7] > 0) {
|
||||||
if (!options.taintTest || (options.taintTest && safeImage(item))) {
|
if (!options.taintTest || (options.taintTest && safeImage(item))) {
|
||||||
ctx.drawImage.apply( ctx, item['arguments'] );
|
ctx.drawImage.apply( ctx, item['arguments'] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
|
default:
|
||||||
ctx[item.name].apply(ctx, item['arguments']);
|
ctx[item.name].apply(ctx, item['arguments']);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return function(zStack, options, doc, queue, _html2canvas) {
|
return function(parsedData, options, document, queue, _html2canvas) {
|
||||||
|
|
||||||
var ctx = canvas.getContext("2d"),
|
var ctx = canvas.getContext("2d"),
|
||||||
storageContext,
|
|
||||||
i,
|
|
||||||
queueLen,
|
|
||||||
newCanvas,
|
newCanvas,
|
||||||
bounds,
|
bounds,
|
||||||
fstyle;
|
fstyle,
|
||||||
|
zStack = parsedData.stack;
|
||||||
|
|
||||||
canvas.width = canvas.style.width = options.width || zStack.ctx.width;
|
canvas.width = canvas.style.width = options.width || zStack.ctx.width;
|
||||||
canvas.height = canvas.style.height = options.height || zStack.ctx.height;
|
canvas.height = canvas.style.height = options.height || zStack.ctx.height;
|
||||||
|
|
||||||
fstyle = ctx.fillStyle;
|
fstyle = ctx.fillStyle;
|
||||||
ctx.fillStyle = (isTransparent(zStack.backgroundColor) && options.background !== undefined) ? options.background : zStack.backgroundColor;
|
ctx.fillStyle = (Util.isTransparent(zStack.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.fillStyle = fstyle;
|
ctx.fillStyle = fstyle;
|
||||||
|
|
||||||
|
queue.forEach(function(storageContext) {
|
||||||
if ( options.svgRendering && zStack.svgRender !== undefined ) {
|
|
||||||
// TODO: enable async rendering to support this
|
|
||||||
ctx.drawImage( zStack.svgRender, 0, 0 );
|
|
||||||
} else {
|
|
||||||
for ( i = 0, queueLen = queue.length; i < queueLen; i+=1 ) {
|
|
||||||
storageContext = queue.splice(0, 1)[0];
|
|
||||||
storageContext.canvasPosition = storageContext.canvasPosition || {};
|
|
||||||
|
|
||||||
// set common settings for canvas
|
// set common settings for canvas
|
||||||
ctx.textBaseline = "bottom";
|
ctx.textBaseline = "bottom";
|
||||||
|
ctx.save();
|
||||||
|
|
||||||
|
if (storageContext.transform.matrix) {
|
||||||
|
ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]);
|
||||||
|
ctx.transform.apply(ctx, storageContext.transform.matrix);
|
||||||
|
ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]);
|
||||||
|
}
|
||||||
|
|
||||||
if (storageContext.clip){
|
if (storageContext.clip){
|
||||||
ctx.save();
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
// console.log(storageContext);
|
|
||||||
ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
|
ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storageContext.ctx.storage) {
|
if (storageContext.ctx.storage) {
|
||||||
storageContext.ctx.storage.forEach(renderItem.bind(null, ctx));
|
storageContext.ctx.storage.forEach(function(item) {
|
||||||
|
renderItem(ctx, item);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storageContext.clip){
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h2clog("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
|
Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
|
||||||
|
|
||||||
queueLen = options.elements.length;
|
if (options.elements.length === 1) {
|
||||||
|
|
||||||
if (queueLen === 1) {
|
|
||||||
if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
|
if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
|
||||||
// crop image to the bounds of selected (single) element
|
// crop image to the bounds of selected (single) element
|
||||||
bounds = _html2canvas.Util.Bounds(options.elements[0]);
|
bounds = _html2canvas.Util.Bounds(options.elements[0]);
|
||||||
newCanvas = doc.createElement('canvas');
|
newCanvas = document.createElement('canvas');
|
||||||
newCanvas.width = bounds.width;
|
newCanvas.width = Math.ceil(bounds.width);
|
||||||
newCanvas.height = bounds.height;
|
newCanvas.height = Math.ceil(bounds.height);
|
||||||
ctx = newCanvas.getContext("2d");
|
ctx = newCanvas.getContext("2d");
|
||||||
|
|
||||||
ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
|
ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
|
||||||
|
@ -194,7 +194,7 @@ _html2canvas.Renderer.SVG = function( options ) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
h2clog("html2canvas: Renderer: SVG Renderer done - returning SVG DOM obj");
|
_html2canvas.Util.log("html2canvas: Renderer: SVG Renderer done - returning SVG DOM obj");
|
||||||
|
|
||||||
return svg;
|
return svg;
|
||||||
}
|
}
|
||||||
|
22
tests/cases/child-textnodes.html
Normal file
22
tests/cases/child-textnodes.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Inline text in the top element</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../test.js"></script>
|
||||||
|
<style>
|
||||||
|
span {
|
||||||
|
color:blue;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Some inline text <span> followed by text in span </span> followed by more inline text.
|
||||||
|
<p>Then a block level element.</p>
|
||||||
|
Then more inline text.
|
||||||
|
</body>
|
||||||
|
</html>
|
10
tests/cases/iframe.html
Normal file
10
tests/cases/iframe.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>iframe test</title>
|
||||||
|
<script type="text/javascript" src="../test.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe src="http://experiments.hertzen.com/csstree/" width="500"></iframe>
|
||||||
|
</body>
|
||||||
|
</html>
|
21
tests/cases/text/shadow.html
Normal file
21
tests/cases/text/shadow.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Text shadow tests</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style>
|
||||||
|
span{
|
||||||
|
text-shadow: 1px 1px 3px #888;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
text-shadow: 1px 1px 2px black, 0 0 1em blue, 0 0 0.2em blue;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Some text <span> followed by text with shadow </span> followed by more text without shadow.
|
||||||
|
<strong>Multi text shadow</strong> and some more text without shadow
|
||||||
|
</body>
|
||||||
|
</html>
|
40
tests/cases/transform/nested.html
Normal file
40
tests/cases/transform/nested.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Nested transform tests</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style>
|
||||||
|
#first {
|
||||||
|
background: indianred;
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
||||||
|
#second {
|
||||||
|
border: 10px solid red;
|
||||||
|
background: darkseagreen;
|
||||||
|
-webkit-transform: rotate(7.5deg); /* Chrome, Safari 3.1+ */
|
||||||
|
-moz-transform: rotate(7.5deg); /* Firefox 3.5-15 */
|
||||||
|
-ms-transform: rotate(7.5deg); /* IE 9 */
|
||||||
|
-o-transform: rotate(7.5deg); /* Opera 10.50-12.00 */
|
||||||
|
transform: rotate(7.5deg);
|
||||||
|
}
|
||||||
|
#third {
|
||||||
|
background: cadetblue;
|
||||||
|
-webkit-transform: rotate(-70.5deg); /* Chrome, Safari 3.1+ */
|
||||||
|
-moz-transform: rotate(-70.5deg); /* Firefox 3.5-15 */
|
||||||
|
-ms-transform: rotate(-70.5deg); /* IE 9 */
|
||||||
|
-o-transform: rotate(-70.5deg); /* Opera 10.50-12.00 */
|
||||||
|
transform: rotate(-70.5deg); /* Firefox 16+, IE 10+, Opera 12.10+ */
|
||||||
|
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="first">First level content <div id="second">with second level content <div id="third">and third level content</div>, ending second</div>, ending first</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
120
tests/cases/zindex/z-index10.html
Normal file
120
tests/cases/zindex/z-index10.html
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FThe_stacking_context
|
||||||
|
-->
|
||||||
|
<title>Understanding CSS z-index: The Stacking Context: Example Source</title>
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
padding: 20px;
|
||||||
|
font: 12px/20px Arial, sans-serif;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
opacity: 0.7;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#div1, #div2 {
|
||||||
|
border: 1px solid #696;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #cfc;
|
||||||
|
}
|
||||||
|
#div1 {
|
||||||
|
z-index: 5;
|
||||||
|
margin-bottom: 190px;
|
||||||
|
}
|
||||||
|
#div2 {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
#div3 {
|
||||||
|
z-index: 4;
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
top: 40px;
|
||||||
|
left: 180px;
|
||||||
|
width: 330px;
|
||||||
|
border: 1px solid #900;
|
||||||
|
background-color: #fdd;
|
||||||
|
padding: 40px 20px 20px;
|
||||||
|
}
|
||||||
|
#div4, #div5 {
|
||||||
|
border: 1px solid #996;
|
||||||
|
background-color: #ffc;
|
||||||
|
}
|
||||||
|
#div4 {
|
||||||
|
z-index: 6;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 25px 10px 5px;
|
||||||
|
}
|
||||||
|
#div5 {
|
||||||
|
z-index: 1;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
#div6 {
|
||||||
|
z-index: 3;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 180px;
|
||||||
|
width: 150px;
|
||||||
|
height: 125px;
|
||||||
|
border: 1px solid #009;
|
||||||
|
padding-top: 125px;
|
||||||
|
background-color: #ddf;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="div1">
|
||||||
|
<h1>Division Element #1</h1>
|
||||||
|
<code>position: relative;<br/>
|
||||||
|
z-index: 5;</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div2">
|
||||||
|
<h1>Division Element #2</h1>
|
||||||
|
<code>position: relative;<br/>
|
||||||
|
z-index: 2;</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div3">
|
||||||
|
|
||||||
|
<div id="div4">
|
||||||
|
<h1>Division Element #4</h1>
|
||||||
|
<code>position: relative;<br/>
|
||||||
|
z-index: 6;</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>Division Element #3</h1>
|
||||||
|
<code>position: absolute;<br/>
|
||||||
|
z-index: 4;</code>
|
||||||
|
|
||||||
|
<div id="div5">
|
||||||
|
<h1>Division Element #5</h1>
|
||||||
|
<code>position: relative;<br/>
|
||||||
|
z-index: 1;</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div6">
|
||||||
|
<h1>Division Element #6</h1>
|
||||||
|
<code>position: absolute;<br/>
|
||||||
|
z-index: 3;</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
48
tests/cases/zindex/z-index11.html
Normal file
48
tests/cases/zindex/z-index11.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>static position inside position relative</title>
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
padding: 20px;
|
||||||
|
font: 12px/20px Arial, sans-serif;
|
||||||
|
}
|
||||||
|
#div1 {
|
||||||
|
padding: 10px;
|
||||||
|
background: #9bfff8;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div2 {
|
||||||
|
background: #7cb659;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div3 {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="div1">
|
||||||
|
<h1>Div Element #1</h1>
|
||||||
|
<code>position: relative;</code>
|
||||||
|
<div id="div2">
|
||||||
|
<h1>Div Element #2</h1>
|
||||||
|
<code>position: static;</code>
|
||||||
|
<div id="div3">
|
||||||
|
<div>
|
||||||
|
<h1>Div Element #3</h1>
|
||||||
|
<code>float: left;</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
48
tests/cases/zindex/z-index12.html
Normal file
48
tests/cases/zindex/z-index12.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Negative z-indexes</title>
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
padding: 20px;
|
||||||
|
font: 12px/20px Arial, sans-serif;
|
||||||
|
}
|
||||||
|
#div1 {
|
||||||
|
padding: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div2 {
|
||||||
|
background: #7cb659;
|
||||||
|
position: absolute;
|
||||||
|
z-index:-999998;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div3 {
|
||||||
|
background: #b69f1a;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -999999;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="div1">
|
||||||
|
<div id="div2">
|
||||||
|
<h1>Div Element #2</h1>
|
||||||
|
<div id="div3">
|
||||||
|
<div>
|
||||||
|
<h1>Div Element #3</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
31
tests/cases/zindex/z-index13.html
Normal file
31
tests/cases/zindex/z-index13.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>text above children with negative z-index; z-index tests #13</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
.outer {
|
||||||
|
background-color:cyan;
|
||||||
|
width:200px;
|
||||||
|
height:200px;
|
||||||
|
z-index:0;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
background-color:green;
|
||||||
|
width:100px;
|
||||||
|
height:100px;
|
||||||
|
z-index:-1;
|
||||||
|
position:absolute;
|
||||||
|
top:0;left:0;
|
||||||
|
}
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="outer">outer
|
||||||
|
<div class="inner"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
25
tests/cases/zindex/z-index14.html
Normal file
25
tests/cases/zindex/z-index14.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>body text above children with negative index but body bgcolor below</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
* {margin:0;padding:0;}
|
||||||
|
body {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#div1 {
|
||||||
|
background-color:cyan;
|
||||||
|
width:200px;
|
||||||
|
height:200px;
|
||||||
|
z-index:-1;
|
||||||
|
position:absolute;
|
||||||
|
top:0; left:0;
|
||||||
|
}
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>body
|
||||||
|
<div id="div1"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
26
tests/cases/zindex/z-index15.html
Normal file
26
tests/cases/zindex/z-index15.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>body text and bgcolor above children with negative z-index</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
* {margin:0;padding:0;}
|
||||||
|
html {background-color: gray;}
|
||||||
|
body {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
#div1 {
|
||||||
|
background-color:cyan;
|
||||||
|
width:200px;
|
||||||
|
height:200px;
|
||||||
|
z-index:-1;
|
||||||
|
position:absolute;
|
||||||
|
top:0; left:0;
|
||||||
|
}
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>body
|
||||||
|
<div id="div1"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
tests/cases/zindex/z-index4.html
Normal file
35
tests/cases/zindex/z-index4.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>z-index tests #4</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
div.lev1 {
|
||||||
|
width: 250px;
|
||||||
|
height: 70px;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid #669966;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
div.background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 250px;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="lev1">
|
||||||
|
<span>LEVEL #1</span>
|
||||||
|
</div>
|
||||||
|
<div class="background"></div>
|
||||||
|
<div class="lev1">
|
||||||
|
<span>LEVEL #1</span>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
34
tests/cases/zindex/z-index5.html
Normal file
34
tests/cases/zindex/z-index5.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>z-index tests #5</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
div.lev1 {
|
||||||
|
width: 250px;
|
||||||
|
height: 70px;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid #669966;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
div.background {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 250px;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
}
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="lev1">
|
||||||
|
LEVEL #1
|
||||||
|
</div>
|
||||||
|
<div class="background"></div>
|
||||||
|
<div class="lev1">
|
||||||
|
<span>LEVEL #1</span>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
33
tests/cases/zindex/z-index6.html
Normal file
33
tests/cases/zindex/z-index6.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>z-index tests #6</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
div {
|
||||||
|
width: 250px;
|
||||||
|
height: 70px;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid #669966;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
div.z0 {
|
||||||
|
z-index: 0;
|
||||||
|
top:105px;
|
||||||
|
left:20px;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.z1 {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="z0"><span>z-index:0</span></div>
|
||||||
|
<div>default z-index</div>
|
||||||
|
<div class="z1">z-index:1</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
102
tests/cases/zindex/z-index7.html
Normal file
102
tests/cases/zindex/z-index7.html
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Stacking without z-index</title>
|
||||||
|
<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/Stacking_without_z-index?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FStacking_without_z-index -->
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
div {
|
||||||
|
font: 12px Arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.bold { font-weight: bold; }
|
||||||
|
|
||||||
|
#normdiv {
|
||||||
|
height: 70px;
|
||||||
|
border: 1px solid #999966;
|
||||||
|
background-color: #ffffcc;
|
||||||
|
margin: 0px 50px 0px 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reldiv1 {
|
||||||
|
opacity: 0.7;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
top: 30px;
|
||||||
|
border: 1px solid #669966;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
margin: 0px 50px 0px 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reldiv2 {
|
||||||
|
opacity: 0.7;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
top: 15px;
|
||||||
|
left: 20px;
|
||||||
|
border: 1px solid #669966;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
margin: 0px 50px 0px 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#absdiv1 {
|
||||||
|
opacity: 0.7;
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 350px;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
border: 1px solid #990000;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#absdiv2 {
|
||||||
|
opacity: 0.7;
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 350px;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
border: 1px solid #990000;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<div id="absdiv1">
|
||||||
|
<br /><span class="bold">DIV #1</span>
|
||||||
|
<br />position: absolute;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="reldiv1">
|
||||||
|
<br /><span class="bold">DIV #2</span>
|
||||||
|
<br />position: relative;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="reldiv2">
|
||||||
|
<br /><span class="bold">DIV #3</span>
|
||||||
|
<br />position: relative;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="absdiv2">
|
||||||
|
<br /><span class="bold">DIV #4</span>
|
||||||
|
<br />position: absolute;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="normdiv">
|
||||||
|
<br /><span class="bold">DIV #5</span>
|
||||||
|
<br />no positioning
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>
|
104
tests/cases/zindex/z-index8.html
Normal file
104
tests/cases/zindex/z-index8.html
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Stacking and float</title>
|
||||||
|
<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/Stacking_and_float?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FStacking_and_float -->
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
div {
|
||||||
|
font: 12px Arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.bold { font-weight: bold; }
|
||||||
|
|
||||||
|
#absdiv1 {
|
||||||
|
opacity: 0.7;
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 200px;
|
||||||
|
top: 10px;
|
||||||
|
right: 140px;
|
||||||
|
border: 1px solid #990000;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#normdiv {
|
||||||
|
/*opacity: 0.7;*/
|
||||||
|
height: 100px;
|
||||||
|
border: 1px solid #999966;
|
||||||
|
background-color: #ffffcc;
|
||||||
|
margin: 0px 10px 0px 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flodiv1 {
|
||||||
|
opacity: 0.7;
|
||||||
|
margin: 0px 10px 0px 20px;
|
||||||
|
float: left;
|
||||||
|
width: 150px;
|
||||||
|
height: 200px;
|
||||||
|
border: 1px solid #009900;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flodiv2 {
|
||||||
|
opacity: 0.7;
|
||||||
|
margin: 0px 20px 0px 10px;
|
||||||
|
float: right;
|
||||||
|
width: 150px;
|
||||||
|
height: 200px;
|
||||||
|
border: 1px solid #009900;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#absdiv2 {
|
||||||
|
opacity: 0.7;
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 100px;
|
||||||
|
top: 130px;
|
||||||
|
left: 100px;
|
||||||
|
border: 1px solid #990000;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<div id="absdiv1">
|
||||||
|
<br /><span class="bold">DIV #1</span>
|
||||||
|
<br />position: absolute;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="flodiv1">
|
||||||
|
<br /><span class="bold">DIV #2</span>
|
||||||
|
<br />float: left;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="flodiv2">
|
||||||
|
<br /><span class="bold">DIV #3</span>
|
||||||
|
<br />float: right;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div id="normdiv">
|
||||||
|
<br /><span class="bold">DIV #4</span>
|
||||||
|
<br />no positioning
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="absdiv2">
|
||||||
|
<br /><span class="bold">DIV #5</span>
|
||||||
|
<br />position: absolute;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>
|
108
tests/cases/zindex/z-index9.html
Normal file
108
tests/cases/zindex/z-index9.html
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html>
|
||||||
|
<head><title>Adding z-index</title>
|
||||||
|
<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/Adding_z-index?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FAdding_z-index -->
|
||||||
|
<script type="text/javascript" src="../../test.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
div {
|
||||||
|
opacity: 0.7;
|
||||||
|
font: 12px Arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.bold { font-weight: bold; }
|
||||||
|
|
||||||
|
#normdiv {
|
||||||
|
z-index: 8;
|
||||||
|
height: 70px;
|
||||||
|
border: 1px solid #999966;
|
||||||
|
background-color: #ffffcc;
|
||||||
|
margin: 0px 50px 0px 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reldiv1 {
|
||||||
|
z-index: 3;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
top: 30px;
|
||||||
|
border: 1px solid #669966;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
margin: 0px 50px 0px 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reldiv2 {
|
||||||
|
z-index: 2;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
top: 15px;
|
||||||
|
left: 20px;
|
||||||
|
border: 1px solid #669966;
|
||||||
|
background-color: #ccffcc;
|
||||||
|
margin: 0px 50px 0px 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#absdiv1 {
|
||||||
|
z-index: 5;
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 350px;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
border: 1px solid #990000;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#absdiv2 {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 350px;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
border: 1px solid #990000;
|
||||||
|
background-color: #ffdddd;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<div id="absdiv1">
|
||||||
|
<br /><span class="bold">DIV #1</span>
|
||||||
|
<br />position: absolute;
|
||||||
|
<br />z-index: 5;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="reldiv1">
|
||||||
|
<br /><span class="bold">DIV #2</span>
|
||||||
|
<br />position: relative;
|
||||||
|
<br />z-index: 3;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="reldiv2">
|
||||||
|
<br /><span class="bold">DIV #3</span>
|
||||||
|
<br />position: relative;
|
||||||
|
<br />z-index: 2;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="absdiv2">
|
||||||
|
<br /><span class="bold">DIV #4</span>
|
||||||
|
<br />position: absolute;
|
||||||
|
<br />z-index: 1;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="normdiv">
|
||||||
|
<br /><span class="bold">DIV #5</span>
|
||||||
|
<br />no positioning
|
||||||
|
<br />z-index: 8;
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>
|
43
tests/certificate.pem.enc
Normal file
43
tests/certificate.pem.enc
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
U2FsdGVkX1/1OhTi++Zu8+GzAWDR9xD8SFu4sB/1mvbeKt7eF3z/t217AJii/H52
|
||||||
|
FYMoPSnwFzEvXuiS/EIpVzRYugcfc4qSkOA2PE726V1b6UQoxbcJnHK4UW2464Yb
|
||||||
|
a8dw+saN7KFpzaY0+L09QzIGqVMvtYQG2dTQk3dfAZ4Y/hduoGuCDReyRP7DbIJw
|
||||||
|
WD933bja/p1HL91AnNo7lIGD529nXugBdlCAEeYGkeAfa7/Or+68sw7fhki3MQJw
|
||||||
|
xWbo/oND/Fr4WzlGDeTQMTVfEVGYmT8K+aNtcKrgAp3SDQf37zuMnxVaKREmlXOF
|
||||||
|
IRKaoR4jzDEgiwlsMZ7CvASwKDZDckFKjg3v/yKLjg+NKg2eET+VD+3CvTai5PHQ
|
||||||
|
xRzPqkmyh+gh3Op2ZpTCtqnlgx7CfobAXjL4BOtu8aT6LZsoS/mMmqfbX49SDBw3
|
||||||
|
wVXFuhPPGsXDJD2F39kG5vX6LeygOGPMOPbzqimUftWlxcUJhb4mGKV5qcLz+WzH
|
||||||
|
17E2fv6kEwMRTfjGjUR9zMR8JjBBu0GcKGyiW6MqQwjF+BXvWoIRuwv99x2cpOjN
|
||||||
|
fAHk9Y9IgJlzWmrTpg1i5sqTNKgmQaKqarJqXwvuq86kJuCMkYr0SxleUlAmbzpC
|
||||||
|
w25JAMcNqabQLDaTJfV6V5EWAh3sHh+lAcL+QKs2Ddx5K9RwwCnNXtIhatECsu0n
|
||||||
|
oNDeWcFtaLmGVF/zXpkCkKBg0EsEcFIXZM662TaGCqukIktq61/UsuR7hVQdPXyH
|
||||||
|
/4rJTITQAXbi+FaQXzrrxKrikU7Rdovn6az5YNXBXh0pMQ9FrahxhYhDrpzpCsNc
|
||||||
|
STL2JN50u3X28ZgfRs7rB5fY9Xr5pERIAFSrNzFPCHzlStoYNSphJP8lO/XjNkcn
|
||||||
|
CTOzZrzxVpzqqzXlmArCfx3QtVo+WUUUW9hdOBgKLl19Wt0x5ZvYDPm6ODht9BjD
|
||||||
|
wfbmkRvdP9CTICBR17l2tPiGcu+4paTKBF6W6aSxQU7Z48uZP19/4cuOiITbcYnt
|
||||||
|
4wHoNk+M5G9SjLSsOlfmYNMcl0D57AOcbyUUTMZ2Wu0zc3/StJ9aotzEcFSxA1Gs
|
||||||
|
wPZr/2iqeEynJmUQeFjs01JSwxwYlLF+HOStdn6MEbsW4ftxsXO9Mr2NGAOTaO1/
|
||||||
|
Y8pyMCRJKOH6kRvHPpzza/stIZQQZk+PMRzS3PgMXsnJoF7cuanrWXJTgiNT6B37
|
||||||
|
y/ARNr3KtZTGOjc5gE+k32hPuqPi5tVKPrFVUrG2l/87Gqjs4JqNDTjTHBlUIViT
|
||||||
|
y87uB2f8/8/V50lXf+RYGPKaJ+jzbyaUxp6QRZdqJk8CEZQnrGwpUEzBVMREd+Ow
|
||||||
|
jn99f/Isn5Vadsdmt/aMy7+TnLVyjPqucIo8dUdkWdfLKF1Q7M06rYgu9MCrsbgi
|
||||||
|
hGp1/YjqSmYxWZuSUZHvd4ps2x9YI6wuwdWXAH/7+L0IVUghpSNoARrsmi/D7Rg5
|
||||||
|
4zF6eCOqH13z1luMRg1ZMpRyVQcI/Yu3IbyepnuIeYIqPklDJ6jl3kIdubEtXZTV
|
||||||
|
1XMlTvZjFuRKLeUXsF/ecTmlob+rc06YvOu91vwpxSMkpYUIc/Yt4tTWey1AbmTG
|
||||||
|
t4pf8q0cVXrVRuXf8HFEEqQlXumJGWi/WCxAawj35znEIquQDu9ERLgSemdTFH2z
|
||||||
|
ncoJseApHD9Gf2UpM4Vva+XdI9wrlYURr83/gOxenCgMq7wCWH87yuCG1IsOryPx
|
||||||
|
1DbES6axy6P+XBQJTDHK560zKSNF+3VVBGEeTQRMAnQs/IHlwWu7Af0DTmUAEO2Z
|
||||||
|
S1R+Ex2HWMM3i8/Sr8P1JDkm0u7gn2wkfI0xEIf5B10md1iniKb0GovMlfMGJTTo
|
||||||
|
+mytGTp3XUZl8lXDpbLTR9nqfpB+hL5Qi30Dv08CAQdvX9OzOEQz5T//t13wAKlo
|
||||||
|
bsbzwIuLhyYbua+wz/3uMS6QAbNNVCgIm/J1rqlwLRmzvSTJyhraamjd2UuwwIau
|
||||||
|
fgdIA5bYqdagzbUuIk8msItfUrAbhxkyBElVb5RgLVJsUbBQTHW3anrbsTxxMrcR
|
||||||
|
9jnZZV2Is2ahbnmcqqIjr7fxSqTCtwpyBxRkW8Bh+Sfu2R8x+DLcJ+ID25fbxWto
|
||||||
|
QrDSGEifpW/5bjGx4lM0j8IxoWqOl0600hlzGsD5ynSu5WPpXQ0CMvuuLXOR4CbZ
|
||||||
|
kzE5kQXAeQzfwwPcWMMhwt5F4hXAXY/+jM0TfuSc7Z9PYYRWdoDg3awRNlQJS5rk
|
||||||
|
wDwbMHKUcdi4XSwIhEdbZ4zBo0o7xXKn2XSBNy+Ym39pKWMwOrffsPtWDV+qTHpD
|
||||||
|
P5rhXEW8aD01kMkxjO2pRZ7nEDQ7VGpi3MuW9FAjXZjcc1grnZSxbKz437K9obI7
|
||||||
|
zHXGcodAXjU4Oxhs/m8JHVaHsDG5LNxLSmArsmbwcu65mlBCXcefcwOUNoVQDmZG
|
||||||
|
ENSAMlVAUX7RJ9nxXI9AqKKuVAYSMznseY3V22BCDrRR0nbVkAWjuBTKU6ajfjQ6
|
||||||
|
W2uhAzl8kH+9Xi10jt5Fhn93b8z4VjrcpZmW/gJuEHm8FY2UFF0P1y3YMCDqCPaz
|
||||||
|
gXsFZVKPPIswg/xBHB9GhiCSHnvivu/K8Ni4FcBW7smBHuqkk0VoN7CdZvNF0CuX
|
||||||
|
EANCwDD53hSmfyxR/SY6y1PQCEsX5AiRS8rMGV2bqOjP6/oM9+2JmkRZ3I6Jht2f
|
||||||
|
sxJsRUTyjecCcwnGDgmLDCZDJqLJoOE6zlRuAOvY1U7m2cFrf9EouOlYUXSeBaEh
|
@ -70,6 +70,17 @@
|
|||||||
<div style="padding: 15% 0 3%;"></div>
|
<div style="padding: 15% 0 3%;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="textShadows">
|
||||||
|
<div style=""></div>
|
||||||
|
<div style="text-shadow: 1px 1px 1px"></div>
|
||||||
|
<div style="text-shadow: 2px 2px rgb(2, 2, 2)"></div>
|
||||||
|
<div style="text-shadow: 3px 3px rgba(2, 2, 2, .2)"></div>
|
||||||
|
<div style="text-shadow: rgb(2, 2, 2) 4px 4px"></div>
|
||||||
|
<div style="text-shadow: rgba(2, 2, 2, .2) 5px 5px"></div>
|
||||||
|
<div style="text-shadow: rgb(2, 2, 2) 6px 6px, #222222 2px 2px"></div>
|
||||||
|
<div style="text-shadow: 7px 7px rgba(2, 2, 2, .2), #222222 2px 2px"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="backgroundPosition">
|
<div id="backgroundPosition">
|
||||||
<div style="background-position: 1px 0;"></div>
|
<div style="background-position: 1px 0;"></div>
|
||||||
<div style="background-position: 1em 0;"></div>
|
<div style="background-position: 1em 0;"></div>
|
||||||
|
@ -145,6 +145,35 @@ $(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('text-shadow', function() {
|
||||||
|
|
||||||
|
$('#textShadows div').each(function(i, el) {
|
||||||
|
var index = i+1;
|
||||||
|
var value = _html2canvas.Util.getCSS(el, 'textShadow'),
|
||||||
|
shadows = _html2canvas.Util.parseTextShadows(value);
|
||||||
|
if (i == 0) {
|
||||||
|
QUnit.equal(shadows.length, 0, 'div #' + index);
|
||||||
|
} else {
|
||||||
|
QUnit.equal(shadows.length, (i >= 6 ? 2 : 1), 'div #' + index);
|
||||||
|
QUnit.equal(shadows[0].offsetX, i, 'div #' + index + ' offsetX');
|
||||||
|
QUnit.equal(shadows[0].offsetY, i, 'div #' + index + ' offsetY');
|
||||||
|
if (i < 2) {
|
||||||
|
QUnit.equal(shadows[0].color, 'rgba(0, 0, 0, 0)', 'div #' + index + ' color');
|
||||||
|
} else if (i % 2 == 0) {
|
||||||
|
QUnit.equal(shadows[0].color, 'rgb(2, 2, 2)', 'div #' + index + ' color');
|
||||||
|
} else {
|
||||||
|
var opacity = '0.199219';
|
||||||
|
QUnit.equal(shadows[0].color, 'rgba(2, 2, 2, '+opacity+')', 'div #' + index + ' color');
|
||||||
|
}
|
||||||
|
|
||||||
|
// only testing blur once
|
||||||
|
if (i == 1) {
|
||||||
|
QUnit.equal(shadows[0].blur, '1', 'div #' + index + ' blur');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('background-image', function () {
|
test('background-image', function () {
|
||||||
|
|
||||||
test_parse_background_image(
|
test_parse_background_image(
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
<table><thead><tr><td></td><th>chrome<br />23.0.1271.97</th><th>firefox<br />12.0</th><th>iexplorer<br />9</th></tr></thead><tbody>
|
<table><thead><tr><td></td><th>chrome<br />23.0.1271.97</th><th>firefox<br />12.0</th><th>iexplorer<br />9</th><th>chrome<br />25.0.1364.172<br />Mac 10.7.4</th><th>firefox<br />19.0.2<br />Mac 10.7.4</th><th>safari<br />6.0<br />Mac 10.7.4</th></tr></thead><tbody>
|
||||||
<tr><td>background/clip.html</td><td>100%</td><td>100%</td><td>99.89%</td></tr>
|
<tr><td>background/clip.html</td><td>100%</td><td>100%</td><td>99.89%</td><td>100%</td><td>100%</td><td>100%</td></tr>
|
||||||
<tr><td>background/encoded.html</td><td>100%</td><td>100%</td><td>100%</td></tr>
|
<tr><td>background/encoded.html</td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td>100%</td></tr>
|
||||||
<tr><td>background/linear-gradient.html</td><td>89.87%</td><td>90.73%</td><td>100%</td></tr>
|
<tr><td>background/linear-gradient.html</td><td>89.87%</td><td>90.73%</td><td>100%</td><td>95.35%</td><td>91.33%</td><td>91.69%</td></tr>
|
||||||
<tr><td>background/multi.html</td><td>100%</td><td>100%</td><td>99.93%</td></tr>
|
<tr><td>background/multi.html</td><td>100%</td><td>100%</td><td>99.93%</td><td>100%</td><td>100%</td><td>100%</td></tr>
|
||||||
<tr><td>background/position.html</td><td>100%</td><td>100%</td><td>99.87%</td></tr>
|
<tr><td>background/position.html</td><td>100%</td><td>100%</td><td>99.87%</td><td>100%</td><td>100%</td><td>100%</td></tr>
|
||||||
<tr><td>background/radial-gradient.html</td><td>73.23%</td><td>70.32%</td><td>94.02%</td></tr>
|
<tr><td>background/radial-gradient.html</td><td>73.23%</td><td>70.32%</td><td>94.02%</td><td>87.65%</td><td>57.99%</td><td>79.95%</td></tr>
|
||||||
<tr><td>background/repeat.html</td><td>100%</td><td>100%</td><td>99.92%</td></tr>
|
<tr><td>background/repeat.html</td><td>100%</td><td>100%</td><td>99.92%</td><td>100%</td><td>100%</td><td>100%</td></tr>
|
||||||
<tr><td>border/dashed.html</td><td>96.45%</td><td>98.38%</td><td>97.7%</td></tr>
|
<tr><td>border/dashed.html</td><td>96.45%</td><td>98.38%</td><td>97.7%</td><td>98.36%</td><td>98.47%</td><td>97.43%</td></tr>
|
||||||
<tr><td>border/dotted.html</td><td>97.41%</td><td>96.46%</td><td>95.93%</td></tr>
|
<tr><td>border/dotted.html</td><td>97.41%</td><td>96.46%</td><td>95.93%</td><td>98.81%</td><td>96.31%</td><td>98.13%</td></tr>
|
||||||
<tr><td>border/double.html</td><td>97.96%</td><td>97.87%</td><td>97.95%</td></tr>
|
<tr><td>border/double.html</td><td>97.96%</td><td>97.87%</td><td>97.95%</td><td>99.06%</td><td>97.99%</td><td>98.53%</td></tr>
|
||||||
<tr><td>border/radius.html</td><td>99.74%</td><td>99.77%</td><td>99.75%</td></tr>
|
<tr><td>border/radius.html</td><td>99.74%</td><td>99.77%</td><td>99.75%</td><td>99.88%</td><td>99.81%</td><td>99.75%</td></tr>
|
||||||
<tr><td>border/solid.html</td><td>99.97%</td><td>99.97%</td><td>99.98%</td></tr>
|
<tr><td>border/solid.html</td><td>99.97%</td><td>99.97%</td><td>99.98%</td><td>99.99%</td><td>99.97%</td><td>99.98%</td></tr>
|
||||||
<tr><td>forms.html</td><td>95.96%</td><td>94.55%</td><td>95.01%</td></tr>
|
<tr><td>forms.html</td><td>95.96%</td><td>94.55%</td><td>95.01%</td><td>98.57%</td><td>94.2%</td><td>97.69%</td></tr>
|
||||||
<tr><td>images/canvas.html</td><td>99.86%</td><td>100%</td><td>100%</td></tr>
|
<tr><td>images/canvas.html</td><td>99.86%</td><td>100%</td><td>100%</td><td>99.93%</td><td>100%</td><td>99.87%</td></tr>
|
||||||
<tr><td>images/cross-origin.html</td><td>97.99%</td><td>97.58%</td><td>99.35%</td></tr>
|
<tr><td>images/cross-origin.html</td><td>97.99%</td><td>97.58%</td><td>99.35%</td><td>99.7%</td><td>98.41%</td><td>99.89%</td></tr>
|
||||||
<tr><td>images/empty.html</td><td>99.86%</td><td>99.87%</td><td>99.85%</td></tr>
|
<tr><td>images/empty.html</td><td>99.86%</td><td>99.87%</td><td>99.85%</td><td>99.91%</td><td>99.81%</td><td>99.86%</td></tr>
|
||||||
<tr><td>images/images.html</td><td>83.72%</td><td>96.93%</td><td>55.09%</td></tr>
|
<tr><td>images/images.html</td><td>83.72%</td><td>96.93%</td><td>55.09%</td><td>92.45%</td><td>95.81%</td><td>87.06%</td></tr>
|
||||||
<tr><td>images/svg.html</td><td>99.92%</td><td>96.79%</td><td>99.93%</td></tr>
|
<tr><td>images/svg.html</td><td>99.92%</td><td>96.79%</td><td>99.93%</td><td>99.95%</td><td>96.51%</td><td>99.92%</td></tr>
|
||||||
<tr><td>list/decimal-leading-zero.html</td><td>99.63%</td><td>99.72%</td><td>35.88%</td></tr>
|
<tr><td>list/decimal-leading-zero.html</td><td>99.63%</td><td>99.72%</td><td>35.88%</td><td>99.7%</td><td>99.99%</td><td>15.05%</td></tr>
|
||||||
<tr><td>list/decimal.html</td><td>99.64%</td><td>99.73%</td><td>35.89%</td></tr>
|
<tr><td>list/decimal.html</td><td>99.64%</td><td>99.73%</td><td>35.89%</td><td>99.71%</td><td>99.99%</td><td>15.06%</td></tr>
|
||||||
<tr><td>list/lower-alpha.html</td><td>99.65%</td><td>99.73%</td><td>35.89%</td></tr>
|
<tr><td>list/lower-alpha.html</td><td>99.65%</td><td>99.73%</td><td>35.89%</td><td>99.72%</td><td>99.98%</td><td>15.06%</td></tr>
|
||||||
<tr><td>list/upper-roman.html</td><td>99.45%</td><td>99.61%</td><td>35.94%</td></tr>
|
<tr><td>list/upper-roman.html</td><td>99.45%</td><td>99.61%</td><td>35.94%</td><td>99.59%</td><td>99.99%</td><td>13.97%</td></tr>
|
||||||
<tr><td>overflow.html</td><td>96.85%</td><td>97.49%</td><td>96.5%</td></tr>
|
<tr><td>overflow.html</td><td>96.85%</td><td>97.49%</td><td>96.5%</td><td>98.15%</td><td>97.96%</td><td>99.42%</td></tr>
|
||||||
<tr><td>pseudoelements.html</td><td>97.36%</td><td>97.94%</td><td>99.37%</td></tr>
|
<tr><td>pseudoelements.html</td><td>97.36%</td><td>97.94%</td><td>99.37%</td><td>98.73%</td><td>97.81%</td><td>98.29%</td></tr>
|
||||||
<tr><td>text/chinese.html</td><td>99.75%</td><td>99.74%</td><td>65.76%</td></tr>
|
<tr><td>text/chinese.html</td><td>99.75%</td><td>99.74%</td><td>65.76%</td><td>93.93%</td><td>96%</td><td>46.75%</td></tr>
|
||||||
<tr><td>text/linethrough.html</td><td>97.14%</td><td>94.12%</td><td>47.08%</td></tr>
|
<tr><td>text/linethrough.html</td><td>97.14%</td><td>94.12%</td><td>47.08%</td><td>98.99%</td><td>90.28%</td><td>31.02%</td></tr>
|
||||||
<tr><td>text/text.html</td><td>95.71%</td><td>94.67%</td><td>85.01%</td></tr>
|
<tr><td>text/text.html</td><td>95.71%</td><td>94.67%</td><td>85.01%</td><td>96.83%</td><td>95.6%</td><td>94.63%</td></tr>b
|
||||||
<tr><td>text/underline-lineheight.html</td><td>97.06%</td><td>92.35%</td><td>53%</td></tr>
|
<tr><td>text/underline-lineheight.html</td><td>97.06%</td><td>92.35%</td><td>53%</td><td>99.15%</td><td>93.69%</td><td>40.76%</td></tr>
|
||||||
<tr><td>text/underline.html</td><td>97.65%</td><td>93.5%</td><td>47.02%</td></tr>
|
<tr><td>text/underline.html</td><td>97.65%</td><td>93.5%</td><td>47.02%</td><td>99.35%</td><td>89.85%</td><td>31.07%</td></tr>
|
||||||
<tr><td>visibility.html</td><td>99.19%</td><td>98.92%</td><td>99.39%</td></tr>
|
<tr><td>visibility.html</td><td>99.19%</td><td>98.92%</td><td>99.39%</td><td>99.69%</td><td>99.32%</td><td>99.74%</td></tr>
|
||||||
<tr><td>zindex/z-index1.html</td><td>96.99%</td><td>99.27%</td><td>99.44%</td></tr>
|
<tr><td>zindex/z-index1.html</td><td>96.99%</td><td>99.27%</td><td>99.44%</td><td>98.62%</td><td>98.48%</td><td>98%</td></tr>
|
||||||
<tr><td>zindex/z-index2.html</td><td>95.85%</td><td>98.06%</td><td>97.72%</td></tr>
|
<tr><td>zindex/z-index2.html</td><td>95.85%</td><td>98.06%</td><td>97.72%</td><td>97.79%</td><td>96.69%</td><td>96.72%</td></tr>
|
||||||
<tr><td>zindex/z-index3.html</td><td>98.6%</td><td>98.29%</td><td>98.56%</td></tr>
|
<tr><td>zindex/z-index3.html</td><td>98.6%</td><td>98.29%</td><td>98.56%</td><td>99.35%</td><td>96.49%</td><td>97.92%</td></tr>
|
||||||
</tbody></table>
|
</tbody></table>
|
@ -1,272 +1,427 @@
|
|||||||
(function(){
|
(function(){
|
||||||
"use strict;"
|
"use strict;";
|
||||||
var webdriver = require("webdriver.js").webdriver,
|
var WebDriver = require('sync-webdriver'),
|
||||||
|
Bacon = require('baconjs').Bacon,
|
||||||
|
express = require('express'),
|
||||||
http = require("http"),
|
http = require("http"),
|
||||||
|
https = require("https"),
|
||||||
url = require("url"),
|
url = require("url"),
|
||||||
path = require("path"),
|
path = require("path"),
|
||||||
base64_arraybuffer = require('base64-arraybuffer'),
|
base64_arraybuffer = require('base64-arraybuffer'),
|
||||||
PNG = require('png-js'),
|
PNG = require('png-js'),
|
||||||
fs = require("fs");
|
fs = require("fs"),
|
||||||
|
googleapis = require('googleapis'),
|
||||||
|
jwt = require('jwt-sign');
|
||||||
|
|
||||||
function createServer(port) {
|
var port = 8080,
|
||||||
return http.createServer(function(request, response) {
|
app = express(),
|
||||||
var uri = url.parse(request.url).pathname,
|
colors = {
|
||||||
filename = path.join(process.cwd(), uri);
|
red: "\x1b[1;31m",
|
||||||
|
blue: "\x1b[1;36m",
|
||||||
fs.exists(filename, function(exists) {
|
violet: "\x1b[0;35m",
|
||||||
if(!exists) {
|
green: "\x1b[0;32m",
|
||||||
response.writeHead(404, {
|
clear: "\x1b[0m"
|
||||||
"Content-Type": "text/plain"
|
|
||||||
});
|
|
||||||
response.write("404 Not Found\n");
|
|
||||||
response.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.statSync(filename).isDirectory()) filename += '/index.html';
|
|
||||||
|
|
||||||
fs.readFile(filename, "binary", function(err, file) {
|
|
||||||
if(err) {
|
|
||||||
response.writeHead(500, {
|
|
||||||
"Content-Type": "text/plain"
|
|
||||||
});
|
|
||||||
response.write(err + "\n");
|
|
||||||
response.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.writeHead(200);
|
|
||||||
response.write(file, "binary");
|
|
||||||
response.end();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}).listen(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkDir(dir, done) {
|
|
||||||
var results = [];
|
|
||||||
fs.readdir(dir, function(err, list) {
|
|
||||||
if (err) return done(err);
|
|
||||||
var i = 0;
|
|
||||||
(function next() {
|
|
||||||
var file = list[i++];
|
|
||||||
if (!file) return done(null, results);
|
|
||||||
file = dir + '/' + file;
|
|
||||||
fs.stat(file, function(err, stat) {
|
|
||||||
if (stat && stat.isDirectory()) {
|
|
||||||
walkDir(file, function(err, res) {
|
|
||||||
results = results.concat(res);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
results.push(file);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPixelArray(base64, func) {
|
var server = app.listen(port);
|
||||||
var arraybuffer = base64_arraybuffer.decode(base64);
|
|
||||||
(new PNG(arraybuffer)).decode(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBaselineFiles() {
|
app.use('/index.html', function(req, res){
|
||||||
return fs.readdirSync("tests/results/").filter(function(name) {
|
res.send("<ul>" + tests.map(function(test) {
|
||||||
return /\.baseline$/.test(name);
|
return "<li><a href='" + test + "'>" + test + "</a></li>";
|
||||||
}).map(function(item) {
|
}).join("") + "</ul>");
|
||||||
return "tests/results/" + item;
|
});
|
||||||
|
|
||||||
|
app.use('/', express.static(__dirname + "/../"));
|
||||||
|
|
||||||
|
function mapStat(item) {
|
||||||
|
return Bacon.combineTemplate({
|
||||||
|
stat: Bacon.fromNodeCallback(fs.stat, item),
|
||||||
|
item: item
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function testPage(browser, url, done) {
|
function isDirectory(item) {
|
||||||
browser.url(url)
|
return item.stat.isDirectory();
|
||||||
.$(".html2canvas", 5000, function(){
|
}
|
||||||
this.execute(function(){
|
|
||||||
var canvas = $('.html2canvas')[0];
|
function getItem(item) {
|
||||||
return canvas.toDataURL("image/png").substring(22);
|
return item.item;
|
||||||
},[], function(dataurl) {
|
}
|
||||||
getPixelArray(dataurl, function(h2cPixels) {
|
|
||||||
browser.screenshot(function(base64){
|
function isFile(item) {
|
||||||
getPixelArray(base64, function(screenPixels) {
|
return !isDirectory(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayStream(arr) {
|
||||||
|
return Bacon.fromArray(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTests(path) {
|
||||||
|
var items = Bacon.fromNodeCallback(fs.readdir, path).flatMap(arrayStream).map(function(name) {
|
||||||
|
return path + "/" + name;
|
||||||
|
}).flatMap(mapStat);
|
||||||
|
return items.filter(isFile).map(getItem).merge(items.filter(isDirectory).map(getItem).flatMap(getTests));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getPixelArray(base64) {
|
||||||
|
return Bacon.fromCallback(function(callback) {
|
||||||
|
var arraybuffer = base64_arraybuffer.decode(base64);
|
||||||
|
(new PNG(arraybuffer)).decode(callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateDifference(h2cPixels, screenPixels) {
|
||||||
var len = h2cPixels.length, index = 0, diff = 0;
|
var len = h2cPixels.length, index = 0, diff = 0;
|
||||||
for (; index < len; index++) {
|
for (; index < len; index++) {
|
||||||
if (screenPixels[index] - h2cPixels[index] !== 0) {
|
if (screenPixels[index] - h2cPixels[index] !== 0) {
|
||||||
diff++;
|
diff++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done(100 - (Math.round((diff/h2cPixels.length) * 10000) / 100));
|
return (100 - (Math.round((diff/h2cPixels.length) * 10000) / 100));
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var writeResultFile = function(filename, json, append) {
|
function canvasToDataUrl(canvas) {
|
||||||
fs.writeFile(filename + (append || ""), json);
|
return canvas.toDataURL("image/png").substring(22);
|
||||||
};
|
|
||||||
|
|
||||||
var openResultFile = function(stats, browser) {
|
|
||||||
var tests = stats[browser].tests,
|
|
||||||
filename = "tests/results/" + browser + ".json",
|
|
||||||
write = writeResultFile.bind(null, filename, JSON.stringify(stats[browser]));
|
|
||||||
|
|
||||||
fs.exists(filename, function(exists) {
|
|
||||||
if(exists) {
|
|
||||||
fs.readFile(filename, "binary", parseResultFile.bind(null, tests, browser, write));
|
|
||||||
} else {
|
|
||||||
write();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var setColor = function(color, text) {
|
function closeServer() {
|
||||||
return [color, " * ", ((isNaN(text.amount)) ? "NEW" : text.amount + "%"), " ", text.test].join("");
|
server.close();
|
||||||
};
|
}
|
||||||
|
|
||||||
var parseResultFile = function(tests, browser, createResultFile, err, file) {
|
function findResult(testName, tests) {
|
||||||
if (err) throw err;
|
var item = null;
|
||||||
var data = JSON.parse(file),
|
return tests.some(function(testCase) {
|
||||||
improved = [],
|
item = testCase;
|
||||||
|
return testCase.test === testName;
|
||||||
|
}) ? item : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareResults(oldResults, newResults, browser) {
|
||||||
|
var improved = [],
|
||||||
regressed = [],
|
regressed = [],
|
||||||
newItems = [],
|
newItems = [];
|
||||||
colors = {
|
|
||||||
red: "\x1b[1;31m",
|
|
||||||
blue: "\x1b[1;36m",
|
|
||||||
violet: "\x1b[0;35m",
|
|
||||||
green: "\x1b[0;32m"
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(tests).forEach(function(test){
|
newResults.forEach(function(testCase){
|
||||||
var testResult = tests[test],
|
var testResult = testCase.result,
|
||||||
dataResult = data.tests[test],
|
oldResult = findResult(testCase.test, oldResults),
|
||||||
|
oldResultValue = oldResult ? oldResult.result : null,
|
||||||
dataObject = {
|
dataObject = {
|
||||||
amount: (Math.abs(testResult - dataResult) < 0.02) ? 0 : testResult - dataResult,
|
amount: (Math.abs(testResult - oldResultValue) < 0.01) ? 0 : testResult - oldResultValue,
|
||||||
test: test
|
test: testCase.test
|
||||||
};
|
};
|
||||||
|
if (oldResultValue === null) {
|
||||||
if (dataObject.amount > 0) {
|
newItems.push(dataObject);
|
||||||
|
} else if (dataObject.amount > 0) {
|
||||||
improved.push(dataObject);
|
improved.push(dataObject);
|
||||||
} else if (dataObject.amount < 0) {
|
} else if (dataObject.amount < 0) {
|
||||||
regressed.push(dataObject);
|
regressed.push(dataObject);
|
||||||
} else if (dataResult === undefined) {
|
|
||||||
newItems.push(dataObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newItems.length > 0 || improved.length > 0 || regressed.length > 0) {
|
reportChanges(browser, improved, regressed, newItems);
|
||||||
if (regressed.length === 0) {
|
|
||||||
createResultFile(".baseline");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(colors.violet, "********************");
|
function reportChanges(browser, improved, regressed, newItems) {
|
||||||
|
if (newItems.length > 0 || improved.length > 0 || regressed.length > 0) {
|
||||||
console.log((regressed.length > 0) ? colors.red : colors.green, browser);
|
console.log((regressed.length > 0) ? colors.red : colors.green, browser);
|
||||||
|
|
||||||
improved.map(setColor.bind(null, colors.green))
|
regressed.forEach(function(item) {
|
||||||
.concat(regressed.map(setColor.bind(null, colors.red)))
|
console.log(colors.red, item.amount + "%", item.test);
|
||||||
.concat(newItems.map(setColor.bind(null, colors.blue)))
|
});
|
||||||
.forEach(function(item) {
|
|
||||||
console.log(item);
|
improved.forEach(function(item) {
|
||||||
|
console.log(colors.green, item.amount + "%", item.test);
|
||||||
|
});
|
||||||
|
|
||||||
|
newItems.forEach(function(item) {
|
||||||
|
console.log(colors.blue, "NEW", item.test);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function httpget(options) {
|
||||||
|
return Bacon.fromCallback(function(callback) {
|
||||||
|
https.get(options, function(res){
|
||||||
|
var data = '';
|
||||||
|
|
||||||
|
res.on('data', function (chunk){
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end',function(){
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
function parseJSON(str) {
|
||||||
|
return JSON.parse(str);
|
||||||
function handleResults(stats) {
|
|
||||||
Object.keys(stats).forEach(openResultFile.bind(null, stats));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function runBrowsers(pages){
|
function writeResults() {
|
||||||
|
Object.keys(results).forEach(function(browser) {
|
||||||
|
var filename = "tests/results/" + browser + ".json";
|
||||||
|
try {
|
||||||
|
var oldResults = JSON.parse(fs.readFileSync(filename));
|
||||||
|
compareResults(oldResults, results[browser], browser);
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
var port = 5555,
|
var date = new Date();
|
||||||
stats = {},
|
var result = JSON.stringify({
|
||||||
browsers = ["chrome", "firefox", "internet explorer"],
|
browser: browser,
|
||||||
browsersDone = 0,
|
results: results[browser],
|
||||||
server = createServer(port),
|
timestamp: date.toISOString()
|
||||||
numPages = pages.length;
|
|
||||||
|
|
||||||
var browserDone = function() {
|
|
||||||
if (++browsersDone >= browsers.length) {
|
|
||||||
server.close();
|
|
||||||
handleResults(stats);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
browsers.forEach(function(browserName){
|
|
||||||
var browser = new webdriver({
|
|
||||||
browser: browserName
|
|
||||||
}),
|
|
||||||
browserType;
|
|
||||||
browserName = browserName.replace("internet explorer", "iexplorer");
|
|
||||||
browser.status(function(browserInfo){
|
|
||||||
browserType = [browserName, browser.version, browserInfo.os.name.replace(/\s+/g, "-").toLowerCase()].join("-");
|
|
||||||
var date = new Date(),
|
|
||||||
obj = {
|
|
||||||
tests: {},
|
|
||||||
date: date.toISOString(),
|
|
||||||
version: browser.version
|
|
||||||
};
|
|
||||||
stats[browserType] = obj;
|
|
||||||
stats[browserName] = obj;
|
|
||||||
processPage(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function processPage(index) {
|
if (process.env.MONGOLAB_APIKEY) {
|
||||||
var page = pages[index++];
|
var options = {
|
||||||
testPage(browser, "http://localhost:" + port + "/" + page + "?selenium", function(result) {
|
host: "api.mongolab.com",
|
||||||
if (numPages > index) {
|
port: 443,
|
||||||
processPage(index);
|
path: "/api/1/databases/html2canvas/collections/webdriver-results?apiKey=" + process.env.MONGOLAB_APIKEY + '&q={"browser":"' + browser + '"}&fo=true&s={"timestamp":-1}'
|
||||||
|
};
|
||||||
|
|
||||||
|
httpget(options).map(parseJSON).onValue(function(data) {
|
||||||
|
compareResults(data.results, results[browser], browser);
|
||||||
|
|
||||||
|
options.method = 'POST';
|
||||||
|
options.path = "/api/1/databases/html2canvas/collections/webdriver-results?apiKey=" + process.env.MONGOLAB_APIKEY;
|
||||||
|
options.headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': result.length
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Sending results for", browser);
|
||||||
|
var request = https.request(options, function(res) {
|
||||||
|
console.log(colors.green, "Results sent for", browser);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.write(result);
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(colors.violet, "Writing", browser + ".json");
|
||||||
|
fs.writeFile(filename, result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function webdriverOptions(browserName, version, platform) {
|
||||||
|
var options = {};
|
||||||
|
if (process.env.SAUCE_USERNAME && process.env.SAUCE_ACCESS_KEY) {
|
||||||
|
options = {
|
||||||
|
port: 4445,
|
||||||
|
hostname: "localhost",
|
||||||
|
name: process.env.TRAVIS_JOB_ID || "Manual run",
|
||||||
|
username: process.env.SAUCE_USERNAME,
|
||||||
|
password: process.env.SAUCE_ACCESS_KEY,
|
||||||
|
desiredCapabilities: {
|
||||||
|
browserName: browserName,
|
||||||
|
version: version,
|
||||||
|
platform: platform,
|
||||||
|
"tunnel-identifier": process.env.TRAVIS_JOB_NUMBER
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapResults(result) {
|
||||||
|
if (!results[result.browser]) {
|
||||||
|
results[result.browser] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
results[result.browser].push({
|
||||||
|
test: result.testCase,
|
||||||
|
result: result.accuracy
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatResultName(navigator) {
|
||||||
|
return (navigator.browser + "-" + ((navigator.version) ? navigator.version : "release") + "-" + navigator.platform).replace(/ /g, "").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function webdriverStream(navigator) {
|
||||||
|
var drive = Bacon.fromCallback(discover, "drive", "v2").toProperty();
|
||||||
|
var auth = Bacon.fromCallback(createToken, "95492219822.apps.googleusercontent.com").toProperty();
|
||||||
|
|
||||||
|
return Bacon.fromCallback(function(callback) {
|
||||||
|
new WebDriver.Session(webdriverOptions(navigator.browser, navigator.version, navigator.platform), function() {
|
||||||
|
var browser = this;
|
||||||
|
|
||||||
|
var resultStream = Bacon.fromArray(tests).flatMap(function(testCase) {
|
||||||
|
console.log(colors.green, "STARTING",formatResultName(navigator), testCase, colors.clear);
|
||||||
|
browser.url = "http://localhost:" + port + "/" + testCase + "?selenium";
|
||||||
|
var canvas = browser.element(".html2canvas", 15000);
|
||||||
|
var dataUrl = Bacon.constant(browser.execute(canvasToDataUrl, canvas));
|
||||||
|
var screenshot = Bacon.constant(browser.screenshot());
|
||||||
|
var result = dataUrl.flatMap(getPixelArray).combine(screenshot.flatMap(getPixelArray), calculateDifference);
|
||||||
|
console.log(colors.green, "COMPLETE", formatResultName(navigator), testCase, colors.clear);
|
||||||
|
return Bacon.combineTemplate({
|
||||||
|
browser: formatResultName(navigator),
|
||||||
|
testCase: testCase,
|
||||||
|
accuracy: result,
|
||||||
|
dataUrl: dataUrl,
|
||||||
|
screenshot: screenshot
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fs.existsSync('tests/certificate.pem')) {
|
||||||
|
Bacon.combineWith(permissionRequest, drive, auth, Bacon.combineWith(uploadRequest, drive, auth, resultStream.doAction(mapResults).flatMap(createImages)).flatMap(executeRequest)).flatMap(executeRequestOriginal).onValue(uploadImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
resultStream.onEnd(callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionRequest(client, authClient, images) {
|
||||||
|
var body = {
|
||||||
|
value: 'me',
|
||||||
|
type: 'anyone',
|
||||||
|
role: 'reader'
|
||||||
|
};
|
||||||
|
|
||||||
|
return images.map(function(data) {
|
||||||
|
var request = client.drive.permissions.insert({fileId: data.id}).withAuthClient(authClient);
|
||||||
|
request.body = body;
|
||||||
|
request.fileData = data;
|
||||||
|
return request;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeRequest(requests) {
|
||||||
|
return Bacon.combineAsArray(requests.map(function(request) {
|
||||||
|
return Bacon.fromCallback(function(callback) {
|
||||||
|
request.execute(function(err, result) {
|
||||||
|
if (!err) {
|
||||||
|
callback(result);
|
||||||
} else {
|
} else {
|
||||||
browser.close(browserDone);
|
console.log("Google drive error", err);
|
||||||
}
|
}
|
||||||
stats[browserType].tests[page] = result;
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeRequestOriginal(requests) {
|
||||||
|
return Bacon.combineAsArray(requests.map(function(request) {
|
||||||
|
return Bacon.fromCallback(function(callback) {
|
||||||
|
request.execute(function(err, result) {
|
||||||
|
if (!err) {
|
||||||
|
callback(request.fileData);
|
||||||
|
} else {
|
||||||
|
console.log("Google drive error", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createImages(data) {
|
||||||
|
var dataurlFileName = "tests/results/" + data.browser + "-" + data.testCase.replace(/\//g, "-") + "-html2canvas.png";
|
||||||
|
var screenshotFileName = "tests/results/" + data.browser + "-" + data.testCase.replace(/\//g, "-") + "-screencapture.png";
|
||||||
|
return Bacon.combineTemplate({
|
||||||
|
name: data.testCase,
|
||||||
|
dataurl: Bacon.fromNodeCallback(fs.writeFile, dataurlFileName, data.dataUrl, "base64").map(function() {
|
||||||
|
return dataurlFileName;
|
||||||
|
}),
|
||||||
|
screenshot: Bacon.fromNodeCallback(fs.writeFile, screenshotFileName, data.screenshot, "base64").map(function() {
|
||||||
|
return screenshotFileName;
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uploadImages(results) {
|
||||||
|
results.forEach(function(result) {
|
||||||
|
console.log(result.webContentLink);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function discover(api, version, callback) {
|
||||||
|
googleapis.discover(api, version).execute(function(err, client) {
|
||||||
|
if (!err) {
|
||||||
|
callback(client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createToken(account, callback) {
|
||||||
|
var payload = {
|
||||||
|
"iss": '95492219822@developer.gserviceaccount.com',
|
||||||
|
"scope": 'https://www.googleapis.com/auth/drive',
|
||||||
|
"aud":"https://accounts.google.com/o/oauth2/token",
|
||||||
|
"exp": ~~(new Date().getTime() / 1000) + (30 * 60),
|
||||||
|
"iat": ~~(new Date().getTime() / 1000 - 60)
|
||||||
|
},
|
||||||
|
key = fs.readFileSync('tests/certificate.pem', 'utf8'),
|
||||||
|
transporterTokenRequest = {
|
||||||
|
method: 'POST',
|
||||||
|
uri: 'https://accounts.google.com/o/oauth2/token',
|
||||||
|
form: {
|
||||||
|
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||||
|
assertion: jwt.sign(payload, key)
|
||||||
|
},
|
||||||
|
json: true
|
||||||
|
},
|
||||||
|
oauth2Client = new googleapis.OAuth2Client(account, "", "");
|
||||||
|
|
||||||
|
oauth2Client.transporter.request(transporterTokenRequest, function(err, result) {
|
||||||
|
if (!err) {
|
||||||
|
oauth2Client.credentials = result;
|
||||||
|
callback(oauth2Client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadRequest(client, authClient, data) {
|
||||||
|
return [
|
||||||
|
client.drive.files.insert({title: data.dataurl, mimeType: 'image/png', description: process.env.TRAVIS_JOB_ID}).withMedia('image/png', fs.readFileSync(data.dataurl)).withAuthClient(authClient),
|
||||||
|
client.drive.files.insert({title: data.screenshot, mimeType: 'image/png', description: process.env.TRAVIS_JOB_ID}).withMedia('image/png', fs.readFileSync(data.screenshot)).withAuthClient(authClient)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function runWebDriver() {
|
||||||
|
var browsers = [
|
||||||
|
{
|
||||||
|
browser: "chrome",
|
||||||
|
platform: "Windows 7"
|
||||||
|
},{
|
||||||
|
browser: "firefox",
|
||||||
|
version: "15",
|
||||||
|
platform: "Windows 7"
|
||||||
|
},{
|
||||||
|
browser: "internet explorer",
|
||||||
|
version: "9",
|
||||||
|
platform: "Windows 7"
|
||||||
|
},{
|
||||||
|
browser: "internet explorer",
|
||||||
|
version: "10",
|
||||||
|
platform: "Windows 8"
|
||||||
|
},{
|
||||||
|
browser: "safari",
|
||||||
|
version: "6",
|
||||||
|
platform: "OS X 10.8"
|
||||||
|
},{
|
||||||
|
browser: "chrome",
|
||||||
|
platform: "OS X 10.8"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var testRunnerStream = Bacon.sequentially(1000, browsers).flatMap(webdriverStream);
|
||||||
|
testRunnerStream.onEnd(writeResults);
|
||||||
|
testRunnerStream.onEnd(closeServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = [],
|
||||||
|
results = {},
|
||||||
|
testStream = getTests("tests/cases");
|
||||||
|
|
||||||
|
testStream.onValue(function(test) {
|
||||||
|
tests.push(test);
|
||||||
|
});
|
||||||
|
|
||||||
exports.tests = function() {
|
exports.tests = function() {
|
||||||
getBaselineFiles().forEach(fs.unlinkSync.bind(fs));
|
testStream.onEnd(runWebDriver);
|
||||||
walkDir("tests/cases", function(err, results) {
|
|
||||||
if (err) throw err;
|
|
||||||
runBrowsers(results);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.baseline = function() {
|
|
||||||
getBaselineFiles().forEach(function(file) {
|
|
||||||
var newName = file.substring(0, file.length - 9);
|
|
||||||
fs.renameSync(file, newName);
|
|
||||||
console.log(newName, "created");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.markdown = function() {
|
|
||||||
var data = {}, html = "<table><thead><tr><td></td>",
|
|
||||||
browsers = ["chrome", "firefox", "iexplorer"];
|
|
||||||
|
|
||||||
browsers.forEach(function(browser) {
|
|
||||||
data[browser] = JSON.parse(fs.readFileSync("tests/results/" + browser + ".json"));
|
|
||||||
html += "<th>" + browser + "<br />" + data[browser].version + "</th>";
|
|
||||||
});
|
|
||||||
html += "</tr></thead><tbody>\n";
|
|
||||||
|
|
||||||
Object.keys(data[browsers[0]].tests).forEach(function(testFile) {
|
|
||||||
html += "<tr><td>" + testFile.substring(12) + "</td>";
|
|
||||||
browsers.forEach(function(browser) {
|
|
||||||
html += "<td>" + Math.round(data[browser].tests[testFile] * 100) / 100 + "%</td>";
|
|
||||||
});
|
|
||||||
html += "</tr>\n"
|
|
||||||
});
|
|
||||||
|
|
||||||
html += "</tbody></table>";
|
|
||||||
|
|
||||||
fs.writeFileSync("tests/readme.md", html);
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
})();
|
Reference in New Issue
Block a user