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 |
7
.gitignore
vendored
7
.gitignore
vendored
@ -5,13 +5,16 @@
|
||||
/tests/flashcanvas.html
|
||||
/lib/
|
||||
/bin/
|
||||
/build/
|
||||
image.jpg
|
||||
/.project
|
||||
/.settings/
|
||||
/tests/certificate.pem
|
||||
node_modules/
|
||||
.envrc
|
||||
server.js
|
||||
*.sublime-workspace
|
||||
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">
|
||||
<html>
|
||||
<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>
|
||||
display/box/float/clear test
|
||||
</title>
|
||||
@ -182,5 +171,13 @@
|
||||
<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>.
|
||||
</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>
|
||||
</html>
|
||||
|
@ -3,17 +3,6 @@
|
||||
<head>
|
||||
<title></title>
|
||||
<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>
|
||||
.feedback-overlay-black{
|
||||
background-color:#000;
|
||||
@ -64,5 +53,13 @@
|
||||
|
||||
</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>
|
||||
</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');
|
||||
|
||||
};
|
45
package.json
45
package.json
@ -2,18 +2,45 @@
|
||||
"title": "html2canvas",
|
||||
"name": "html2canvas",
|
||||
"description": "Screenshots with JavaScript",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"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",
|
||||
"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",
|
||||
"licenses": [{
|
||||
"type": "MIT"
|
||||
}]
|
||||
|
||||
}
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
53
readme.md
53
readme.md
@ -1,16 +1,19 @@
|
||||
html2canvas
|
||||
===========
|
||||
|
||||
### Current build status ###
|
||||
[](https://travis-ci.org/niklasvh/html2canvas)
|
||||
|
||||
#### 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?###
|
||||
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 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 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 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.
|
||||
|
||||
@ -22,27 +25,28 @@ The script should work fine on the following browsers:
|
||||
* Google Chrome
|
||||
* Opera 12+
|
||||
* 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.
|
||||
|
||||
### Usage ###
|
||||
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:
|
||||
|
||||
html2canvas( [ document.body ], {
|
||||
html2canvas(document.body, {
|
||||
onrendered: function(canvas) {
|
||||
/* canvas is the actual canvas element,
|
||||
to append it to the page call for example
|
||||
document.body.appendChild( canvas );
|
||||
*/
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
### 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):
|
||||
|
||||
@ -50,14 +54,13 @@ Run the full build process (including lint, qunit and webdriver tests):
|
||||
|
||||
Skip lint and tests and simply build from source:
|
||||
|
||||
$ grunt concat
|
||||
$ grunt min
|
||||
$ grunt build
|
||||
|
||||
### 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:
|
||||
|
||||
@ -67,26 +70,24 @@ Run qunit tests:
|
||||
|
||||
$ 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 ###
|
||||
|
||||
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 ###
|
||||
|
||||
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 ###
|
||||
|
||||
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>
|
||||
* Switched to using grunt for building
|
||||
* Removed support for IE<9, including any FlashCanvas bits
|
||||
@ -96,7 +97,7 @@ v0.40 -
|
||||
* Support for placeholder rendering
|
||||
* 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>)
|
||||
* 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>)
|
||||
* 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>)
|
||||
* 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>)
|
||||
* 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 bookmarklet (<a href="https://github.com/niklasvh/html2canvas/commit/b320dd306e1a2d32a3bc5a71b6ebf6d8c060cde5">cobexer</a>)
|
||||
|
281
src/Core.js
281
src/Core.js
@ -5,20 +5,49 @@ previousElement,
|
||||
computedCSS,
|
||||
html2canvas;
|
||||
|
||||
function h2clog(a) {
|
||||
_html2canvas.Util = {};
|
||||
|
||||
_html2canvas.Util.log = function(a) {
|
||||
if (_html2canvas.logging && window.console && window.console.log) {
|
||||
window.console.log(a);
|
||||
}
|
||||
}
|
||||
|
||||
_html2canvas.Util = {};
|
||||
};
|
||||
|
||||
_html2canvas.Util.trimText = (function(isNative){
|
||||
return function(input){
|
||||
if(isNative) { return isNative.apply( input ); }
|
||||
else { return ((input || '') + '').replace( /^\s+|\s+$/g , '' ); }
|
||||
return function(input) {
|
||||
return isNative ? isNative.apply(input) : ((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) {
|
||||
var whitespace = ' \r\n\t',
|
||||
@ -119,38 +148,42 @@ _html2canvas.Util.parseBackgroundImage = function (value) {
|
||||
return results;
|
||||
};
|
||||
|
||||
_html2canvas.Util.Bounds = function getBounds (el) {
|
||||
var clientRect,
|
||||
bounds = {};
|
||||
|
||||
if (el.getBoundingClientRect){
|
||||
clientRect = el.getBoundingClientRect();
|
||||
_html2canvas.Util.Bounds = function (element) {
|
||||
var clientRect, bounds = {};
|
||||
|
||||
if (element.getBoundingClientRect){
|
||||
clientRect = element.getBoundingClientRect();
|
||||
|
||||
// TODO add scroll position to bounds, so no scrolling of window necessary
|
||||
bounds.top = clientRect.top;
|
||||
bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
|
||||
bounds.left = clientRect.left;
|
||||
|
||||
// older IE doesn't have width/height, but top/bottom instead
|
||||
bounds.width = clientRect.width || (clientRect.right - clientRect.left);
|
||||
bounds.height = clientRect.height || (clientRect.bottom - clientRect.top);
|
||||
|
||||
return bounds;
|
||||
|
||||
bounds.width = element.offsetWidth;
|
||||
bounds.height = element.offsetHeight;
|
||||
}
|
||||
|
||||
return bounds;
|
||||
};
|
||||
|
||||
_html2canvas.Util.getCSS = function (el, attribute, index) {
|
||||
// return $(el).css(attribute);
|
||||
// TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
|
||||
// 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,
|
||||
isBackgroundSizePosition = attribute.match( /^background(Size|Position)$/ );
|
||||
return {
|
||||
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 ) {
|
||||
var rsLeft = el.runtimeStyle && el.runtimeStyle[ attribute ],
|
||||
left,
|
||||
style = el.style;
|
||||
function toPX(element, attribute, value ) {
|
||||
var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
|
||||
left,
|
||||
style = element.style;
|
||||
|
||||
// Check if we are not dealing with pixels, (Opera has issues with this)
|
||||
// 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
|
||||
// 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
|
||||
left = style.left;
|
||||
|
||||
// Remember the original values
|
||||
left = style.left;
|
||||
|
||||
// Put in the new values to get a computed value out
|
||||
if ( rsLeft ) {
|
||||
el.runtimeStyle.left = el.currentStyle.left;
|
||||
}
|
||||
style.left = attribute === "fontSize" ? "1em" : (val || 0);
|
||||
val = style.pixelLeft + "px";
|
||||
|
||||
// Revert the changed values
|
||||
style.left = left;
|
||||
if ( rsLeft ) {
|
||||
el.runtimeStyle.left = rsLeft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!/^(thin|medium|thick)$/i.test( val )) {
|
||||
return Math.round(parseFloat( val )) + "px";
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
if (previousElement !== el) {
|
||||
computedCSS = document.defaultView.getComputedStyle(el, null);
|
||||
}
|
||||
val = computedCSS[attribute];
|
||||
|
||||
if (isBackgroundSizePosition) {
|
||||
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
|
||||
|
||||
} else {
|
||||
val[ 0 ] = ( val[ 0 ].indexOf( "%" ) === -1 ) ? toPX( attribute + "X", val[ 0 ] ) : val[ 0 ];
|
||||
if(val[ 1 ] === undefined) {
|
||||
if(attribute === 'backgroundSize') {
|
||||
val[ 1 ] = 'auto';
|
||||
return val;
|
||||
}
|
||||
else {
|
||||
// IE 9 doesn't return double digit always
|
||||
val[ 1 ] = val[ 0 ];
|
||||
}
|
||||
// Put in the new values to get a computed value out
|
||||
if (rsLeft) {
|
||||
element.runtimeStyle.left = element.currentStyle.left;
|
||||
}
|
||||
style.left = attribute === "fontSize" ? "1em" : (value || 0);
|
||||
value = style.pixelLeft + "px";
|
||||
|
||||
// Revert the changed values
|
||||
style.left = left;
|
||||
if (rsLeft) {
|
||||
element.runtimeStyle.left = rsLeft;
|
||||
}
|
||||
val[ 1 ] = ( val[ 1 ].indexOf( "%" ) === -1 ) ? toPX( attribute + "Y", val[ 1 ] ) : val[ 1 ];
|
||||
}
|
||||
} else if ( /border(Top|Bottom)(Left|Right)Radius/.test( attribute) ) {
|
||||
var arr = val.split(" ");
|
||||
if ( arr.length <= 1 ) {
|
||||
arr[ 1 ] = arr[ 0 ];
|
||||
}
|
||||
arr[ 0 ] = parseInt( arr[ 0 ], 10 );
|
||||
arr[ 1 ] = parseInt( arr[ 1 ], 10 );
|
||||
val = arr;
|
||||
}
|
||||
|
||||
return val;
|
||||
if (!/^(thin|medium|thick)$/i.test(value)) {
|
||||
return Math.round(parseFloat(value)) + "px";
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function asInt(val) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
|
||||
function parseBackgroundSizePosition(value, element, attribute, index) {
|
||||
value = (value || '').split(',');
|
||||
value = value[index || 0] || value[0] || 'auto';
|
||||
value = _html2canvas.Util.trimText(value).split(' ');
|
||||
|
||||
if(attribute === 'backgroundSize' && (!value[0] || value[0].match(/cover|contain|auto/))) {
|
||||
//these values will be handled in the parent function
|
||||
} else {
|
||||
value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
|
||||
if(value[1] === undefined) {
|
||||
if(attribute === 'backgroundSize') {
|
||||
value[1] = 'auto';
|
||||
return value;
|
||||
} else {
|
||||
// IE 9 doesn't return double digit always
|
||||
value[1] = value[0];
|
||||
}
|
||||
}
|
||||
value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
_html2canvas.Util.getCSS = function (element, attribute, index) {
|
||||
if (previousElement !== element) {
|
||||
computedCSS = document.defaultView.getComputedStyle(element, null);
|
||||
}
|
||||
|
||||
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 ){
|
||||
@ -235,18 +273,18 @@ _html2canvas.Util.resizeBounds = function( current_width, current_height, target
|
||||
if(!stretch_mode || stretch_mode === 'auto') {
|
||||
output_width = target_width;
|
||||
output_height = target_height;
|
||||
|
||||
} else if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
|
||||
output_height = target_height;
|
||||
output_width = target_height * current_ratio;
|
||||
} else {
|
||||
if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
|
||||
output_height = target_height;
|
||||
output_width = target_height * current_ratio;
|
||||
} else {
|
||||
output_width = target_width;
|
||||
output_height = target_width / current_ratio;
|
||||
}
|
||||
output_width = target_width;
|
||||
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 ) {
|
||||
@ -271,24 +309,21 @@ function backgroundBoundsFactory( prop, el, bounds, image, imageIndex, backgroun
|
||||
if(prop !== 'backgroundSize') {
|
||||
left -= (backgroundSize || image).width*percentage;
|
||||
}
|
||||
|
||||
} else {
|
||||
if(prop === 'backgroundSize') {
|
||||
if(bgposition[0] === 'auto') {
|
||||
left = image.width;
|
||||
|
||||
} else {
|
||||
if(bgposition[0].match(/contain|cover/)) {
|
||||
var resized = _html2canvas.Util.resizeBounds( image.width, image.height, bounds.width, bounds.height, bgposition[0] );
|
||||
if (/contain|cover/.test(bgposition[0])) {
|
||||
var resized = _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, bgposition[0]);
|
||||
left = resized.width;
|
||||
topPos = resized.height;
|
||||
} else {
|
||||
left = parseInt (bgposition[0], 10 );
|
||||
left = parseInt(bgposition[0], 10);
|
||||
}
|
||||
}
|
||||
|
||||
} 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 );
|
||||
return { left: result[0], top: result[1] };
|
||||
};
|
||||
|
||||
_html2canvas.Util.BackgroundSize = function( el, bounds, image, imageIndex ) {
|
||||
var result = backgroundBoundsFactory( 'backgroundSize', el, bounds, image, imageIndex );
|
||||
return { width: result[0], height: result[1] };
|
||||
@ -335,45 +371,40 @@ _html2canvas.Util.Extend = function (options, defaults) {
|
||||
* http://jquery.org/license
|
||||
*/
|
||||
_html2canvas.Util.Children = function( elem ) {
|
||||
|
||||
|
||||
var children;
|
||||
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 = [];
|
||||
|
||||
if ( array !== null ) {
|
||||
|
||||
(function( first, second ) {
|
||||
if (array !== null) {
|
||||
(function(first, second ) {
|
||||
var i = first.length,
|
||||
j = 0;
|
||||
|
||||
if ( typeof second.length === "number" ) {
|
||||
for ( var l = second.length; j < l; j++ ) {
|
||||
first[ i++ ] = second[ j ];
|
||||
if (typeof second.length === "number") {
|
||||
for (var l = second.length; j < l; j++) {
|
||||
first[i++] = second[j];
|
||||
}
|
||||
|
||||
} else {
|
||||
while ( second[j] !== undefined ) {
|
||||
first[ i++ ] = second[ j++ ];
|
||||
while (second[j] !== undefined) {
|
||||
first[i++] = second[j++];
|
||||
}
|
||||
}
|
||||
|
||||
first.length = i;
|
||||
|
||||
return first;
|
||||
})( ret, array );
|
||||
|
||||
})(ret, array);
|
||||
}
|
||||
|
||||
return ret;
|
||||
})( elem.childNodes );
|
||||
})(elem.childNodes);
|
||||
|
||||
} catch (ex) {
|
||||
h2clog("html2canvas.Util.Children failed with exception: " + ex.message);
|
||||
_html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
|
||||
children = [];
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
_html2canvas.Util.isTransparent = function(backgroundColor) {
|
||||
return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
|
||||
};
|
107
src/Generate.js
107
src/Generate.js
@ -1,6 +1,8 @@
|
||||
(function(){
|
||||
var Util = _html2canvas.Util,
|
||||
Generate = {};
|
||||
|
||||
_html2canvas.Generate = {};
|
||||
_html2canvas.Generate = Generate;
|
||||
|
||||
var reGradients = [
|
||||
/^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
|
||||
@ -18,7 +20,7 @@
|
||||
* TODO: Add old Webkit -webkit-gradient(radial, ...) support
|
||||
* 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;
|
||||
|
||||
for(i = 0; i < len; i+=1){
|
||||
@ -320,14 +322,25 @@
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
var canvas = document.createElement('canvas'),
|
||||
ctx = canvas.getContext('2d'),
|
||||
gradient, grad, i, len;
|
||||
gradient, grad;
|
||||
|
||||
canvas.width = bounds.width;
|
||||
canvas.height = bounds.height;
|
||||
@ -336,72 +349,46 @@
|
||||
gradient = _html2canvas.Generate.parseGradient(src, bounds);
|
||||
|
||||
if(gradient) {
|
||||
if(gradient.type === 'linear') {
|
||||
grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
|
||||
switch(gradient.type) {
|
||||
case 'linear':
|
||||
grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
|
||||
gradient.colorStops.forEach(addScrollStops(grad));
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(0, 0, bounds.width, bounds.height);
|
||||
break;
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
case 'circle':
|
||||
grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
|
||||
gradient.colorStops.forEach(addScrollStops(grad));
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(0, 0, bounds.width, bounds.height);
|
||||
break;
|
||||
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(0, 0, bounds.width, bounds.height);
|
||||
case 'ellipse':
|
||||
var canvasRadial = document.createElement('canvas'),
|
||||
ctxRadial = canvasRadial.getContext('2d'),
|
||||
ri = Math.max(gradient.rx, gradient.ry),
|
||||
di = ri * 2;
|
||||
|
||||
} else if(gradient.type === 'circle') {
|
||||
canvasRadial.width = canvasRadial.height = di;
|
||||
|
||||
grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(0, 0, bounds.width, bounds.height);
|
||||
|
||||
} else if(gradient.type === 'ellipse') {
|
||||
|
||||
// draw circle
|
||||
var canvasRadial = document.createElement('canvas'),
|
||||
ctxRadial = canvasRadial.getContext('2d'),
|
||||
ri = Math.max(gradient.rx, gradient.ry),
|
||||
di = ri * 2, imgRadial;
|
||||
|
||||
canvasRadial.width = canvasRadial.height = di;
|
||||
|
||||
grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
|
||||
|
||||
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.fillRect(0, 0, di, di);
|
||||
|
||||
ctx.fillStyle = gradient.colorStops[i - 1].color;
|
||||
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);
|
||||
ctxRadial.fillStyle = grad;
|
||||
ctxRadial.fillRect(0, 0, di, di);
|
||||
|
||||
ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
};
|
||||
|
||||
_html2canvas.Generate.ListAlpha = function(number) {
|
||||
Generate.ListAlpha = function(number) {
|
||||
var tmp = "",
|
||||
modulus;
|
||||
|
||||
@ -414,7 +401,7 @@
|
||||
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"],
|
||||
decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
|
||||
roman = "",
|
||||
@ -433,7 +420,5 @@
|
||||
}
|
||||
|
||||
return roman;
|
||||
|
||||
};
|
||||
|
||||
})();
|
317
src/Parse.js
317
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
|
||||
numDraws = 0,
|
||||
doc = element.ownerDocument,
|
||||
support = _html2canvas.Util.Support(options, doc),
|
||||
Util = _html2canvas.Util,
|
||||
support = Util.Support(options, doc),
|
||||
ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"),
|
||||
body = doc.body,
|
||||
getCSS = _html2canvas.Util.getCSS,
|
||||
getCSS = Util.getCSS,
|
||||
pseudoHide = "___html2canvas___pseudoelement",
|
||||
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) {
|
||||
switch(transform){
|
||||
case "lowercase":
|
||||
return text.toLowerCase();
|
||||
case "capitalize":
|
||||
return text.replace( /(^|\s|:|-|\(|\))([a-z])/g , function (m, p1, p2) {
|
||||
if (m.length > 0) {
|
||||
return p1 + p2.toUpperCase();
|
||||
}
|
||||
} );
|
||||
return text.replace( /(^|\s|:|-|\(|\))([a-z])/g, capitalize);
|
||||
case "uppercase":
|
||||
return text.toUpperCase();
|
||||
default:
|
||||
@ -69,7 +72,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
}
|
||||
|
||||
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);
|
||||
numDraws+=1;
|
||||
}
|
||||
@ -79,7 +82,8 @@ _html2canvas.Parse = function (images, options) {
|
||||
var align = false,
|
||||
bold = getCSS(el, "fontWeight"),
|
||||
family = getCSS(el, "fontFamily"),
|
||||
size = getCSS(el, "fontSize");
|
||||
size = getCSS(el, "fontSize"),
|
||||
shadows = Util.parseTextShadows(getCSS(el, "textShadow"));
|
||||
|
||||
switch(parseInt(bold, 10)){
|
||||
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("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"){
|
||||
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;
|
||||
if (support.rangeBounds) {
|
||||
if (textDecoration !== "none" || _html2canvas.Util.trimText(text).length !== 0) {
|
||||
if (support.rangeBounds && !transform) {
|
||||
if (textDecoration !== "none" || Util.trimText(text).length !== 0) {
|
||||
bounds = textRangeBounds(text, state.node, state.textOffset);
|
||||
}
|
||||
state.textOffset += text.length;
|
||||
} else if (state.node && typeof state.node.nodeValue === "string" ){
|
||||
var newTextNode = (isLast) ? state.node.splitText(text.length) : null;
|
||||
bounds = textWrapperBounds(state.node);
|
||||
bounds = textWrapperBounds(state.node, transform);
|
||||
state.node = newTextNode;
|
||||
}
|
||||
return bounds;
|
||||
@ -138,7 +151,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
return range.getBoundingClientRect();
|
||||
}
|
||||
|
||||
function textWrapperBounds(oldTextNode) {
|
||||
function textWrapperBounds(oldTextNode, transform) {
|
||||
var parent = oldTextNode.parentNode,
|
||||
wrapElement = doc.createElement('wrapper'),
|
||||
backupText = oldTextNode.cloneNode(true);
|
||||
@ -146,7 +159,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
wrapElement.appendChild(oldTextNode.cloneNode(true));
|
||||
parent.replaceChild(wrapElement, oldTextNode);
|
||||
|
||||
var bounds = _html2canvas.Util.Bounds(wrapElement);
|
||||
var bounds = transform ? Util.OffsetBounds(wrapElement) : Util.Bounds(wrapElement);
|
||||
parent.replaceChild(backupText, wrapElement);
|
||||
return bounds;
|
||||
}
|
||||
@ -163,7 +176,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
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"));
|
||||
textAlign = textAlign.replace(["-webkit-auto"],["auto"]);
|
||||
|
||||
@ -184,7 +197,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
}
|
||||
|
||||
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) {
|
||||
drawText(text, bounds.left, bounds.bottom, ctx);
|
||||
renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
|
||||
@ -207,20 +220,20 @@ _html2canvas.Parse = function (images, options) {
|
||||
|
||||
element.insertBefore(boundElement, element.firstChild);
|
||||
|
||||
bounds = _html2canvas.Util.Bounds(boundElement);
|
||||
bounds = Util.Bounds(boundElement);
|
||||
element.removeChild(boundElement);
|
||||
element.style.listStyleType = originalType;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
function elementIndex( el ) {
|
||||
function elementIndex(el) {
|
||||
var i = -1,
|
||||
count = 1,
|
||||
childs = el.parentNode.childNodes;
|
||||
|
||||
if (el.parentNode) {
|
||||
while( childs[ ++i ] !== el ) {
|
||||
if ( childs[ i ].nodeType === 1 ) {
|
||||
while(childs[++i] !== el) {
|
||||
if (childs[i].nodeType === 1) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@ -231,8 +244,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
}
|
||||
|
||||
function listItemText(element, type) {
|
||||
var currentIndex = elementIndex(element),
|
||||
text;
|
||||
var currentIndex = elementIndex(element), text;
|
||||
switch(type){
|
||||
case "decimal":
|
||||
text = currentIndex;
|
||||
@ -254,8 +266,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
break;
|
||||
}
|
||||
|
||||
text += ". ";
|
||||
return text;
|
||||
return text + ". ";
|
||||
}
|
||||
|
||||
function renderListItem(element, stack, elBounds) {
|
||||
@ -283,11 +294,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
|
||||
function loadImage (src){
|
||||
var img = images[src];
|
||||
if (img && img.succeeded === true) {
|
||||
return img.img;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return (img && img.succeeded === true) ? img.img : false;
|
||||
}
|
||||
|
||||
function clipBounds(src, dst){
|
||||
@ -304,22 +311,29 @@ _html2canvas.Parse = function (images, options) {
|
||||
};
|
||||
}
|
||||
|
||||
function setZ(zIndex, parentZ){
|
||||
// TODO fix static elements overlapping relative/absolute elements under same stack, if they are defined after them
|
||||
var newContext;
|
||||
if (!parentZ){
|
||||
newContext = h2czContext(0);
|
||||
return newContext;
|
||||
function setZ(element, stack, parentStack){
|
||||
var newContext,
|
||||
isPositioned = stack.cssPosition !== 'static',
|
||||
zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto',
|
||||
opacity = getCSS(element, 'opacity'),
|
||||
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) {
|
||||
@ -509,8 +523,8 @@ _html2canvas.Parse = function (images, options) {
|
||||
tlv = borderRadius[0][1],
|
||||
trh = borderRadius[1][0],
|
||||
trv = borderRadius[1][1],
|
||||
brv = borderRadius[2][0],
|
||||
brh = borderRadius[2][1],
|
||||
brh = borderRadius[2][0],
|
||||
brv = borderRadius[2][1],
|
||||
blh = borderRadius[3][0],
|
||||
blv = borderRadius[3][1],
|
||||
|
||||
@ -664,7 +678,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
c1: [bx + bw, by + bh],
|
||||
c2: [bx, by + bh],
|
||||
c3: [bx + borders[3].width, by],
|
||||
c4: [bx + bw - borders[2].width, by]
|
||||
c4: [bx + bw - borders[3].width, by]
|
||||
}, borderRadius[2], borderRadius[3],
|
||||
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
|
||||
break;
|
||||
@ -722,7 +736,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
valueWrap.style[property] = getCSS(el, property);
|
||||
} catch(e) {
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
var content = elStyle.content + '',
|
||||
@ -775,11 +789,16 @@ _html2canvas.Parse = function (images, options) {
|
||||
elps.className = pseudoHide + "-before " + pseudoHide + "-after";
|
||||
|
||||
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
|
||||
elps.style[prop] = elStyle[prop];
|
||||
// Prevent assigning of read only CSS Rules, ex. length, parentRule
|
||||
try {
|
||||
elps.style[prop] = elStyle[prop];
|
||||
} catch (e) {
|
||||
Util.log(['Tried to assign readonly property ', prop, 'Error:', e]);
|
||||
}
|
||||
});
|
||||
|
||||
if(isImage) {
|
||||
elps.src = _html2canvas.Util.parseBackgroundImage(content)[0].args[0];
|
||||
elps.src = Util.parseBackgroundImage(content)[0].args[0];
|
||||
} else {
|
||||
elps.innerHTML = content;
|
||||
}
|
||||
@ -850,11 +869,9 @@ _html2canvas.Parse = function (images, options) {
|
||||
}
|
||||
|
||||
function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) {
|
||||
var backgroundSize = _html2canvas.Util.BackgroundSize(el, bounds, image, imageIndex),
|
||||
backgroundPosition = _html2canvas.Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
|
||||
backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(function(value) {
|
||||
return value.trim();
|
||||
});
|
||||
var backgroundSize = Util.BackgroundSize(el, bounds, image, imageIndex),
|
||||
backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
|
||||
backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(Util.trimText);
|
||||
|
||||
image = resizeImage(image, backgroundSize);
|
||||
|
||||
@ -889,7 +906,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
|
||||
function renderBackgroundImage(element, bounds, ctx) {
|
||||
var backgroundImage = getCSS(element, "backgroundImage"),
|
||||
backgroundImages = _html2canvas.Util.parseBackgroundImage(backgroundImage),
|
||||
backgroundImages = Util.parseBackgroundImage(backgroundImage),
|
||||
image,
|
||||
imageIndex = backgroundImages.length;
|
||||
|
||||
@ -910,7 +927,7 @@ _html2canvas.Parse = function (images, options) {
|
||||
if (image) {
|
||||
renderBackgroundRepeating(element, bounds, ctx, image, imageIndex);
|
||||
} 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) {
|
||||
var opacity = getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1);
|
||||
ctx.setVariable("globalAlpha", opacity);
|
||||
return opacity;
|
||||
return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1));
|
||||
}
|
||||
|
||||
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),
|
||||
stack = {
|
||||
ctx: ctx,
|
||||
zIndex: setZ(getCSS(element, "zIndex"), (parentStack) ? parentStack.zIndex : null),
|
||||
opacity: setOpacity(ctx, element, parentStack),
|
||||
cssPosition: getCSS(element, "position"),
|
||||
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
|
||||
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.zIndex.children.push(stack);
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
@ -971,24 +1015,35 @@ _html2canvas.Parse = function (images, options) {
|
||||
return backgroundBounds;
|
||||
}
|
||||
|
||||
function renderElement(element, parentStack, pseudoElement){
|
||||
var bounds = _html2canvas.Util.Bounds(element),
|
||||
function getBounds(element, transform) {
|
||||
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,
|
||||
bgcolor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor"),
|
||||
stack = createStack(element, parentStack, bounds),
|
||||
stack = createStack(element, parentStack, bounds, transform),
|
||||
borders = stack.borders,
|
||||
ctx = stack.ctx,
|
||||
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);
|
||||
|
||||
ctx.save();
|
||||
ctx.clip();
|
||||
|
||||
if (backgroundBounds.height > 0 && backgroundBounds.width > 0){
|
||||
renderBackgroundColor(ctx, bounds, bgcolor);
|
||||
if (backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) {
|
||||
renderBackgroundColor(ctx, bounds, backgroundColor);
|
||||
renderBackgroundImage(element, backgroundBounds, ctx);
|
||||
} else if (ignoreBackground) {
|
||||
stack.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
@ -1006,13 +1061,13 @@ _html2canvas.Parse = function (images, options) {
|
||||
if ((image = loadImage(element.getAttribute('src')))) {
|
||||
renderImage(ctx, element, image, bounds, borders);
|
||||
} else {
|
||||
h2clog("html2canvas: Error loading <img>:" + element.getAttribute('src'));
|
||||
Util.log("html2canvas: Error loading <img>:" + element.getAttribute('src'));
|
||||
}
|
||||
break;
|
||||
case "INPUT":
|
||||
// TODO add all relevant type's, i.e. HTML5 new stuff
|
||||
// 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);
|
||||
}
|
||||
break;
|
||||
@ -1041,102 +1096,40 @@ _html2canvas.Parse = function (images, options) {
|
||||
return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
|
||||
}
|
||||
|
||||
function parseElement (el, stack, pseudoElement) {
|
||||
|
||||
if (isElementVisible(el)) {
|
||||
stack = renderElement(el, stack, pseudoElement) || stack;
|
||||
if (!ignoreElementsRegExp.test(el.nodeName)) {
|
||||
_html2canvas.Util.Children(el).forEach(function(node) {
|
||||
if (node.nodeType === 1) {
|
||||
parseElement(node, stack, pseudoElement);
|
||||
} else if (node.nodeType === 3) {
|
||||
renderText(el, node, stack);
|
||||
}
|
||||
});
|
||||
function parseElement (element, stack, pseudoElement) {
|
||||
if (isElementVisible(element)) {
|
||||
stack = renderElement(element, stack, pseudoElement, false) || stack;
|
||||
if (!ignoreElementsRegExp.test(element.nodeName)) {
|
||||
parseChildren(element, stack, pseudoElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() + ">";
|
||||
}
|
||||
}
|
||||
|
||||
function parseChildren(element, stack, pseudoElement) {
|
||||
Util.Children(element).forEach(function(node) {
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
parseElement(node, stack, pseudoElement);
|
||||
} else if (node.nodeType === node.TEXT_NODE) {
|
||||
renderText(element, node, stack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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() {
|
||||
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) {
|
||||
svgDOMRender(document.documentElement, stack);
|
||||
if (transparentBackground) {
|
||||
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);
|
||||
return stack;
|
||||
return {
|
||||
backgroundColor: background,
|
||||
stack: stack
|
||||
};
|
||||
}
|
||||
|
||||
return init();
|
||||
@ -1147,4 +1140,4 @@ function h2czContext(zindex) {
|
||||
zindex: zindex,
|
||||
children: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,13 @@ _html2canvas.Preload = function( options ) {
|
||||
cleanupDone: false
|
||||
},
|
||||
pageOrigin,
|
||||
Util = _html2canvas.Util,
|
||||
methods,
|
||||
i,
|
||||
count = 0,
|
||||
element = options.elements[0] || document.body,
|
||||
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,
|
||||
link = doc.createElement("a"),
|
||||
supportCORS = (function( img ){
|
||||
@ -31,9 +32,9 @@ _html2canvas.Preload = function( options ) {
|
||||
}
|
||||
|
||||
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){
|
||||
h2clog("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");
|
||||
Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");
|
||||
|
||||
if (typeof options.complete === "function"){
|
||||
options.complete(images);
|
||||
@ -141,9 +142,7 @@ _html2canvas.Preload = function( options ) {
|
||||
|
||||
// Firefox fails with permission denied on pages with iframes
|
||||
try {
|
||||
_html2canvas.Util.Children(el).forEach(function(img) {
|
||||
getImages(img);
|
||||
});
|
||||
Util.Children(el).forEach(getImages);
|
||||
}
|
||||
catch( e ) {}
|
||||
|
||||
@ -151,15 +150,15 @@ _html2canvas.Preload = function( options ) {
|
||||
elNodeType = el.nodeType;
|
||||
} catch (ex) {
|
||||
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) {
|
||||
loadPseudoElementImages(el);
|
||||
try {
|
||||
loadBackgroundImages(_html2canvas.Util.getCSS(el, 'backgroundImage'), el);
|
||||
loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el);
|
||||
} 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);
|
||||
}
|
||||
@ -231,17 +230,6 @@ _html2canvas.Preload = function( options ) {
|
||||
images.numTotal++;
|
||||
setImageLoadHandlers(img, imageObj);
|
||||
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 ) {
|
||||
imageObj = images[src] = {
|
||||
img: img
|
||||
@ -256,9 +244,9 @@ _html2canvas.Preload = function( options ) {
|
||||
var img, src;
|
||||
if (!images.cleanupDone) {
|
||||
if (cause && typeof cause === "string") {
|
||||
h2clog("html2canvas: Cleanup because: " + cause);
|
||||
Util.log("html2canvas: Cleanup because: " + cause);
|
||||
} else {
|
||||
h2clog("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
|
||||
Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
|
||||
}
|
||||
|
||||
for (src in images) {
|
||||
@ -276,7 +264,7 @@ _html2canvas.Preload = function( options ) {
|
||||
}
|
||||
images.numLoaded++;
|
||||
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);
|
||||
}
|
||||
|
||||
h2clog('html2canvas: Preload starts: finding background-images');
|
||||
Util.log('html2canvas: Preload starts: finding background-images');
|
||||
images.firstRun = true;
|
||||
|
||||
getImages(element);
|
||||
|
||||
h2clog('html2canvas: Preload: Finding images');
|
||||
Util.log('html2canvas: Preload: Finding images');
|
||||
// load <img> images
|
||||
for (i = 0; i < imgLen; i+=1){
|
||||
methods.loadImage( domImages[i].getAttribute( "src" ) );
|
||||
}
|
||||
|
||||
images.firstRun = false;
|
||||
h2clog('html2canvas: Preload: Done.');
|
||||
if ( images.numTotal === images.numLoaded ) {
|
||||
Util.log('html2canvas: Preload: Done.');
|
||||
if (images.numTotal === images.numLoaded) {
|
||||
start();
|
||||
}
|
||||
|
||||
return methods;
|
||||
|
||||
};
|
||||
};
|
||||
|
@ -117,6 +117,7 @@ function h2cRenderContext(width, height) {
|
||||
name: variable,
|
||||
'arguments': value
|
||||
});
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
@ -1,38 +1,81 @@
|
||||
_html2canvas.Renderer = function(parseQueue, options){
|
||||
|
||||
// http://www.w3.org/TR/CSS21/zindex.html
|
||||
function createRenderQueue(parseQueue) {
|
||||
var queue = [];
|
||||
var queue = [],
|
||||
rootContext;
|
||||
|
||||
var sortZ = function(zStack){
|
||||
var subStacks = [],
|
||||
stackValues = [];
|
||||
rootContext = (function buildStackingContext(rootNode) {
|
||||
var rootContext = {};
|
||||
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 (stackChild.children && stackChild.children.length > 0){
|
||||
subStacks.push(stackChild);
|
||||
stackValues.push(stackChild.zindex);
|
||||
} else {
|
||||
queue.push(stackChild);
|
||||
if (node.zIndex.ownStacking) {
|
||||
// '!' comes before numbers in sorted array
|
||||
contextForChildren = stub.context = { '!': [{node:node, children: []}]};
|
||||
childrenDest = undefined;
|
||||
} else if (isPositioned || isFloated) {
|
||||
childrenDest = stub.children = [];
|
||||
}
|
||||
});
|
||||
|
||||
stackValues.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
if (zi === 0 && specialParent) {
|
||||
specialParent.push(stub);
|
||||
} else {
|
||||
if (!context[zi]) { context[zi] = []; }
|
||||
context[zi].push(stub);
|
||||
}
|
||||
|
||||
stackValues.forEach(function(zValue) {
|
||||
var index;
|
||||
|
||||
subStacks.some(function(stack, i){
|
||||
index = i;
|
||||
return (stack.zindex === zValue);
|
||||
node.zIndex.children.forEach(function(childNode) {
|
||||
insert(contextForChildren, childNode, childrenDest);
|
||||
});
|
||||
sortZ(subStacks.splice(index, 1)[0]);
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
(function walk(arr) {
|
||||
arr.forEach(function(v) {
|
||||
list.push(v);
|
||||
if (v.children) { walk(v.children); }
|
||||
});
|
||||
})(nonPositioned.concat(floated, positioned));
|
||||
|
||||
list.forEach(function(v) {
|
||||
if (v.context) {
|
||||
sortZ(v.context);
|
||||
} else {
|
||||
queue.push(v.node);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
sortZ(parseQueue.zIndex);
|
||||
sortZ(rootContext);
|
||||
|
||||
return queue;
|
||||
}
|
||||
@ -54,5 +97,5 @@ _html2canvas.Renderer = function(parseQueue, options){
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
h2clog('html2canvas: Parse: SVG powered rendering available');
|
||||
_html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -71,11 +71,11 @@ window.html2canvas = function(elements, opts) {
|
||||
preload: function( opts ) {
|
||||
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 = {
|
||||
Canvas: undefined // We are assuming this will be used
|
||||
};
|
@ -1,14 +1,13 @@
|
||||
_html2canvas.Renderer.Canvas = function(options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var doc = document,
|
||||
safeImages = [],
|
||||
testCanvas = document.createElement("canvas"),
|
||||
testctx = testCanvas.getContext("2d"),
|
||||
Util = _html2canvas.Util,
|
||||
canvas = options.canvas || doc.createElement('canvas');
|
||||
|
||||
|
||||
function createShape(ctx, args) {
|
||||
ctx.beginPath();
|
||||
args.forEach(function(arg) {
|
||||
@ -32,99 +31,90 @@ _html2canvas.Renderer.Canvas = function(options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTransparent(backgroundColor) {
|
||||
return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
|
||||
}
|
||||
|
||||
function renderItem(ctx, item) {
|
||||
switch(item.type){
|
||||
case "variable":
|
||||
ctx[item.name] = item['arguments'];
|
||||
break;
|
||||
case "function":
|
||||
if (item.name === "createPattern") {
|
||||
if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
|
||||
try {
|
||||
ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
|
||||
switch(item.name) {
|
||||
case "createPattern":
|
||||
if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
|
||||
try {
|
||||
ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
|
||||
}
|
||||
catch(e) {
|
||||
Util.log("html2canvas: Renderer: Error creating pattern", e.message);
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
h2clog("html2canvas: Renderer: Error creating pattern", e.message);
|
||||
break;
|
||||
case "drawShape":
|
||||
createShape(ctx, item['arguments']);
|
||||
break;
|
||||
case "drawImage":
|
||||
if (item['arguments'][8] > 0 && item['arguments'][7] > 0) {
|
||||
if (!options.taintTest || (options.taintTest && safeImage(item))) {
|
||||
ctx.drawImage.apply( ctx, item['arguments'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (item.name === "drawShape") {
|
||||
createShape(ctx, item['arguments']);
|
||||
} else if (item.name === "drawImage") {
|
||||
if (item['arguments'][8] > 0 && item['arguments'][7] > 0) {
|
||||
if (!options.taintTest || (options.taintTest && safeImage(item))) {
|
||||
ctx.drawImage.apply( ctx, item['arguments'] );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx[item.name].apply(ctx, item['arguments']);
|
||||
break;
|
||||
default:
|
||||
ctx[item.name].apply(ctx, item['arguments']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return function(zStack, options, doc, queue, _html2canvas) {
|
||||
|
||||
return function(parsedData, options, document, queue, _html2canvas) {
|
||||
var ctx = canvas.getContext("2d"),
|
||||
storageContext,
|
||||
i,
|
||||
queueLen,
|
||||
newCanvas,
|
||||
bounds,
|
||||
fstyle;
|
||||
fstyle,
|
||||
zStack = parsedData.stack;
|
||||
|
||||
canvas.width = canvas.style.width = options.width || zStack.ctx.width;
|
||||
canvas.height = canvas.style.height = options.height || zStack.ctx.height;
|
||||
|
||||
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.fillStyle = fstyle;
|
||||
|
||||
queue.forEach(function(storageContext) {
|
||||
// set common settings for canvas
|
||||
ctx.textBaseline = "bottom";
|
||||
ctx.save();
|
||||
|
||||
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
|
||||
ctx.textBaseline = "bottom";
|
||||
|
||||
if (storageContext.clip){
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
// console.log(storageContext);
|
||||
ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
|
||||
ctx.clip();
|
||||
}
|
||||
|
||||
if (storageContext.ctx.storage) {
|
||||
storageContext.ctx.storage.forEach(renderItem.bind(null, ctx));
|
||||
}
|
||||
|
||||
if (storageContext.clip){
|
||||
ctx.restore();
|
||||
}
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
h2clog("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
|
||||
if (storageContext.clip){
|
||||
ctx.beginPath();
|
||||
ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
|
||||
ctx.clip();
|
||||
}
|
||||
|
||||
queueLen = options.elements.length;
|
||||
if (storageContext.ctx.storage) {
|
||||
storageContext.ctx.storage.forEach(function(item) {
|
||||
renderItem(ctx, item);
|
||||
});
|
||||
}
|
||||
|
||||
if (queueLen === 1) {
|
||||
ctx.restore();
|
||||
});
|
||||
|
||||
Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
|
||||
|
||||
if (options.elements.length === 1) {
|
||||
if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
|
||||
// crop image to the bounds of selected (single) element
|
||||
bounds = _html2canvas.Util.Bounds(options.elements[0]);
|
||||
newCanvas = doc.createElement('canvas');
|
||||
newCanvas.width = bounds.width;
|
||||
newCanvas.height = bounds.height;
|
||||
newCanvas = document.createElement('canvas');
|
||||
newCanvas.width = Math.ceil(bounds.width);
|
||||
newCanvas.height = Math.ceil(bounds.height);
|
||||
ctx = newCanvas.getContext("2d");
|
||||
|
||||
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;
|
||||
}
|
||||
|
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>
|
||||
|
||||
<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 style="background-position: 1px 0;"></div>
|
||||
<div style="background-position: 1em 0;"></div>
|
||||
|
@ -143,7 +143,36 @@ $(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 () {
|
||||
|
||||
|
@ -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>
|
||||
<tr><td>background/clip.html</td><td>100%</td><td>100%</td><td>99.89%</td></tr>
|
||||
<tr><td>background/encoded.html</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/multi.html</td><td>100%</td><td>100%</td><td>99.93%</td></tr>
|
||||
<tr><td>background/position.html</td><td>100%</td><td>100%</td><td>99.87%</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/repeat.html</td><td>100%</td><td>100%</td><td>99.92%</td></tr>
|
||||
<tr><td>border/dashed.html</td><td>96.45%</td><td>98.38%</td><td>97.7%</td></tr>
|
||||
<tr><td>border/dotted.html</td><td>97.41%</td><td>96.46%</td><td>95.93%</td></tr>
|
||||
<tr><td>border/double.html</td><td>97.96%</td><td>97.87%</td><td>97.95%</td></tr>
|
||||
<tr><td>border/radius.html</td><td>99.74%</td><td>99.77%</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>forms.html</td><td>95.96%</td><td>94.55%</td><td>95.01%</td></tr>
|
||||
<tr><td>images/canvas.html</td><td>99.86%</td><td>100%</td><td>100%</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/empty.html</td><td>99.86%</td><td>99.87%</td><td>99.85%</td></tr>
|
||||
<tr><td>images/images.html</td><td>83.72%</td><td>96.93%</td><td>55.09%</td></tr>
|
||||
<tr><td>images/svg.html</td><td>99.92%</td><td>96.79%</td><td>99.93%</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.html</td><td>99.64%</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></tr>
|
||||
<tr><td>list/upper-roman.html</td><td>99.45%</td><td>99.61%</td><td>35.94%</td></tr>
|
||||
<tr><td>overflow.html</td><td>96.85%</td><td>97.49%</td><td>96.5%</td></tr>
|
||||
<tr><td>pseudoelements.html</td><td>97.36%</td><td>97.94%</td><td>99.37%</td></tr>
|
||||
<tr><td>text/chinese.html</td><td>99.75%</td><td>99.74%</td><td>65.76%</td></tr>
|
||||
<tr><td>text/linethrough.html</td><td>97.14%</td><td>94.12%</td><td>47.08%</td></tr>
|
||||
<tr><td>text/text.html</td><td>95.71%</td><td>94.67%</td><td>85.01%</td></tr>
|
||||
<tr><td>text/underline-lineheight.html</td><td>97.06%</td><td>92.35%</td><td>53%</td></tr>
|
||||
<tr><td>text/underline.html</td><td>97.65%</td><td>93.5%</td><td>47.02%</td></tr>
|
||||
<tr><td>visibility.html</td><td>99.19%</td><td>98.92%</td><td>99.39%</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-index2.html</td><td>95.85%</td><td>98.06%</td><td>97.72%</td></tr>
|
||||
<tr><td>zindex/z-index3.html</td><td>98.6%</td><td>98.29%</td><td>98.56%</td></tr>
|
||||
</tbody></table>
|
||||
<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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><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><td>99.35%</td><td>96.49%</td><td>97.92%</td></tr>
|
||||
</tbody></table>
|
||||
|
@ -1,272 +1,427 @@
|
||||
(function(){
|
||||
"use strict;"
|
||||
var webdriver = require("webdriver.js").webdriver,
|
||||
http = require("http"),
|
||||
url = require("url"),
|
||||
path = require("path"),
|
||||
base64_arraybuffer = require('base64-arraybuffer'),
|
||||
PNG = require('png-js'),
|
||||
fs = require("fs");
|
||||
"use strict;";
|
||||
var WebDriver = require('sync-webdriver'),
|
||||
Bacon = require('baconjs').Bacon,
|
||||
express = require('express'),
|
||||
http = require("http"),
|
||||
https = require("https"),
|
||||
url = require("url"),
|
||||
path = require("path"),
|
||||
base64_arraybuffer = require('base64-arraybuffer'),
|
||||
PNG = require('png-js'),
|
||||
fs = require("fs"),
|
||||
googleapis = require('googleapis'),
|
||||
jwt = require('jwt-sign');
|
||||
|
||||
function createServer(port) {
|
||||
return http.createServer(function(request, response) {
|
||||
var uri = url.parse(request.url).pathname,
|
||||
filename = path.join(process.cwd(), uri);
|
||||
var port = 8080,
|
||||
app = express(),
|
||||
colors = {
|
||||
red: "\x1b[1;31m",
|
||||
blue: "\x1b[1;36m",
|
||||
violet: "\x1b[0;35m",
|
||||
green: "\x1b[0;32m",
|
||||
clear: "\x1b[0m"
|
||||
};
|
||||
|
||||
fs.exists(filename, function(exists) {
|
||||
if(!exists) {
|
||||
response.writeHead(404, {
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.write("404 Not Found\n");
|
||||
response.end();
|
||||
return;
|
||||
var server = app.listen(port);
|
||||
|
||||
app.use('/index.html', function(req, res){
|
||||
res.send("<ul>" + tests.map(function(test) {
|
||||
return "<li><a href='" + test + "'>" + test + "</a></li>";
|
||||
}).join("") + "</ul>");
|
||||
});
|
||||
|
||||
app.use('/', express.static(__dirname + "/../"));
|
||||
|
||||
function mapStat(item) {
|
||||
return Bacon.combineTemplate({
|
||||
stat: Bacon.fromNodeCallback(fs.stat, item),
|
||||
item: item
|
||||
});
|
||||
}
|
||||
|
||||
function isDirectory(item) {
|
||||
return item.stat.isDirectory();
|
||||
}
|
||||
|
||||
function getItem(item) {
|
||||
return item.item;
|
||||
}
|
||||
|
||||
function isFile(item) {
|
||||
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;
|
||||
for (; index < len; index++) {
|
||||
if (screenPixels[index] - h2cPixels[index] !== 0) {
|
||||
diff++;
|
||||
}
|
||||
}
|
||||
return (100 - (Math.round((diff/h2cPixels.length) * 10000) / 100));
|
||||
}
|
||||
|
||||
function canvasToDataUrl(canvas) {
|
||||
return canvas.toDataURL("image/png").substring(22);
|
||||
}
|
||||
|
||||
function closeServer() {
|
||||
server.close();
|
||||
}
|
||||
|
||||
function findResult(testName, tests) {
|
||||
var item = null;
|
||||
return tests.some(function(testCase) {
|
||||
item = testCase;
|
||||
return testCase.test === testName;
|
||||
}) ? item : null;
|
||||
}
|
||||
|
||||
function compareResults(oldResults, newResults, browser) {
|
||||
var improved = [],
|
||||
regressed = [],
|
||||
newItems = [];
|
||||
|
||||
newResults.forEach(function(testCase){
|
||||
var testResult = testCase.result,
|
||||
oldResult = findResult(testCase.test, oldResults),
|
||||
oldResultValue = oldResult ? oldResult.result : null,
|
||||
dataObject = {
|
||||
amount: (Math.abs(testResult - oldResultValue) < 0.01) ? 0 : testResult - oldResultValue,
|
||||
test: testCase.test
|
||||
};
|
||||
if (oldResultValue === null) {
|
||||
newItems.push(dataObject);
|
||||
} else if (dataObject.amount > 0) {
|
||||
improved.push(dataObject);
|
||||
} else if (dataObject.amount < 0) {
|
||||
regressed.push(dataObject);
|
||||
}
|
||||
});
|
||||
|
||||
reportChanges(browser, improved, regressed, newItems);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
regressed.forEach(function(item) {
|
||||
console.log(colors.red, item.amount + "%", item.test);
|
||||
});
|
||||
|
||||
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 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 date = new Date();
|
||||
var result = JSON.stringify({
|
||||
browser: browser,
|
||||
results: results[browser],
|
||||
timestamp: date.toISOString()
|
||||
});
|
||||
|
||||
if (process.env.MONGOLAB_APIKEY) {
|
||||
var options = {
|
||||
host: "api.mongolab.com",
|
||||
port: 443,
|
||||
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] = [];
|
||||
}
|
||||
|
||||
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();
|
||||
results[result.browser].push({
|
||||
test: result.testCase,
|
||||
result: result.accuracy
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}).listen(port);
|
||||
}
|
||||
function formatResultName(navigator) {
|
||||
return (navigator.browser + "-" + ((navigator.version) ? navigator.version : "release") + "-" + navigator.platform).replace(/ /g, "").toLowerCase();
|
||||
}
|
||||
|
||||
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 webdriverStream(navigator) {
|
||||
var drive = Bacon.fromCallback(discover, "drive", "v2").toProperty();
|
||||
var auth = Bacon.fromCallback(createToken, "95492219822.apps.googleusercontent.com").toProperty();
|
||||
|
||||
function getPixelArray(base64, func) {
|
||||
var arraybuffer = base64_arraybuffer.decode(base64);
|
||||
(new PNG(arraybuffer)).decode(func);
|
||||
}
|
||||
return Bacon.fromCallback(function(callback) {
|
||||
new WebDriver.Session(webdriverOptions(navigator.browser, navigator.version, navigator.platform), function() {
|
||||
var browser = this;
|
||||
|
||||
function getBaselineFiles() {
|
||||
return fs.readdirSync("tests/results/").filter(function(name) {
|
||||
return /\.baseline$/.test(name);
|
||||
}).map(function(item) {
|
||||
return "tests/results/" + item;
|
||||
});
|
||||
}
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
function testPage(browser, url, done) {
|
||||
browser.url(url)
|
||||
.$(".html2canvas", 5000, function(){
|
||||
this.execute(function(){
|
||||
var canvas = $('.html2canvas')[0];
|
||||
return canvas.toDataURL("image/png").substring(22);
|
||||
},[], function(dataurl) {
|
||||
getPixelArray(dataurl, function(h2cPixels) {
|
||||
browser.screenshot(function(base64){
|
||||
getPixelArray(base64, function(screenPixels) {
|
||||
var len = h2cPixels.length, index = 0, diff = 0;
|
||||
for (; index < len; index++) {
|
||||
if (screenPixels[index] - h2cPixels[index] !== 0) {
|
||||
diff++;
|
||||
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);
|
||||
}
|
||||
}
|
||||
done(100 - (Math.round((diff/h2cPixels.length) * 10000) / 100));
|
||||
|
||||
resultStream.onEnd(callback);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var writeResultFile = function(filename, json, append) {
|
||||
fs.writeFile(filename + (append || ""), json);
|
||||
};
|
||||
function permissionRequest(client, authClient, images) {
|
||||
var body = {
|
||||
value: 'me',
|
||||
type: 'anyone',
|
||||
role: 'reader'
|
||||
};
|
||||
|
||||
var openResultFile = function(stats, browser) {
|
||||
var tests = stats[browser].tests,
|
||||
filename = "tests/results/" + browser + ".json",
|
||||
write = writeResultFile.bind(null, filename, JSON.stringify(stats[browser]));
|
||||
return images.map(function(data) {
|
||||
var request = client.drive.permissions.insert({fileId: data.id}).withAuthClient(authClient);
|
||||
request.body = body;
|
||||
request.fileData = data;
|
||||
return request;
|
||||
});
|
||||
}
|
||||
|
||||
fs.exists(filename, function(exists) {
|
||||
if(exists) {
|
||||
fs.readFile(filename, "binary", parseResultFile.bind(null, tests, browser, write));
|
||||
} else {
|
||||
write();
|
||||
}
|
||||
});
|
||||
};
|
||||
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 {
|
||||
console.log("Google drive error", err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
var setColor = function(color, text) {
|
||||
return [color, " * ", ((isNaN(text.amount)) ? "NEW" : text.amount + "%"), " ", text.test].join("");
|
||||
};
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
var parseResultFile = function(tests, browser, createResultFile, err, file) {
|
||||
if (err) throw err;
|
||||
var data = JSON.parse(file),
|
||||
improved = [],
|
||||
regressed = [],
|
||||
newItems = [],
|
||||
colors = {
|
||||
red: "\x1b[1;31m",
|
||||
blue: "\x1b[1;36m",
|
||||
violet: "\x1b[0;35m",
|
||||
green: "\x1b[0;32m"
|
||||
};
|
||||
|
||||
Object.keys(tests).forEach(function(test){
|
||||
var testResult = tests[test],
|
||||
dataResult = data.tests[test],
|
||||
dataObject = {
|
||||
amount: (Math.abs(testResult - dataResult) < 0.02) ? 0 : testResult - dataResult,
|
||||
test: test
|
||||
};
|
||||
|
||||
if (dataObject.amount > 0) {
|
||||
improved.push(dataObject);
|
||||
} else if (dataObject.amount < 0) {
|
||||
regressed.push(dataObject);
|
||||
} else if (dataResult === undefined) {
|
||||
newItems.push(dataObject);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (newItems.length > 0 || improved.length > 0 || regressed.length > 0) {
|
||||
if (regressed.length === 0) {
|
||||
createResultFile(".baseline");
|
||||
}
|
||||
|
||||
console.log(colors.violet, "********************");
|
||||
console.log((regressed.length > 0) ? colors.red : colors.green, browser);
|
||||
|
||||
improved.map(setColor.bind(null, colors.green))
|
||||
.concat(regressed.map(setColor.bind(null, colors.red)))
|
||||
.concat(newItems.map(setColor.bind(null, colors.blue)))
|
||||
.forEach(function(item) {
|
||||
console.log(item);
|
||||
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 handleResults(stats) {
|
||||
Object.keys(stats).forEach(openResultFile.bind(null, stats));
|
||||
}
|
||||
|
||||
function runBrowsers(pages){
|
||||
|
||||
var port = 5555,
|
||||
stats = {},
|
||||
browsers = ["chrome", "firefox", "internet explorer"],
|
||||
browsersDone = 0,
|
||||
server = createServer(port),
|
||||
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) {
|
||||
var page = pages[index++];
|
||||
testPage(browser, "http://localhost:" + port + "/" + page + "?selenium", function(result) {
|
||||
if (numPages > index) {
|
||||
processPage(index);
|
||||
} else {
|
||||
browser.close(browserDone);
|
||||
}
|
||||
stats[browserType].tests[page] = result;
|
||||
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() {
|
||||
getBaselineFiles().forEach(fs.unlinkSync.bind(fs));
|
||||
walkDir("tests/cases", function(err, results) {
|
||||
if (err) throw err;
|
||||
runBrowsers(results);
|
||||
});
|
||||
testStream.onEnd(runWebDriver);
|
||||
};
|
||||
|
||||
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