mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Compare commits
290 Commits
0.4.0
...
0.5.0-alph
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9a0d43d60b | ||
![]() |
3671de81f9 | ||
![]() |
f3b6df267e | ||
![]() |
60619dca72 | ||
![]() |
ed299b3db1 | ||
![]() |
c7e484af89 | ||
![]() |
19ecd8bd7f | ||
![]() |
edb113c230 | ||
![]() |
33cd2c5ef6 | ||
![]() |
3400354d78 | ||
![]() |
fc01263f68 | ||
![]() |
399ae9f33d | ||
![]() |
db88784655 | ||
![]() |
77c73c478f | ||
![]() |
37b39ec716 | ||
![]() |
6d53461a68 | ||
![]() |
33844129e9 | ||
![]() |
0df71b34f3 | ||
![]() |
3996449e88 | ||
![]() |
2699670bf1 | ||
![]() |
7a58ad019f | ||
![]() |
9b372a4399 | ||
![]() |
498527918c | ||
![]() |
e363f3813e | ||
![]() |
433dc98177 | ||
![]() |
b6a73b635a | ||
![]() |
e2713cd6f9 | ||
![]() |
4b771d558a | ||
![]() |
2f5f9f6e59 | ||
![]() |
60368d4670 | ||
![]() |
f5e318d968 | ||
![]() |
612e59c3d3 | ||
![]() |
fcbcb9bfaa | ||
![]() |
3b0352a3d7 | ||
![]() |
313c227a1f | ||
![]() |
893ce74a33 | ||
![]() |
e4329c4d37 | ||
![]() |
069140974b | ||
![]() |
1e826e32ae | ||
![]() |
0579804cbb | ||
![]() |
f15666e156 | ||
![]() |
d4cb7e8868 | ||
![]() |
657eb983cf | ||
![]() |
fc800bff9d | ||
![]() |
36648afc3d | ||
![]() |
aa3aafbc0c | ||
![]() |
525b5c4f36 | ||
![]() |
706b2abb19 | ||
![]() |
6b1d116a5e | ||
![]() |
772767d0d9 | ||
![]() |
7ebd191488 | ||
![]() |
6ece2a3d5a | ||
![]() |
1793b802b1 | ||
![]() |
8184b7cf51 | ||
![]() |
04bdb48cba | ||
![]() |
7e13231807 | ||
![]() |
199685ebd1 | ||
![]() |
444869e3ca | ||
![]() |
717f69d99a | ||
![]() |
541f6a62d8 | ||
![]() |
d8dcbdedd8 | ||
![]() |
5712b621ca | ||
![]() |
6073928978 | ||
![]() |
e103ad8219 | ||
![]() |
d5430070a2 | ||
![]() |
f3d45e005e | ||
![]() |
b60b4b2a45 | ||
![]() |
bd1abe1857 | ||
![]() |
8a3d1d7f22 | ||
![]() |
e6d31ada4a | ||
![]() |
19777c6623 | ||
![]() |
33d84c82b0 | ||
![]() |
1e19832171 | ||
![]() |
fa659ad1df | ||
![]() |
f517a35781 | ||
![]() |
3f3424e49c | ||
![]() |
1d8a316f13 | ||
![]() |
b1f948bb60 | ||
![]() |
24d9a22556 | ||
![]() |
7ee2f411b0 | ||
![]() |
9406f76a18 | ||
![]() |
6092629679 | ||
![]() |
349d0a300c | ||
![]() |
0a7df6d9b9 | ||
![]() |
70241a789d | ||
![]() |
075c836d16 | ||
![]() |
c9993a7237 | ||
![]() |
3b8d4dece2 | ||
![]() |
440120b087 | ||
![]() |
e80fe312ee | ||
![]() |
b141c9f0d1 | ||
![]() |
3a3a61e316 | ||
![]() |
6c08c3fa04 | ||
![]() |
d4c9a41873 | ||
![]() |
6347e7f043 | ||
![]() |
9220eb6def | ||
![]() |
af965cc3a6 | ||
![]() |
9d088fa431 | ||
![]() |
b8d3688c29 | ||
![]() |
9907149513 | ||
![]() |
645fcd60b3 | ||
![]() |
0325a9b836 | ||
![]() |
36052c2765 | ||
![]() |
281efd2d5e | ||
![]() |
44b958beaf | ||
![]() |
2a020e5a21 | ||
![]() |
c20e679f2c | ||
![]() |
52c669fe5b | ||
![]() |
382c16a522 | ||
![]() |
ba9d5201cf | ||
![]() |
2e2d722e3d | ||
![]() |
b9edf0b1c5 | ||
![]() |
07f793b0ed | ||
![]() |
180b624cb3 | ||
![]() |
b2280bc8ec | ||
![]() |
f3f92ab425 | ||
![]() |
503add6e2f | ||
![]() |
d2bfb810d4 | ||
![]() |
649cdd4e37 | ||
![]() |
73a34493ac | ||
![]() |
ce1c4c84f5 | ||
![]() |
44beaf2989 | ||
![]() |
d716210509 | ||
![]() |
be5d1f8665 | ||
![]() |
340b125b19 | ||
![]() |
ad1f0d418c | ||
![]() |
08373f0bd4 | ||
![]() |
6959058560 | ||
![]() |
d6ed6c0194 | ||
![]() |
9ee87339a3 | ||
![]() |
b7595e19e9 | ||
![]() |
281e6bbedf | ||
![]() |
fee91055b2 | ||
![]() |
650ead63e5 | ||
![]() |
b35fcaeaf9 | ||
![]() |
81c22866bc | ||
![]() |
25d892f525 | ||
![]() |
9db1ecfdfc | ||
![]() |
12d85e3c04 | ||
![]() |
3101f2007a | ||
![]() |
85b77ca49f | ||
![]() |
bb8c5a973b | ||
![]() |
0187fcab42 | ||
![]() |
cfe4137bcc | ||
![]() |
d2c3378c3e | ||
![]() |
95f4bcea0a | ||
![]() |
9bae5b610a | ||
![]() |
7ce46e95cd | ||
![]() |
84c1dc6283 | ||
![]() |
15ca3381eb | ||
![]() |
18d95d669b | ||
![]() |
5137e5f35a | ||
![]() |
314d26f1f1 | ||
![]() |
82e5a8a7c0 | ||
![]() |
9af96d3812 | ||
![]() |
6f2a775841 | ||
![]() |
f2b662801e | ||
![]() |
60587c72bf | ||
![]() |
d9d516d27e | ||
![]() |
899d5321d4 | ||
![]() |
5d20493f46 | ||
![]() |
b5891c49b4 | ||
![]() |
467ff87482 | ||
![]() |
9beae48cf0 | ||
![]() |
17731169e9 | ||
![]() |
e27c41efd3 | ||
![]() |
1f90defbfa | ||
![]() |
b4bb34c95b | ||
![]() |
64668fe694 | ||
![]() |
9ebae161e2 | ||
![]() |
729bc88d1f | ||
![]() |
b1c2f03ae9 | ||
![]() |
74cb3466ec | ||
![]() |
2afdcaff35 | ||
![]() |
1070cec852 | ||
![]() |
ba9a33b1bc | ||
![]() |
f474542382 | ||
![]() |
0cb259f6cd | ||
![]() |
1a7f5732bf | ||
![]() |
2b8389cb64 | ||
![]() |
8b8c080841 | ||
![]() |
6201e09118 | ||
![]() |
517fd8cd1d | ||
![]() |
e228fc57ce | ||
![]() |
46cc8b6975 | ||
![]() |
443fd17a12 | ||
![]() |
0b213eecef | ||
![]() |
ae1a15f7c5 | ||
![]() |
cea3005056 | ||
![]() |
1d4b1753d6 | ||
![]() |
f00b23a9ec | ||
![]() |
e9afe03960 | ||
![]() |
57d20a9794 | ||
![]() |
35c5ca3340 | ||
![]() |
7cc7f80ee2 | ||
![]() |
4c75d819a4 | ||
![]() |
838f91e156 | ||
![]() |
8bea01b81d | ||
![]() |
e115180731 | ||
![]() |
e782efa614 | ||
![]() |
806cd60474 | ||
![]() |
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 |
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,17 +1,11 @@
|
||||
/nbproject/
|
||||
/images/
|
||||
/tests/templates/
|
||||
/tests/cache/
|
||||
/tests/flashcanvas.html
|
||||
/lib/
|
||||
/bin/
|
||||
/build/
|
||||
image.jpg
|
||||
/.project
|
||||
/.settings/
|
||||
node_modules/
|
||||
.envrc
|
||||
server.js
|
||||
*.sublime-workspace
|
||||
chromedriver.log
|
||||
*.baseline
|
||||
*.baseline
|
||||
*.iml
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "src/fabric"]
|
||||
path = src/fabric
|
||||
url = https://github.com/kangax/fabric.js.git
|
19
.jshintrc
Normal file
19
.jshintrc
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"latedef": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"sub": true,
|
||||
"undef": true,
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"indent": 4,
|
||||
"globals": {
|
||||
"jQuery": true
|
||||
},
|
||||
"predef": ["-Promise"]
|
||||
}
|
21
.travis.yml
Normal file
21
.travis.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
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="
|
||||
addons:
|
||||
sauce_connect: true
|
||||
before_script:
|
||||
- npm install -g grunt-cli
|
||||
- npm install -g uglify-js
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/2b007d4f86de89588804
|
||||
on_success: always
|
||||
on_failure: always
|
||||
on_start: false
|
57
CHANGELOG.md
Normal file
57
CHANGELOG.md
Normal file
@@ -0,0 +1,57 @@
|
||||
### Changelog ###
|
||||
|
||||
v0.5.0-alpha2 - 3.2.2015
|
||||
* Switch to using browserify for building
|
||||
* Fix (#517) Chrome stretches background images with 'auto' or single attributes
|
||||
|
||||
v0.5.0-alpha - 19.1.2015
|
||||
* Complete rewrite of library
|
||||
* Switched interface to return Promise
|
||||
* Uses hidden iframe window to perform rendering, allowing async rendering and doesn't force the viewport to be scrolled to the top anymore.
|
||||
* Better support for unicode
|
||||
* Checkbox/radio button rendering
|
||||
* SVG rendering
|
||||
* iframe rendering
|
||||
* Changed format for proxy requests, permitting binary responses with CORS headers as well
|
||||
* Fixed many layering issues (see z-index tests)
|
||||
|
||||
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
|
||||
* Support for border-radius
|
||||
* Support for multiple background images, size, and clipping
|
||||
* Support for :before and :after pseudo elements
|
||||
* Support for placeholder rendering
|
||||
* Reformatted all tests to small units to test specific features
|
||||
|
||||
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>)
|
||||
* Radial gradients (<a href="https://github.com/niklasvh/html2canvas/commit/4f22c18043a73c0c3bbf3b5e4d62714c56acd3c7">SunboX</a>)
|
||||
* Split renderers to their own objects (<a href="https://github.com/niklasvh/html2canvas/commit/94f2f799a457cd29a21cc56ef8c06f1697866739">niklasvh</a>)
|
||||
* Simplified API, cleaned up code (<a href="https://github.com/niklasvh/html2canvas/commit/c7d526c9eaa6a4abf4754d205fe1dee360c7660e">niklasvh</a>)
|
||||
|
||||
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>)
|
||||
* Improved minification saved ~1K! (<a href="https://github.com/cobexer/html2canvas/commit/b82be022b2b9240bd503e078ac980bde2b953e43">cobexer</a>)
|
||||
* Added integrated support for Flashcanvas (<a href="https://github.com/niklasvh/html2canvas/commit/e9257191519f67d74fd5e364d8dee3c0963ba5fc">niklasvh</a>)
|
||||
* Fixed a variety of legacy IE bugs (<a href="https://github.com/niklasvh/html2canvas/commit/b65357c55d0701017bafcd357bc654b54d458f8f">niklasvh</a>)
|
||||
|
||||
v0.3.2 - 20.2.2012
|
||||
|
||||
* Added changelog!
|
||||
* Added bookmarklet (<a href="https://github.com/niklasvh/html2canvas/commit/b320dd306e1a2d32a3bc5a71b6ebf6d8c060cde5">cobexer</a>)
|
||||
* Option to select single element to render (<a href="https://github.com/niklasvh/html2canvas/commit/0cb252ada91c84ef411288b317c03e97da1f12ad">niklasvh</a>)
|
||||
* Fixed closure compiler warnings (<a href="https://github.com/niklasvh/html2canvas/commit/36ff1ec7aadcbdf66851a0b77f0b9e87e4a8e4a1">cobexer</a>)
|
||||
* Enable profiling in FF (<a href="https://github.com/niklasvh/html2canvas/commit/bbd75286a8406cf9e5aea01fdb7950d547edefb9">cobexer</a>)
|
220
Gruntfile.js
Normal file
220
Gruntfile.js
Normal file
@@ -0,0 +1,220 @@
|
||||
/*global module:false*/
|
||||
var _ = require('lodash'), path = require('path');
|
||||
var proxy = require('html2canvas-proxy');
|
||||
|
||||
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'
|
||||
};
|
||||
|
||||
var browsers = {
|
||||
chrome: {
|
||||
browserName: "chrome",
|
||||
platform: "Windows 7",
|
||||
version: "39"
|
||||
},
|
||||
firefox: {
|
||||
browserName: "firefox",
|
||||
version: "15",
|
||||
platform: "Windows 7"
|
||||
},
|
||||
ie9: {
|
||||
browserName: "internet explorer",
|
||||
version: "9",
|
||||
platform: "Windows 7"
|
||||
},
|
||||
ie10: {
|
||||
browserName: "internet explorer",
|
||||
version: "10",
|
||||
platform: "Windows 8"
|
||||
},
|
||||
ie11: {
|
||||
browserName: "internet explorer",
|
||||
version: "11",
|
||||
platform: "Windows 8.1"
|
||||
},
|
||||
safari6: {
|
||||
browserName: "safari",
|
||||
version: "6",
|
||||
platform: "OS X 10.8"
|
||||
},
|
||||
safari7:{
|
||||
browserName: "safari",
|
||||
platform: "OS X 10.9",
|
||||
version: "7"
|
||||
},
|
||||
chromeOSX:{
|
||||
browserName: "chrome",
|
||||
platform: "OS X 10.8",
|
||||
version: "39"
|
||||
}
|
||||
};
|
||||
grunt.initConfig({
|
||||
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
browserify: {
|
||||
dist: {
|
||||
src: ['src/core.js'],
|
||||
dest: 'dist/<%= pkg.name %>.js',
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
standalone: 'html2canvas'
|
||||
},
|
||||
banner: meta.banner
|
||||
}
|
||||
},
|
||||
svg: {
|
||||
src: [
|
||||
'src/fabric/dist/fabric.js'
|
||||
],
|
||||
dest: 'dist/<%= pkg.name %>.svg.js',
|
||||
options:{
|
||||
browserifyOptions: {
|
||||
standalone: 'html2canvas.svg'
|
||||
},
|
||||
banner: meta.banner
|
||||
}
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
server: {
|
||||
options: {
|
||||
port: 8080,
|
||||
base: './',
|
||||
keepalive: true
|
||||
}
|
||||
},
|
||||
altServer: {
|
||||
options: {
|
||||
port: 8083,
|
||||
base: './'
|
||||
}
|
||||
},
|
||||
cors: {
|
||||
options: {
|
||||
port: 8081,
|
||||
base: './',
|
||||
middleware: function(connect, options) {
|
||||
return [
|
||||
function(req, res, next) {
|
||||
if (req.url !== '/tests/assets/image2.jpg') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.end(require("fs").readFileSync('tests/assets/image2.jpg'));
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
proxy: {
|
||||
options: {
|
||||
port: 8082,
|
||||
middleware: function(connect, options) {
|
||||
return [
|
||||
function(req, res, next) {
|
||||
res.jsonp = function(content) {
|
||||
res.end(req.query.callback + "(" + JSON.stringify(content) + ")");
|
||||
};
|
||||
next();
|
||||
},
|
||||
proxy()
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
ci: {
|
||||
options: {
|
||||
port: 8080,
|
||||
base: './'
|
||||
}
|
||||
}
|
||||
},
|
||||
execute: {
|
||||
fabric: {
|
||||
options: {
|
||||
args: ['modules=' + ['text','serialization',
|
||||
'parser', 'gradient', 'pattern', 'shadow', 'freedrawing',
|
||||
'image_filters', 'serialization'].join(","), 'no-es5-compat', 'dest=' + path.resolve(__dirname, 'src/fabric/dist/') + '/']
|
||||
},
|
||||
src: ['src/fabric/build.js']
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
dist: {
|
||||
src: ['<%= browserify.dist.dest %>'],
|
||||
dest: 'dist/<%= pkg.name %>.min.js'
|
||||
},
|
||||
svg: {
|
||||
src: ['<%= browserify.svg.dest %>'],
|
||||
dest: 'dist/<%= pkg.name %>.svg.min.js'
|
||||
},
|
||||
options: {
|
||||
banner: meta.banner
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
files: ['src/**/*', '!src/fabric/**/*'],
|
||||
tasks: ['jshint', 'build']
|
||||
},
|
||||
jshint: {
|
||||
all: ['src/*.js', 'src/renderers/*.js'],
|
||||
options: grunt.file.readJSON('./.jshintrc')
|
||||
},
|
||||
mochacli: {
|
||||
options: {
|
||||
reporter: 'spec'
|
||||
},
|
||||
all: ['tests/node/*.js']
|
||||
},
|
||||
mocha_phantomjs: {
|
||||
all: ['tests/mocha/**/*.html']
|
||||
},
|
||||
mocha_webdriver: browsers,
|
||||
webdriver: browsers
|
||||
});
|
||||
|
||||
grunt.registerTask('webdriver', 'Browser render tests', function(browser, test) {
|
||||
var selenium = require("./tests/selenium.js");
|
||||
var done = this.async();
|
||||
var browsers = (browser) ? [grunt.config.get(this.name + "." + browser)] : _.values(grunt.config.get(this.name));
|
||||
selenium.tests(browsers, test).catch(function() {
|
||||
done(false);
|
||||
}).finally(function() {
|
||||
console.log("Done");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
grunt.registerTask('mocha_webdriver', 'Browser mocha tests', function(browser, test) {
|
||||
var selenium = require("./tests/mocha/selenium.js");
|
||||
var done = this.async();
|
||||
var browsers = (browser) ? [grunt.config.get(this.name + "." + browser)] : _.values(grunt.config.get(this.name));
|
||||
selenium.tests(browsers, test).catch(function() {
|
||||
done(false);
|
||||
}).finally(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-mocha-phantomjs');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks('grunt-execute');
|
||||
grunt.loadNpmTasks('grunt-mocha-cli');
|
||||
|
||||
grunt.registerTask('server', ['connect:cors', 'connect:proxy', 'connect:altServer', 'connect:server']);
|
||||
grunt.registerTask('build', ['execute', 'browserify', 'uglify']);
|
||||
grunt.registerTask('default', ['jshint', 'build', 'mochacli', 'connect:altServer', 'mocha_phantomjs']);
|
||||
grunt.registerTask('travis', ['jshint', 'build', 'connect:altServer', 'connect:ci', 'connect:proxy', 'connect:cors', 'mocha_phantomjs', 'webdriver']);
|
||||
|
||||
};
|
9
bower.json
Normal file
9
bower.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "html2canvas",
|
||||
"description": "Screenshots with JavaScript",
|
||||
"main": "dist/html2canvas.js",
|
||||
"ignore": [
|
||||
"tests",
|
||||
".travis.yml"
|
||||
]
|
||||
}
|
4534
dist/html2canvas.js
vendored
Normal file
4534
dist/html2canvas.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
dist/html2canvas.min.js
vendored
Normal file
9
dist/html2canvas.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
19271
dist/html2canvas.svg.js
vendored
Normal file
19271
dist/html2canvas.svg.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
dist/html2canvas.svg.min.js
vendored
Normal file
12
dist/html2canvas.svg.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,11 @@
|
||||
<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="../dist/html2canvas.js"></script>
|
||||
<script type="text/javascript">
|
||||
html2canvas(document.body).then(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,11 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../dist/html2canvas.js"></script>
|
||||
<script type="text/javascript">
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
52
examples/existing_canvas.html
Normal file
52
examples/existing_canvas.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>Using an existing canvas to draw on</title>
|
||||
<style>
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
}
|
||||
button {
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
#content {
|
||||
background: rgba(100, 255, 255, 0.5);
|
||||
padding: 50px 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div><h1>HTML content to render:</h1>
|
||||
<div id="content">Render the content in this element <strong>only</strong> onto the existing canvas element</div>
|
||||
</div>
|
||||
<h1>Existing canvas:</h1>
|
||||
<canvas width="500" height="200"></canvas>
|
||||
<script type="text/javascript" src="../dist/html2canvas.js"></script>
|
||||
<button>Run html2canvas</button>
|
||||
<script type="text/javascript">
|
||||
var canvas = document.querySelector("canvas");
|
||||
var ctx = canvas.getContext("2d");
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
|
||||
ctx.moveTo(110,75);
|
||||
ctx.arc(75,75,35,0,Math.PI,false); // Mouth (clockwise)
|
||||
ctx.moveTo(65,65);
|
||||
ctx.arc(60,65,5,0,Math.PI*2,true); // Left eye
|
||||
ctx.moveTo(95,65);
|
||||
ctx.arc(90,65,5,0,Math.PI*2,true); // Right eye
|
||||
ctx.stroke();
|
||||
|
||||
document.querySelector("button").addEventListener("click", function() {
|
||||
html2canvas(document.querySelector("#content"), {canvas: canvas}).then(function(canvas) {
|
||||
console.log('Drew on the existing canvas');
|
||||
});
|
||||
}, false);
|
||||
|
||||
</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');
|
||||
|
||||
};
|
51
package.json
51
package.json
@@ -2,18 +2,53 @@
|
||||
"title": "html2canvas",
|
||||
"name": "html2canvas",
|
||||
"description": "Screenshots with JavaScript",
|
||||
"version": "0.4.0",
|
||||
"main": "src/core.js",
|
||||
"version": "0.5.0-alpha2",
|
||||
"author": {
|
||||
"name":"Niklas von Hertzen (@niklasvh)"
|
||||
"name": "Niklas von Hertzen",
|
||||
"email": "niklasvh@gmail.com",
|
||||
"url": "http://hertzen.com"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"es6-promise": "^2.0.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:niklasvh/html2canvas.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/niklasvh/html2canvas/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"base64-arraybuffer": ">= 0.1.0",
|
||||
"bluebird": "^2.7.1",
|
||||
"bower": "^1.3.12",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-browserify": "^3.3.0",
|
||||
"grunt-contrib-connect": "^0.8.0",
|
||||
"grunt-contrib-jshint": "^0.10.0",
|
||||
"grunt-contrib-uglify": "^0.6.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-execute": "^0.2.2",
|
||||
"grunt-mocha-cli": "^1.12.0",
|
||||
"grunt-mocha-phantomjs": "^0.6.0",
|
||||
"html2canvas-proxy": "0.0.5",
|
||||
"humanize-duration": "^2.0.1",
|
||||
"lodash": "^2.4.1",
|
||||
"png-js": ">= 0.1.1",
|
||||
"webdriver.js": ">= 0.1.0"
|
||||
"wd": "^0.2.21"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt travis --verbose",
|
||||
"postpublish": "bower register html2canvas git://github.com/niklasvh/html2canvas.git"
|
||||
},
|
||||
"homepage": "http://html2canvas.hertzen.com",
|
||||
"licenses": [{
|
||||
"type": "MIT"
|
||||
}]
|
||||
|
||||
}
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
93
readme.md
93
readme.md
@@ -1,16 +1,20 @@
|
||||
html2canvas
|
||||
===========
|
||||
|
||||
[Homepage](http://html2canvas.hertzen.com) | [Downloads](https://github.com/niklasvh/html2canvas/releases) | [Questions](http://stackoverflow.com/questions/tagged/html2canvas?sort=newest) | [Donate](https://www.gittip.com/niklasvh/)
|
||||
|
||||
[](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](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 +26,34 @@ 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 ###
|
||||
|
||||
**Note!** These instructions are for using the current dev version of 0.5, for the latest release version (0.4.1), checkout the [old readme](https://github.com/niklasvh/html2canvas/blob/v0.4/readme.md).
|
||||
|
||||
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:
|
||||
The function returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) containing the `<canvas>` element. Simply add a promise fullfillment handler to the promise using `then`:
|
||||
|
||||
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 );
|
||||
*/
|
||||
}
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
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](https://github.com/niklasvh/html2canvas/blob/master/dist/html2canvas.js).
|
||||
|
||||
Clone git repository with submodules:
|
||||
|
||||
$ git clone --recursive git://github.com/niklasvh/html2canvas.git
|
||||
|
||||
Install Grunt and uglifyjs:
|
||||
|
||||
$ npm install -g grunt-cli uglify-js
|
||||
|
||||
Run the full build process (including lint, qunit and webdriver tests):
|
||||
|
||||
@@ -50,14 +61,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,55 +77,10 @@ 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.
|
||||
|
||||
### Changelog ###
|
||||
|
||||
v0.40 -
|
||||
* 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
|
||||
* Support for border-radius
|
||||
* Support for multiple background images, size, and clipping
|
||||
* Support for :before and :after pseudo elements
|
||||
* Support for placeholder rendering
|
||||
* Reformatted all tests to small units to test specific features
|
||||
|
||||
v0.34 - 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>)
|
||||
* Radial gradients (<a href="https://github.com/niklasvh/html2canvas/commit/4f22c18043a73c0c3bbf3b5e4d62714c56acd3c7">SunboX</a>)
|
||||
* Split renderers to their own objects (<a href="https://github.com/niklasvh/html2canvas/commit/94f2f799a457cd29a21cc56ef8c06f1697866739">niklasvh</a>)
|
||||
* Simplified API, cleaned up code (<a href="https://github.com/niklasvh/html2canvas/commit/c7d526c9eaa6a4abf4754d205fe1dee360c7660e">niklasvh</a>)
|
||||
|
||||
v0.33 - 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>)
|
||||
* Improved minification saved ~1K! (<a href="https://github.com/cobexer/html2canvas/commit/b82be022b2b9240bd503e078ac980bde2b953e43">cobexer</a>)
|
||||
* Added integrated support for Flashcanvas (<a href="https://github.com/niklasvh/html2canvas/commit/e9257191519f67d74fd5e364d8dee3c0963ba5fc">niklasvh</a>)
|
||||
* Fixed a variety of legacy IE bugs (<a href="https://github.com/niklasvh/html2canvas/commit/b65357c55d0701017bafcd357bc654b54d458f8f">niklasvh</a>)
|
||||
|
||||
v0.32 - 20.2.2012
|
||||
|
||||
* Added changelog!
|
||||
* Added bookmarklet (<a href="https://github.com/niklasvh/html2canvas/commit/b320dd306e1a2d32a3bc5a71b6ebf6d8c060cde5">cobexer</a>)
|
||||
* Option to select single element to render (<a href="https://github.com/niklasvh/html2canvas/commit/0cb252ada91c84ef411288b317c03e97da1f12ad">niklasvh</a>)
|
||||
* Fixed closure compiler warnings (<a href="https://github.com/niklasvh/html2canvas/commit/36ff1ec7aadcbdf66851a0b77f0b9e87e4a8e4a1">cobexer</a>)
|
||||
* Enable profiling in FF (<a href="https://github.com/niklasvh/html2canvas/commit/bbd75286a8406cf9e5aea01fdb7950d547edefb9">cobexer</a>)
|
||||
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.
|
||||
|
379
src/Core.js
379
src/Core.js
@@ -1,379 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var _html2canvas = {},
|
||||
previousElement,
|
||||
computedCSS,
|
||||
html2canvas;
|
||||
|
||||
function h2clog(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 , '' ); }
|
||||
};
|
||||
})( String.prototype.trim );
|
||||
|
||||
_html2canvas.Util.parseBackgroundImage = function (value) {
|
||||
var whitespace = ' \r\n\t',
|
||||
method, definition, prefix, prefix_i, block, results = [],
|
||||
c, mode = 0, numParen = 0, quote, args;
|
||||
|
||||
var appendResult = function(){
|
||||
if(method) {
|
||||
if(definition.substr( 0, 1 ) === '"') {
|
||||
definition = definition.substr( 1, definition.length - 2 );
|
||||
}
|
||||
if(definition) {
|
||||
args.push(definition);
|
||||
}
|
||||
if(method.substr( 0, 1 ) === '-' &&
|
||||
(prefix_i = method.indexOf( '-', 1 ) + 1) > 0) {
|
||||
prefix = method.substr( 0, prefix_i);
|
||||
method = method.substr( prefix_i );
|
||||
}
|
||||
results.push({
|
||||
prefix: prefix,
|
||||
method: method.toLowerCase(),
|
||||
value: block,
|
||||
args: args
|
||||
});
|
||||
}
|
||||
args = []; //for some odd reason, setting .length = 0 didn't work in safari
|
||||
method =
|
||||
prefix =
|
||||
definition =
|
||||
block = '';
|
||||
};
|
||||
|
||||
appendResult();
|
||||
for(var i = 0, ii = value.length; i<ii; i++) {
|
||||
c = value[i];
|
||||
if(mode === 0 && whitespace.indexOf( c ) > -1){
|
||||
continue;
|
||||
}
|
||||
switch(c) {
|
||||
case '"':
|
||||
if(!quote) {
|
||||
quote = c;
|
||||
}
|
||||
else if(quote === c) {
|
||||
quote = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case '(':
|
||||
if(quote) { break; }
|
||||
else if(mode === 0) {
|
||||
mode = 1;
|
||||
block += c;
|
||||
continue;
|
||||
} else {
|
||||
numParen++;
|
||||
}
|
||||
break;
|
||||
|
||||
case ')':
|
||||
if(quote) { break; }
|
||||
else if(mode === 1) {
|
||||
if(numParen === 0) {
|
||||
mode = 0;
|
||||
block += c;
|
||||
appendResult();
|
||||
continue;
|
||||
} else {
|
||||
numParen--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ',':
|
||||
if(quote) { break; }
|
||||
else if(mode === 0) {
|
||||
appendResult();
|
||||
continue;
|
||||
}
|
||||
else if (mode === 1) {
|
||||
if(numParen === 0 && !method.match(/^url$/i)) {
|
||||
args.push(definition);
|
||||
definition = '';
|
||||
block += c;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
block += c;
|
||||
if(mode === 0) { method += c; }
|
||||
else { definition += c; }
|
||||
}
|
||||
appendResult();
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
_html2canvas.Util.Bounds = function getBounds (el) {
|
||||
var clientRect,
|
||||
bounds = {};
|
||||
|
||||
if (el.getBoundingClientRect){
|
||||
clientRect = el.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;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
_html2canvas.Util.getCSS = function (el, attribute, index) {
|
||||
// return $(el).css(attribute);
|
||||
|
||||
var val,
|
||||
isBackgroundSizePosition = attribute.match( /^background(Size|Position)$/ );
|
||||
|
||||
function toPX( attribute, val ) {
|
||||
var rsLeft = el.runtimeStyle && el.runtimeStyle[ attribute ],
|
||||
left,
|
||||
style = el.style;
|
||||
|
||||
// Check if we are not dealing with pixels, (Opera has issues with this)
|
||||
// Ported from jQuery css.js
|
||||
// From the awesome hack by Dean Edwards
|
||||
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
|
||||
|
||||
// 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 ) ) {
|
||||
|
||||
// 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 ];
|
||||
}
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
_html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){
|
||||
var target_ratio = target_width / target_height,
|
||||
current_ratio = current_width / current_height,
|
||||
output_width, output_height;
|
||||
|
||||
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 {
|
||||
output_width = target_width;
|
||||
output_height = target_width / current_ratio;
|
||||
}
|
||||
}
|
||||
|
||||
return { width: output_width, height: output_height };
|
||||
};
|
||||
|
||||
function backgroundBoundsFactory( prop, el, bounds, image, imageIndex, backgroundSize ) {
|
||||
var bgposition = _html2canvas.Util.getCSS( el, prop, imageIndex ) ,
|
||||
topPos,
|
||||
left,
|
||||
percentage,
|
||||
val;
|
||||
|
||||
if (bgposition.length === 1){
|
||||
val = bgposition[0];
|
||||
|
||||
bgposition = [];
|
||||
|
||||
bgposition[0] = val;
|
||||
bgposition[1] = val;
|
||||
}
|
||||
|
||||
if (bgposition[0].toString().indexOf("%") !== -1){
|
||||
percentage = (parseFloat(bgposition[0])/100);
|
||||
left = bounds.width * percentage;
|
||||
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] );
|
||||
left = resized.width;
|
||||
topPos = resized.height;
|
||||
} else {
|
||||
left = parseInt (bgposition[0], 10 );
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
left = parseInt( bgposition[0], 10 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(bgposition[1] === 'auto') {
|
||||
topPos = left / image.width * image.height;
|
||||
} else if (bgposition[1].toString().indexOf("%") !== -1){
|
||||
percentage = (parseFloat(bgposition[1])/100);
|
||||
topPos = bounds.height * percentage;
|
||||
if(prop !== 'backgroundSize') {
|
||||
topPos -= (backgroundSize || image).height * percentage;
|
||||
}
|
||||
|
||||
} else {
|
||||
topPos = parseInt(bgposition[1],10);
|
||||
}
|
||||
|
||||
return [left, topPos];
|
||||
}
|
||||
|
||||
_html2canvas.Util.BackgroundPosition = function( el, bounds, image, imageIndex, backgroundSize ) {
|
||||
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] };
|
||||
};
|
||||
|
||||
_html2canvas.Util.Extend = function (options, defaults) {
|
||||
for (var key in options) {
|
||||
if (options.hasOwnProperty(key)) {
|
||||
defaults[key] = options[key];
|
||||
}
|
||||
}
|
||||
return defaults;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Derived from jQuery.contents()
|
||||
* Copyright 2010, John Resig
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* 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 ){
|
||||
var ret = [];
|
||||
|
||||
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 ];
|
||||
}
|
||||
|
||||
} else {
|
||||
while ( second[j] !== undefined ) {
|
||||
first[ i++ ] = second[ j++ ];
|
||||
}
|
||||
}
|
||||
|
||||
first.length = i;
|
||||
|
||||
return first;
|
||||
})( ret, array );
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
})( elem.childNodes );
|
||||
|
||||
} catch (ex) {
|
||||
h2clog("html2canvas.Util.Children failed with exception: " + ex.message);
|
||||
children = [];
|
||||
}
|
||||
return children;
|
||||
};
|
64
src/Font.js
64
src/Font.js
@@ -1,64 +0,0 @@
|
||||
_html2canvas.Util.Font = (function () {
|
||||
|
||||
var fontData = {};
|
||||
|
||||
return function(font, fontSize, doc) {
|
||||
if (fontData[font + "-" + fontSize] !== undefined) {
|
||||
return fontData[font + "-" + fontSize];
|
||||
}
|
||||
|
||||
var container = doc.createElement('div'),
|
||||
img = doc.createElement('img'),
|
||||
span = doc.createElement('span'),
|
||||
sampleText = 'Hidden Text',
|
||||
baseline,
|
||||
middle,
|
||||
metricsObj;
|
||||
|
||||
container.style.visibility = "hidden";
|
||||
container.style.fontFamily = font;
|
||||
container.style.fontSize = fontSize;
|
||||
container.style.margin = 0;
|
||||
container.style.padding = 0;
|
||||
|
||||
doc.body.appendChild(container);
|
||||
|
||||
// http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif)
|
||||
img.src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=";
|
||||
img.width = 1;
|
||||
img.height = 1;
|
||||
|
||||
img.style.margin = 0;
|
||||
img.style.padding = 0;
|
||||
img.style.verticalAlign = "baseline";
|
||||
|
||||
span.style.fontFamily = font;
|
||||
span.style.fontSize = fontSize;
|
||||
span.style.margin = 0;
|
||||
span.style.padding = 0;
|
||||
|
||||
span.appendChild(doc.createTextNode(sampleText));
|
||||
container.appendChild(span);
|
||||
container.appendChild(img);
|
||||
baseline = (img.offsetTop - span.offsetTop) + 1;
|
||||
|
||||
container.removeChild(span);
|
||||
container.appendChild(doc.createTextNode(sampleText));
|
||||
|
||||
container.style.lineHeight = "normal";
|
||||
img.style.verticalAlign = "super";
|
||||
|
||||
middle = (img.offsetTop-container.offsetTop) + 1;
|
||||
metricsObj = {
|
||||
baseline: baseline,
|
||||
lineWidth: 1,
|
||||
middle: middle
|
||||
};
|
||||
|
||||
fontData[font + "-" + fontSize] = metricsObj;
|
||||
|
||||
doc.body.removeChild(container);
|
||||
|
||||
return metricsObj;
|
||||
};
|
||||
})();
|
439
src/Generate.js
439
src/Generate.js
@@ -1,439 +0,0 @@
|
||||
(function(){
|
||||
|
||||
_html2canvas.Generate = {};
|
||||
|
||||
var reGradients = [
|
||||
/^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
|
||||
/^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
|
||||
/^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/,
|
||||
/^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/,
|
||||
/^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/,
|
||||
/^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/,
|
||||
/^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/
|
||||
];
|
||||
|
||||
/*
|
||||
* TODO: Add IE10 vendor prefix (-ms) support
|
||||
* TODO: Add W3C gradient (linear-gradient) support
|
||||
* TODO: Add old Webkit -webkit-gradient(radial, ...) support
|
||||
* TODO: Maybe some RegExp optimizations are possible ;o)
|
||||
*/
|
||||
_html2canvas.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){
|
||||
m1 = css.match(reGradients[i]);
|
||||
if(m1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(m1) {
|
||||
switch(m1[1]) {
|
||||
case '-webkit-linear-gradient':
|
||||
case '-o-linear-gradient':
|
||||
|
||||
gradient = {
|
||||
type: 'linear',
|
||||
x0: null,
|
||||
y0: null,
|
||||
x1: null,
|
||||
y1: null,
|
||||
colorStops: []
|
||||
};
|
||||
|
||||
// get coordinates
|
||||
m2 = m1[2].match(/\w+/g);
|
||||
if(m2){
|
||||
m2Len = m2.length;
|
||||
for(i = 0; i < m2Len; i+=1){
|
||||
switch(m2[i]) {
|
||||
case 'top':
|
||||
gradient.y0 = 0;
|
||||
gradient.y1 = bounds.height;
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
gradient.x0 = bounds.width;
|
||||
gradient.x1 = 0;
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
gradient.y0 = bounds.height;
|
||||
gradient.y1 = 0;
|
||||
break;
|
||||
|
||||
case 'left':
|
||||
gradient.x0 = 0;
|
||||
gradient.x1 = bounds.width;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(gradient.x0 === null && gradient.x1 === null){ // center
|
||||
gradient.x0 = gradient.x1 = bounds.width / 2;
|
||||
}
|
||||
if(gradient.y0 === null && gradient.y1 === null){ // center
|
||||
gradient.y0 = gradient.y1 = bounds.height / 2;
|
||||
}
|
||||
|
||||
// get colors and stops
|
||||
m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
|
||||
if(m2){
|
||||
m2Len = m2.length;
|
||||
step = 1 / Math.max(m2Len - 1, 1);
|
||||
for(i = 0; i < m2Len; i+=1){
|
||||
m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
|
||||
if(m3[2]){
|
||||
stop = parseFloat(m3[2]);
|
||||
if(m3[3] === '%'){
|
||||
stop /= 100;
|
||||
} else { // px - stupid opera
|
||||
stop /= bounds.width;
|
||||
}
|
||||
} else {
|
||||
stop = i * step;
|
||||
}
|
||||
gradient.colorStops.push({
|
||||
color: m3[1],
|
||||
stop: stop
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '-webkit-gradient':
|
||||
|
||||
gradient = {
|
||||
type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
x1: 0,
|
||||
y1: 0,
|
||||
colorStops: []
|
||||
};
|
||||
|
||||
// get coordinates
|
||||
m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/);
|
||||
if(m2){
|
||||
gradient.x0 = (m2[1] * bounds.width) / 100;
|
||||
gradient.y0 = (m2[2] * bounds.height) / 100;
|
||||
gradient.x1 = (m2[3] * bounds.width) / 100;
|
||||
gradient.y1 = (m2[4] * bounds.height) / 100;
|
||||
}
|
||||
|
||||
// get colors and stops
|
||||
m2 = m1[4].match(/((?:from|to|color-stop)\((?:[0-9\.]+,\s)?(?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)\))+/g);
|
||||
if(m2){
|
||||
m2Len = m2.length;
|
||||
for(i = 0; i < m2Len; i+=1){
|
||||
m3 = m2[i].match(/(from|to|color-stop)\(([0-9\.]+)?(?:,\s)?((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\)/);
|
||||
stop = parseFloat(m3[2]);
|
||||
if(m3[1] === 'from') {
|
||||
stop = 0.0;
|
||||
}
|
||||
if(m3[1] === 'to') {
|
||||
stop = 1.0;
|
||||
}
|
||||
gradient.colorStops.push({
|
||||
color: m3[3],
|
||||
stop: stop
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '-moz-linear-gradient':
|
||||
|
||||
gradient = {
|
||||
type: 'linear',
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
x1: 0,
|
||||
y1: 0,
|
||||
colorStops: []
|
||||
};
|
||||
|
||||
// get coordinates
|
||||
m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
|
||||
|
||||
// m2[1] == 0% -> left
|
||||
// m2[1] == 50% -> center
|
||||
// m2[1] == 100% -> right
|
||||
|
||||
// m2[2] == 0% -> top
|
||||
// m2[2] == 50% -> center
|
||||
// m2[2] == 100% -> bottom
|
||||
|
||||
if(m2){
|
||||
gradient.x0 = (m2[1] * bounds.width) / 100;
|
||||
gradient.y0 = (m2[2] * bounds.height) / 100;
|
||||
gradient.x1 = bounds.width - gradient.x0;
|
||||
gradient.y1 = bounds.height - gradient.y0;
|
||||
}
|
||||
|
||||
// get colors and stops
|
||||
m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}%)?)+/g);
|
||||
if(m2){
|
||||
m2Len = m2.length;
|
||||
step = 1 / Math.max(m2Len - 1, 1);
|
||||
for(i = 0; i < m2Len; i+=1){
|
||||
m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/);
|
||||
if(m3[2]){
|
||||
stop = parseFloat(m3[2]);
|
||||
if(m3[3]){ // percentage
|
||||
stop /= 100;
|
||||
}
|
||||
} else {
|
||||
stop = i * step;
|
||||
}
|
||||
gradient.colorStops.push({
|
||||
color: m3[1],
|
||||
stop: stop
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '-webkit-radial-gradient':
|
||||
case '-moz-radial-gradient':
|
||||
case '-o-radial-gradient':
|
||||
|
||||
gradient = {
|
||||
type: 'circle',
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
x1: bounds.width,
|
||||
y1: bounds.height,
|
||||
cx: 0,
|
||||
cy: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
colorStops: []
|
||||
};
|
||||
|
||||
// center
|
||||
m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
|
||||
if(m2){
|
||||
gradient.cx = (m2[1] * bounds.width) / 100;
|
||||
gradient.cy = (m2[2] * bounds.height) / 100;
|
||||
}
|
||||
|
||||
// size
|
||||
m2 = m1[3].match(/\w+/);
|
||||
m3 = m1[4].match(/[a-z\-]*/);
|
||||
if(m2 && m3){
|
||||
switch(m3[0]){
|
||||
case 'farthest-corner':
|
||||
case 'cover': // is equivalent to farthest-corner
|
||||
case '': // mozilla removes "cover" from definition :(
|
||||
tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
|
||||
tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
|
||||
br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
|
||||
bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
|
||||
gradient.rx = gradient.ry = Math.max(tl, tr, br, bl);
|
||||
break;
|
||||
case 'closest-corner':
|
||||
tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
|
||||
tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
|
||||
br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
|
||||
bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
|
||||
gradient.rx = gradient.ry = Math.min(tl, tr, br, bl);
|
||||
break;
|
||||
case 'farthest-side':
|
||||
if(m2[0] === 'circle'){
|
||||
gradient.rx = gradient.ry = Math.max(
|
||||
gradient.cx,
|
||||
gradient.cy,
|
||||
gradient.x1 - gradient.cx,
|
||||
gradient.y1 - gradient.cy
|
||||
);
|
||||
} else { // ellipse
|
||||
|
||||
gradient.type = m2[0];
|
||||
|
||||
gradient.rx = Math.max(
|
||||
gradient.cx,
|
||||
gradient.x1 - gradient.cx
|
||||
);
|
||||
gradient.ry = Math.max(
|
||||
gradient.cy,
|
||||
gradient.y1 - gradient.cy
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'closest-side':
|
||||
case 'contain': // is equivalent to closest-side
|
||||
if(m2[0] === 'circle'){
|
||||
gradient.rx = gradient.ry = Math.min(
|
||||
gradient.cx,
|
||||
gradient.cy,
|
||||
gradient.x1 - gradient.cx,
|
||||
gradient.y1 - gradient.cy
|
||||
);
|
||||
} else { // ellipse
|
||||
|
||||
gradient.type = m2[0];
|
||||
|
||||
gradient.rx = Math.min(
|
||||
gradient.cx,
|
||||
gradient.x1 - gradient.cx
|
||||
);
|
||||
gradient.ry = Math.min(
|
||||
gradient.cy,
|
||||
gradient.y1 - gradient.cy
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
// TODO: add support for "30px 40px" sizes (webkit only)
|
||||
}
|
||||
}
|
||||
|
||||
// color stops
|
||||
m2 = m1[5].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
|
||||
if(m2){
|
||||
m2Len = m2.length;
|
||||
step = 1 / Math.max(m2Len - 1, 1);
|
||||
for(i = 0; i < m2Len; i+=1){
|
||||
m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
|
||||
if(m3[2]){
|
||||
stop = parseFloat(m3[2]);
|
||||
if(m3[3] === '%'){
|
||||
stop /= 100;
|
||||
} else { // px - stupid opera
|
||||
stop /= bounds.width;
|
||||
}
|
||||
} else {
|
||||
stop = i * step;
|
||||
}
|
||||
gradient.colorStops.push({
|
||||
color: m3[1],
|
||||
stop: stop
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return gradient;
|
||||
};
|
||||
|
||||
_html2canvas.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;
|
||||
|
||||
canvas.width = bounds.width;
|
||||
canvas.height = bounds.height;
|
||||
|
||||
// TODO: add support for multi defined background gradients
|
||||
gradient = _html2canvas.Generate.parseGradient(src, bounds);
|
||||
|
||||
if(gradient) {
|
||||
if(gradient.type === 'linear') {
|
||||
grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
|
||||
|
||||
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 === 'circle') {
|
||||
|
||||
grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
};
|
||||
|
||||
_html2canvas.Generate.ListAlpha = function(number) {
|
||||
var tmp = "",
|
||||
modulus;
|
||||
|
||||
do {
|
||||
modulus = number % 26;
|
||||
tmp = String.fromCharCode((modulus) + 64) + tmp;
|
||||
number = number / 26;
|
||||
}while((number*26) > 26);
|
||||
|
||||
return tmp;
|
||||
};
|
||||
|
||||
_html2canvas.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 = "",
|
||||
v,
|
||||
len = romanArray.length;
|
||||
|
||||
if (number <= 0 || number >= 4000) {
|
||||
return number;
|
||||
}
|
||||
|
||||
for (v=0; v < len; v+=1) {
|
||||
while (number >= decimal[v]) {
|
||||
number -= decimal[v];
|
||||
roman += romanArray[v];
|
||||
}
|
||||
}
|
||||
|
||||
return roman;
|
||||
|
||||
};
|
||||
|
||||
})();
|
1150
src/Parse.js
1150
src/Parse.js
File diff suppressed because it is too large
Load Diff
330
src/Preload.js
330
src/Preload.js
@@ -1,330 +0,0 @@
|
||||
_html2canvas.Preload = function( options ) {
|
||||
|
||||
var images = {
|
||||
numLoaded: 0, // also failed are counted here
|
||||
numFailed: 0,
|
||||
numTotal: 0,
|
||||
cleanupDone: false
|
||||
},
|
||||
pageOrigin,
|
||||
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
|
||||
imgLen = domImages.length,
|
||||
link = doc.createElement("a"),
|
||||
supportCORS = (function( img ){
|
||||
return (img.crossOrigin !== undefined);
|
||||
})(new Image()),
|
||||
timeoutTimer;
|
||||
|
||||
link.href = window.location.href;
|
||||
pageOrigin = link.protocol + link.host;
|
||||
|
||||
function isSameOrigin(url){
|
||||
link.href = url;
|
||||
link.href = link.href; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/
|
||||
var origin = link.protocol + link.host;
|
||||
return (origin === pageOrigin);
|
||||
}
|
||||
|
||||
function start(){
|
||||
h2clog("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 + ")");
|
||||
|
||||
if (typeof options.complete === "function"){
|
||||
options.complete(images);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TODO modify proxy to serve images with CORS enabled, where available
|
||||
function proxyGetImage(url, img, imageObj){
|
||||
var callback_name,
|
||||
scriptUrl = options.proxy,
|
||||
script;
|
||||
|
||||
link.href = url;
|
||||
url = link.href; // work around for pages with base href="" set - WARNING: this may change the url
|
||||
|
||||
callback_name = 'html2canvas_' + (count++);
|
||||
imageObj.callbackname = callback_name;
|
||||
|
||||
if (scriptUrl.indexOf("?") > -1) {
|
||||
scriptUrl += "&";
|
||||
} else {
|
||||
scriptUrl += "?";
|
||||
}
|
||||
scriptUrl += 'url=' + encodeURIComponent(url) + '&callback=' + callback_name;
|
||||
script = doc.createElement("script");
|
||||
|
||||
window[callback_name] = function(a){
|
||||
if (a.substring(0,6) === "error:"){
|
||||
imageObj.succeeded = false;
|
||||
images.numLoaded++;
|
||||
images.numFailed++;
|
||||
start();
|
||||
} else {
|
||||
setImageLoadHandlers(img, imageObj);
|
||||
img.src = a;
|
||||
}
|
||||
window[callback_name] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
|
||||
try {
|
||||
delete window[callback_name]; // for all browser that support this
|
||||
} catch(ex) {}
|
||||
script.parentNode.removeChild(script);
|
||||
script = null;
|
||||
delete imageObj.script;
|
||||
delete imageObj.callbackname;
|
||||
};
|
||||
|
||||
script.setAttribute("type", "text/javascript");
|
||||
script.setAttribute("src", scriptUrl);
|
||||
imageObj.script = script;
|
||||
window.document.body.appendChild(script);
|
||||
|
||||
}
|
||||
|
||||
function loadPseudoElement(element, type) {
|
||||
var style = window.getComputedStyle(element, type),
|
||||
content = style.content;
|
||||
if (content.substr(0, 3) === 'url') {
|
||||
methods.loadImage(_html2canvas.Util.parseBackgroundImage(content)[0].args[0]);
|
||||
}
|
||||
loadBackgroundImages(style.backgroundImage, element);
|
||||
}
|
||||
|
||||
function loadPseudoElementImages(element) {
|
||||
loadPseudoElement(element, ":before");
|
||||
loadPseudoElement(element, ":after");
|
||||
}
|
||||
|
||||
function loadGradientImage(backgroundImage, bounds) {
|
||||
var img = _html2canvas.Generate.Gradient(backgroundImage, bounds);
|
||||
|
||||
if (img !== undefined){
|
||||
images[backgroundImage] = {
|
||||
img: img,
|
||||
succeeded: true
|
||||
};
|
||||
images.numTotal++;
|
||||
images.numLoaded++;
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
function invalidBackgrounds(background_image) {
|
||||
return (background_image && background_image.method && background_image.args && background_image.args.length > 0 );
|
||||
}
|
||||
|
||||
function loadBackgroundImages(background_image, el) {
|
||||
var bounds;
|
||||
|
||||
_html2canvas.Util.parseBackgroundImage(background_image).filter(invalidBackgrounds).forEach(function(background_image) {
|
||||
if (background_image.method === 'url') {
|
||||
methods.loadImage(background_image.args[0]);
|
||||
} else if(background_image.method.match(/\-?gradient$/)) {
|
||||
if(bounds === undefined) {
|
||||
bounds = _html2canvas.Util.Bounds(el);
|
||||
}
|
||||
loadGradientImage(background_image.value, bounds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getImages (el) {
|
||||
var elNodeType = false;
|
||||
|
||||
// Firefox fails with permission denied on pages with iframes
|
||||
try {
|
||||
_html2canvas.Util.Children(el).forEach(function(img) {
|
||||
getImages(img);
|
||||
});
|
||||
}
|
||||
catch( e ) {}
|
||||
|
||||
try {
|
||||
elNodeType = el.nodeType;
|
||||
} catch (ex) {
|
||||
elNodeType = false;
|
||||
h2clog("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);
|
||||
} catch(e) {
|
||||
h2clog("html2canvas: failed to get background-image - Exception: " + e.message);
|
||||
}
|
||||
loadBackgroundImages(el);
|
||||
}
|
||||
}
|
||||
|
||||
function setImageLoadHandlers(img, imageObj) {
|
||||
img.onload = function() {
|
||||
if ( imageObj.timer !== undefined ) {
|
||||
// CORS succeeded
|
||||
window.clearTimeout( imageObj.timer );
|
||||
}
|
||||
|
||||
images.numLoaded++;
|
||||
imageObj.succeeded = true;
|
||||
img.onerror = img.onload = null;
|
||||
start();
|
||||
};
|
||||
img.onerror = function() {
|
||||
if (img.crossOrigin === "anonymous") {
|
||||
// CORS failed
|
||||
window.clearTimeout( imageObj.timer );
|
||||
|
||||
// let's try with proxy instead
|
||||
if ( options.proxy ) {
|
||||
var src = img.src;
|
||||
img = new Image();
|
||||
imageObj.img = img;
|
||||
img.src = src;
|
||||
|
||||
proxyGetImage( img.src, img, imageObj );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
images.numLoaded++;
|
||||
images.numFailed++;
|
||||
imageObj.succeeded = false;
|
||||
img.onerror = img.onload = null;
|
||||
start();
|
||||
};
|
||||
}
|
||||
|
||||
methods = {
|
||||
loadImage: function( src ) {
|
||||
var img, imageObj;
|
||||
if ( src && images[src] === undefined ) {
|
||||
img = new Image();
|
||||
if ( src.match(/data:image\/.*;base64,/i) ) {
|
||||
img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, '');
|
||||
imageObj = images[src] = {
|
||||
img: img
|
||||
};
|
||||
images.numTotal++;
|
||||
setImageLoadHandlers(img, imageObj);
|
||||
} else if ( isSameOrigin( src ) || options.allowTaint === true ) {
|
||||
imageObj = images[src] = {
|
||||
img: img
|
||||
};
|
||||
images.numTotal++;
|
||||
setImageLoadHandlers(img, imageObj);
|
||||
img.src = src;
|
||||
} else if ( supportCORS && !options.allowTaint && options.useCORS ) {
|
||||
// attempt to load with CORS
|
||||
|
||||
img.crossOrigin = "anonymous";
|
||||
imageObj = images[src] = {
|
||||
img: img
|
||||
};
|
||||
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
|
||||
};
|
||||
images.numTotal++;
|
||||
proxyGetImage( src, img, imageObj );
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
cleanupDOM: function(cause) {
|
||||
var img, src;
|
||||
if (!images.cleanupDone) {
|
||||
if (cause && typeof cause === "string") {
|
||||
h2clog("html2canvas: Cleanup because: " + cause);
|
||||
} else {
|
||||
h2clog("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
|
||||
}
|
||||
|
||||
for (src in images) {
|
||||
if (images.hasOwnProperty(src)) {
|
||||
img = images[src];
|
||||
if (typeof img === "object" && img.callbackname && img.succeeded === undefined) {
|
||||
// cancel proxy image request
|
||||
window[img.callbackname] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
|
||||
try {
|
||||
delete window[img.callbackname]; // for all browser that support this
|
||||
} catch(ex) {}
|
||||
if (img.script && img.script.parentNode) {
|
||||
img.script.setAttribute("src", "about:blank"); // try to cancel running request
|
||||
img.script.parentNode.removeChild(img.script);
|
||||
}
|
||||
images.numLoaded++;
|
||||
images.numFailed++;
|
||||
h2clog("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cancel any pending requests
|
||||
if(window.stop !== undefined) {
|
||||
window.stop();
|
||||
} else if(document.execCommand !== undefined) {
|
||||
document.execCommand("Stop", false);
|
||||
}
|
||||
if (document.close !== undefined) {
|
||||
document.close();
|
||||
}
|
||||
images.cleanupDone = true;
|
||||
if (!(cause && typeof cause === "string")) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
renderingDone: function() {
|
||||
if (timeoutTimer) {
|
||||
window.clearTimeout(timeoutTimer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (options.timeout > 0) {
|
||||
timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout);
|
||||
}
|
||||
|
||||
h2clog('html2canvas: Preload starts: finding background-images');
|
||||
images.firstRun = true;
|
||||
|
||||
getImages(element);
|
||||
|
||||
h2clog('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 ) {
|
||||
start();
|
||||
}
|
||||
|
||||
return methods;
|
||||
|
||||
};
|
122
src/Queue.js
122
src/Queue.js
@@ -1,122 +0,0 @@
|
||||
function h2cRenderContext(width, height) {
|
||||
var storage = [];
|
||||
return {
|
||||
storage: storage,
|
||||
width: width,
|
||||
height: height,
|
||||
clip: function() {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "clip",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
translate: function() {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "translate",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
fill: function() {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "fill",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
save: function() {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "save",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
restore: function() {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "restore",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
fillRect: function () {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "fillRect",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
createPattern: function() {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "createPattern",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
drawShape: function() {
|
||||
|
||||
var shape = [];
|
||||
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "drawShape",
|
||||
'arguments': shape
|
||||
});
|
||||
|
||||
return {
|
||||
moveTo: function() {
|
||||
shape.push({
|
||||
name: "moveTo",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
lineTo: function() {
|
||||
shape.push({
|
||||
name: "lineTo",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
arcTo: function() {
|
||||
shape.push({
|
||||
name: "arcTo",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
bezierCurveTo: function() {
|
||||
shape.push({
|
||||
name: "bezierCurveTo",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
quadraticCurveTo: function() {
|
||||
shape.push({
|
||||
name: "quadraticCurveTo",
|
||||
'arguments': arguments
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
},
|
||||
drawImage: function () {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "drawImage",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
fillText: function () {
|
||||
storage.push({
|
||||
type: "function",
|
||||
name: "fillText",
|
||||
'arguments': arguments
|
||||
});
|
||||
},
|
||||
setVariable: function (variable, value) {
|
||||
storage.push({
|
||||
type: "variable",
|
||||
name: variable,
|
||||
'arguments': value
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
_html2canvas.Renderer = function(parseQueue, options){
|
||||
|
||||
function createRenderQueue(parseQueue) {
|
||||
var queue = [];
|
||||
|
||||
var sortZ = function(zStack){
|
||||
var subStacks = [],
|
||||
stackValues = [];
|
||||
|
||||
zStack.children.forEach(function(stackChild) {
|
||||
if (stackChild.children && stackChild.children.length > 0){
|
||||
subStacks.push(stackChild);
|
||||
stackValues.push(stackChild.zindex);
|
||||
} else {
|
||||
queue.push(stackChild);
|
||||
}
|
||||
});
|
||||
|
||||
stackValues.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
stackValues.forEach(function(zValue) {
|
||||
var index;
|
||||
|
||||
subStacks.some(function(stack, i){
|
||||
index = i;
|
||||
return (stack.zindex === zValue);
|
||||
});
|
||||
sortZ(subStacks.splice(index, 1)[0]);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
sortZ(parseQueue.zIndex);
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
function getRenderer(rendererName) {
|
||||
var renderer;
|
||||
|
||||
if (typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) {
|
||||
renderer = _html2canvas.Renderer[rendererName](options);
|
||||
} else if (typeof rendererName === "function") {
|
||||
renderer = rendererName(options);
|
||||
} else {
|
||||
throw new Error("Unknown renderer");
|
||||
}
|
||||
|
||||
if ( typeof renderer !== "function" ) {
|
||||
throw new Error("Invalid renderer defined");
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
|
||||
return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue), _html2canvas);
|
||||
};
|
@@ -1,63 +0,0 @@
|
||||
_html2canvas.Util.Support = function (options, doc) {
|
||||
|
||||
function supportSVGRendering() {
|
||||
var img = new Image(),
|
||||
canvas = doc.createElement("canvas"),
|
||||
ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d");
|
||||
if (ctx === false) {
|
||||
return false;
|
||||
}
|
||||
canvas.width = canvas.height = 10;
|
||||
img.src = [
|
||||
"data:image/svg+xml,",
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'>",
|
||||
"<foreignObject width='10' height='10'>",
|
||||
"<div xmlns='http://www.w3.org/1999/xhtml' style='width:10;height:10;'>",
|
||||
"sup",
|
||||
"</div>",
|
||||
"</foreignObject>",
|
||||
"</svg>"
|
||||
].join("");
|
||||
try {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
canvas.toDataURL();
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
h2clog('html2canvas: Parse: SVG powered rendering available');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Test whether we can use ranges to measure bounding boxes
|
||||
// Opera doesn't provide valid bounds.height/bottom even though it supports the method.
|
||||
|
||||
function supportRangeBounds() {
|
||||
var r, testElement, rangeBounds, rangeHeight, support = false;
|
||||
|
||||
if (doc.createRange) {
|
||||
r = doc.createRange();
|
||||
if (r.getBoundingClientRect) {
|
||||
testElement = doc.createElement('boundtest');
|
||||
testElement.style.height = "123px";
|
||||
testElement.style.display = "block";
|
||||
doc.body.appendChild(testElement);
|
||||
|
||||
r.selectNode(testElement);
|
||||
rangeBounds = r.getBoundingClientRect();
|
||||
rangeHeight = rangeBounds.height;
|
||||
|
||||
if (rangeHeight === 123) {
|
||||
support = true;
|
||||
}
|
||||
doc.body.removeChild(testElement);
|
||||
}
|
||||
}
|
||||
|
||||
return support;
|
||||
}
|
||||
|
||||
return {
|
||||
rangeBounds: supportRangeBounds(),
|
||||
svgRendering: options.svgRendering && supportSVGRendering()
|
||||
};
|
||||
};
|
81
src/Util.js
81
src/Util.js
@@ -1,81 +0,0 @@
|
||||
window.html2canvas = function(elements, opts) {
|
||||
elements = (elements.length) ? elements : [elements];
|
||||
var queue,
|
||||
canvas,
|
||||
options = {
|
||||
// general
|
||||
logging: false,
|
||||
elements: elements,
|
||||
background: "#fff",
|
||||
|
||||
// preload options
|
||||
proxy: null,
|
||||
timeout: 0, // no timeout
|
||||
useCORS: false, // try to load images as CORS (where available), before falling back to proxy
|
||||
allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true
|
||||
|
||||
// parse options
|
||||
svgRendering: false, // use svg powered rendering where available (FF11+)
|
||||
ignoreElements: "IFRAME|OBJECT|PARAM",
|
||||
useOverflow: true,
|
||||
letterRendering: false,
|
||||
chinese: false,
|
||||
|
||||
// render options
|
||||
|
||||
width: null,
|
||||
height: null,
|
||||
taintTest: true, // do a taint test with all images before applying to canvas
|
||||
renderer: "Canvas"
|
||||
};
|
||||
|
||||
options = _html2canvas.Util.Extend(opts, options);
|
||||
|
||||
_html2canvas.logging = options.logging;
|
||||
options.complete = function( images ) {
|
||||
|
||||
if (typeof options.onpreloaded === "function") {
|
||||
if ( options.onpreloaded( images ) === false ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
queue = _html2canvas.Parse( images, options );
|
||||
|
||||
if (typeof options.onparsed === "function") {
|
||||
if ( options.onparsed( queue ) === false ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
canvas = _html2canvas.Renderer( queue, options );
|
||||
|
||||
if (typeof options.onrendered === "function") {
|
||||
options.onrendered( canvas );
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
// for pages without images, we still want this to be async, i.e. return methods before executing
|
||||
window.setTimeout( function(){
|
||||
_html2canvas.Preload( options );
|
||||
}, 0 );
|
||||
|
||||
return {
|
||||
render: function( queue, opts ) {
|
||||
return _html2canvas.Renderer( queue, _html2canvas.Util.Extend(opts, options) );
|
||||
},
|
||||
parse: function( images, opts ) {
|
||||
return _html2canvas.Parse( images, _html2canvas.Util.Extend(opts, options) );
|
||||
},
|
||||
preload: function( opts ) {
|
||||
return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) );
|
||||
},
|
||||
log: h2clog
|
||||
};
|
||||
};
|
||||
|
||||
window.html2canvas.log = h2clog; // for renderers
|
||||
window.html2canvas.Renderer = {
|
||||
Canvas: undefined // We are assuming this will be used
|
||||
};
|
125
src/clone.js
Normal file
125
src/clone.js
Normal file
@@ -0,0 +1,125 @@
|
||||
var log = require('./log');
|
||||
var Promise = require('./promise');
|
||||
|
||||
var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone";
|
||||
var html2canvasCanvasCloneIndex = 0;
|
||||
|
||||
function cloneNodeValues(document, clone, nodeName) {
|
||||
var originalNodes = document.getElementsByTagName(nodeName);
|
||||
var clonedNodes = clone.getElementsByTagName(nodeName);
|
||||
var count = originalNodes.length;
|
||||
for (var i = 0; i < count; i++) {
|
||||
clonedNodes[i].value = originalNodes[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
function restoreOwnerScroll(ownerDocument, x, y) {
|
||||
if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) {
|
||||
ownerDocument.defaultView.scrollTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
function labelCanvasElements(ownerDocument) {
|
||||
[].slice.call(ownerDocument.querySelectorAll("canvas"), 0).forEach(function(canvas) {
|
||||
canvas.setAttribute(html2canvasCanvasCloneAttribute, "canvas-" + html2canvasCanvasCloneIndex++);
|
||||
});
|
||||
}
|
||||
|
||||
function cloneCanvasContents(ownerDocument, documentClone) {
|
||||
[].slice.call(ownerDocument.querySelectorAll("[" + html2canvasCanvasCloneAttribute + "]"), 0).forEach(function(canvas) {
|
||||
try {
|
||||
var clonedCanvas = documentClone.querySelector('[' + html2canvasCanvasCloneAttribute + '="' + canvas.getAttribute(html2canvasCanvasCloneAttribute) + '"]');
|
||||
if (clonedCanvas) {
|
||||
clonedCanvas.width = canvas.width;
|
||||
clonedCanvas.height = canvas.height;
|
||||
clonedCanvas.getContext("2d").putImageData(canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height), 0, 0);
|
||||
}
|
||||
} catch(e) {
|
||||
log("Unable to copy canvas content from", canvas, e);
|
||||
}
|
||||
canvas.removeAttribute(html2canvasCanvasCloneAttribute);
|
||||
});
|
||||
}
|
||||
|
||||
function removeScriptNodes(parent) {
|
||||
[].slice.call(parent.childNodes, 0).filter(isElementNode).forEach(function(node) {
|
||||
if (node.tagName === "SCRIPT") {
|
||||
parent.removeChild(node);
|
||||
} else {
|
||||
removeScriptNodes(node);
|
||||
}
|
||||
});
|
||||
return parent;
|
||||
}
|
||||
|
||||
function isIE9() {
|
||||
return document.documentMode && document.documentMode <= 9;
|
||||
}
|
||||
|
||||
// https://github.com/niklasvh/html2canvas/issues/503
|
||||
function cloneNodeIE9(node, javascriptEnabled) {
|
||||
var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
|
||||
|
||||
var child = node.firstChild;
|
||||
while(child) {
|
||||
if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
|
||||
clone.appendChild(cloneNodeIE9(child, javascriptEnabled));
|
||||
}
|
||||
child = child.nextSibling;
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function isElementNode(node) {
|
||||
return node.nodeType === Node.ELEMENT_NODE;
|
||||
}
|
||||
|
||||
module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) {
|
||||
labelCanvasElements(ownerDocument);
|
||||
var documentElement = isIE9() ? cloneNodeIE9(ownerDocument.documentElement, options.javascriptEnabled) : ownerDocument.documentElement.cloneNode(true);
|
||||
var container = containerDocument.createElement("iframe");
|
||||
|
||||
container.className = "html2canvas-container";
|
||||
container.style.visibility = "hidden";
|
||||
container.style.position = "fixed";
|
||||
container.style.left = "-10000px";
|
||||
container.style.top = "0px";
|
||||
container.style.border = "0";
|
||||
container.width = width;
|
||||
container.height = height;
|
||||
container.scrolling = "no"; // ios won't scroll without it
|
||||
containerDocument.body.appendChild(container);
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
var documentClone = container.contentWindow.document;
|
||||
|
||||
cloneNodeValues(ownerDocument.documentElement, documentElement, "textarea");
|
||||
cloneNodeValues(ownerDocument.documentElement, documentElement, "select");
|
||||
|
||||
/* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
|
||||
if window url is about:blank, we can assign the url to current by writing onto the document
|
||||
*/
|
||||
container.contentWindow.onload = container.onload = function() {
|
||||
var interval = setInterval(function() {
|
||||
if (documentClone.body.childNodes.length > 0) {
|
||||
cloneCanvasContents(ownerDocument, documentClone);
|
||||
clearInterval(interval);
|
||||
if (options.type === "view") {
|
||||
container.contentWindow.scrollTo(x, y);
|
||||
}
|
||||
resolve(container);
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
|
||||
documentClone.open();
|
||||
documentClone.write("<!DOCTYPE html><html></html>");
|
||||
// Chrome scrolls the parent document for some reason after the write to the cloned window???
|
||||
restoreOwnerScroll(ownerDocument, x, y);
|
||||
documentClone.replaceChild(options.javascriptEnabled === true ? documentClone.adoptNode(documentElement) : removeScriptNodes(documentClone.adoptNode(documentElement)), documentClone.documentElement);
|
||||
documentClone.close();
|
||||
});
|
||||
};
|
271
src/color.js
Normal file
271
src/color.js
Normal file
@@ -0,0 +1,271 @@
|
||||
// http://dev.w3.org/csswg/css-color/
|
||||
|
||||
function Color(value) {
|
||||
this.r = 0;
|
||||
this.g = 0;
|
||||
this.b = 0;
|
||||
this.a = null;
|
||||
var result = this.fromArray(value) ||
|
||||
this.namedColor(value) ||
|
||||
this.rgb(value) ||
|
||||
this.rgba(value) ||
|
||||
this.hex6(value) ||
|
||||
this.hex3(value);
|
||||
}
|
||||
|
||||
Color.prototype.darken = function(amount) {
|
||||
var a = 1 - amount;
|
||||
return new Color([
|
||||
Math.round(this.r * a),
|
||||
Math.round(this.g * a),
|
||||
Math.round(this.b * a),
|
||||
this.a
|
||||
]);
|
||||
};
|
||||
|
||||
Color.prototype.isTransparent = function() {
|
||||
return this.a === 0;
|
||||
};
|
||||
|
||||
Color.prototype.isBlack = function() {
|
||||
return this.r === 0 && this.g === 0 && this.b === 0;
|
||||
};
|
||||
|
||||
Color.prototype.fromArray = function(array) {
|
||||
if (Array.isArray(array)) {
|
||||
this.r = Math.min(array[0], 255);
|
||||
this.g = Math.min(array[1], 255);
|
||||
this.b = Math.min(array[2], 255);
|
||||
if (array.length > 3) {
|
||||
this.a = array[3];
|
||||
}
|
||||
}
|
||||
|
||||
return (Array.isArray(array));
|
||||
};
|
||||
|
||||
var _hex3 = /^#([a-f0-9]{3})$/i;
|
||||
|
||||
Color.prototype.hex3 = function(value) {
|
||||
var match = null;
|
||||
if ((match = value.match(_hex3)) !== null) {
|
||||
this.r = parseInt(match[1][0] + match[1][0], 16);
|
||||
this.g = parseInt(match[1][1] + match[1][1], 16);
|
||||
this.b = parseInt(match[1][2] + match[1][2], 16);
|
||||
}
|
||||
return match !== null;
|
||||
};
|
||||
|
||||
var _hex6 = /^#([a-f0-9]{6})$/i;
|
||||
|
||||
Color.prototype.hex6 = function(value) {
|
||||
var match = null;
|
||||
if ((match = value.match(_hex6)) !== null) {
|
||||
this.r = parseInt(match[1].substring(0, 2), 16);
|
||||
this.g = parseInt(match[1].substring(2, 4), 16);
|
||||
this.b = parseInt(match[1].substring(4, 6), 16);
|
||||
}
|
||||
return match !== null;
|
||||
};
|
||||
|
||||
|
||||
var _rgb = /^rgb\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3})\)$/;
|
||||
|
||||
Color.prototype.rgb = function(value) {
|
||||
var match = null;
|
||||
if ((match = value.match(_rgb)) !== null) {
|
||||
this.r = Number(match[1]);
|
||||
this.g = Number(match[2]);
|
||||
this.b = Number(match[3]);
|
||||
}
|
||||
return match !== null;
|
||||
};
|
||||
|
||||
var _rgba = /^rgba\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *, *(\d+\.?\d*)\)$/;
|
||||
|
||||
Color.prototype.rgba = function(value) {
|
||||
var match = null;
|
||||
if ((match = value.match(_rgba)) !== null) {
|
||||
this.r = Number(match[1]);
|
||||
this.g = Number(match[2]);
|
||||
this.b = Number(match[3]);
|
||||
this.a = Number(match[4]);
|
||||
}
|
||||
return match !== null;
|
||||
};
|
||||
|
||||
Color.prototype.toString = function() {
|
||||
return this.a !== null && this.a !== 1 ?
|
||||
"rgba(" + [this.r, this.g, this.b, this.a].join(",") + ")" :
|
||||
"rgb(" + [this.r, this.g, this.b].join(",") + ")";
|
||||
};
|
||||
|
||||
Color.prototype.namedColor = function(value) {
|
||||
var color = colors[value.toLowerCase()];
|
||||
if (color) {
|
||||
this.r = color[0];
|
||||
this.g = color[1];
|
||||
this.b = color[2];
|
||||
} else if (value.toLowerCase() === "transparent") {
|
||||
this.r = this.g = this.b = this.a = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!color;
|
||||
};
|
||||
|
||||
Color.prototype.isColor = true;
|
||||
|
||||
// JSON.stringify([].slice.call($$('.named-color-table tr'), 1).map(function(row) { return [row.childNodes[3].textContent, row.childNodes[5].textContent.trim().split(",").map(Number)] }).reduce(function(data, row) {data[row[0]] = row[1]; return data}, {}))
|
||||
var colors = {
|
||||
"aliceblue": [240, 248, 255],
|
||||
"antiquewhite": [250, 235, 215],
|
||||
"aqua": [0, 255, 255],
|
||||
"aquamarine": [127, 255, 212],
|
||||
"azure": [240, 255, 255],
|
||||
"beige": [245, 245, 220],
|
||||
"bisque": [255, 228, 196],
|
||||
"black": [0, 0, 0],
|
||||
"blanchedalmond": [255, 235, 205],
|
||||
"blue": [0, 0, 255],
|
||||
"blueviolet": [138, 43, 226],
|
||||
"brown": [165, 42, 42],
|
||||
"burlywood": [222, 184, 135],
|
||||
"cadetblue": [95, 158, 160],
|
||||
"chartreuse": [127, 255, 0],
|
||||
"chocolate": [210, 105, 30],
|
||||
"coral": [255, 127, 80],
|
||||
"cornflowerblue": [100, 149, 237],
|
||||
"cornsilk": [255, 248, 220],
|
||||
"crimson": [220, 20, 60],
|
||||
"cyan": [0, 255, 255],
|
||||
"darkblue": [0, 0, 139],
|
||||
"darkcyan": [0, 139, 139],
|
||||
"darkgoldenrod": [184, 134, 11],
|
||||
"darkgray": [169, 169, 169],
|
||||
"darkgreen": [0, 100, 0],
|
||||
"darkgrey": [169, 169, 169],
|
||||
"darkkhaki": [189, 183, 107],
|
||||
"darkmagenta": [139, 0, 139],
|
||||
"darkolivegreen": [85, 107, 47],
|
||||
"darkorange": [255, 140, 0],
|
||||
"darkorchid": [153, 50, 204],
|
||||
"darkred": [139, 0, 0],
|
||||
"darksalmon": [233, 150, 122],
|
||||
"darkseagreen": [143, 188, 143],
|
||||
"darkslateblue": [72, 61, 139],
|
||||
"darkslategray": [47, 79, 79],
|
||||
"darkslategrey": [47, 79, 79],
|
||||
"darkturquoise": [0, 206, 209],
|
||||
"darkviolet": [148, 0, 211],
|
||||
"deeppink": [255, 20, 147],
|
||||
"deepskyblue": [0, 191, 255],
|
||||
"dimgray": [105, 105, 105],
|
||||
"dimgrey": [105, 105, 105],
|
||||
"dodgerblue": [30, 144, 255],
|
||||
"firebrick": [178, 34, 34],
|
||||
"floralwhite": [255, 250, 240],
|
||||
"forestgreen": [34, 139, 34],
|
||||
"fuchsia": [255, 0, 255],
|
||||
"gainsboro": [220, 220, 220],
|
||||
"ghostwhite": [248, 248, 255],
|
||||
"gold": [255, 215, 0],
|
||||
"goldenrod": [218, 165, 32],
|
||||
"gray": [128, 128, 128],
|
||||
"green": [0, 128, 0],
|
||||
"greenyellow": [173, 255, 47],
|
||||
"grey": [128, 128, 128],
|
||||
"honeydew": [240, 255, 240],
|
||||
"hotpink": [255, 105, 180],
|
||||
"indianred": [205, 92, 92],
|
||||
"indigo": [75, 0, 130],
|
||||
"ivory": [255, 255, 240],
|
||||
"khaki": [240, 230, 140],
|
||||
"lavender": [230, 230, 250],
|
||||
"lavenderblush": [255, 240, 245],
|
||||
"lawngreen": [124, 252, 0],
|
||||
"lemonchiffon": [255, 250, 205],
|
||||
"lightblue": [173, 216, 230],
|
||||
"lightcoral": [240, 128, 128],
|
||||
"lightcyan": [224, 255, 255],
|
||||
"lightgoldenrodyellow": [250, 250, 210],
|
||||
"lightgray": [211, 211, 211],
|
||||
"lightgreen": [144, 238, 144],
|
||||
"lightgrey": [211, 211, 211],
|
||||
"lightpink": [255, 182, 193],
|
||||
"lightsalmon": [255, 160, 122],
|
||||
"lightseagreen": [32, 178, 170],
|
||||
"lightskyblue": [135, 206, 250],
|
||||
"lightslategray": [119, 136, 153],
|
||||
"lightslategrey": [119, 136, 153],
|
||||
"lightsteelblue": [176, 196, 222],
|
||||
"lightyellow": [255, 255, 224],
|
||||
"lime": [0, 255, 0],
|
||||
"limegreen": [50, 205, 50],
|
||||
"linen": [250, 240, 230],
|
||||
"magenta": [255, 0, 255],
|
||||
"maroon": [128, 0, 0],
|
||||
"mediumaquamarine": [102, 205, 170],
|
||||
"mediumblue": [0, 0, 205],
|
||||
"mediumorchid": [186, 85, 211],
|
||||
"mediumpurple": [147, 112, 219],
|
||||
"mediumseagreen": [60, 179, 113],
|
||||
"mediumslateblue": [123, 104, 238],
|
||||
"mediumspringgreen": [0, 250, 154],
|
||||
"mediumturquoise": [72, 209, 204],
|
||||
"mediumvioletred": [199, 21, 133],
|
||||
"midnightblue": [25, 25, 112],
|
||||
"mintcream": [245, 255, 250],
|
||||
"mistyrose": [255, 228, 225],
|
||||
"moccasin": [255, 228, 181],
|
||||
"navajowhite": [255, 222, 173],
|
||||
"navy": [0, 0, 128],
|
||||
"oldlace": [253, 245, 230],
|
||||
"olive": [128, 128, 0],
|
||||
"olivedrab": [107, 142, 35],
|
||||
"orange": [255, 165, 0],
|
||||
"orangered": [255, 69, 0],
|
||||
"orchid": [218, 112, 214],
|
||||
"palegoldenrod": [238, 232, 170],
|
||||
"palegreen": [152, 251, 152],
|
||||
"paleturquoise": [175, 238, 238],
|
||||
"palevioletred": [219, 112, 147],
|
||||
"papayawhip": [255, 239, 213],
|
||||
"peachpuff": [255, 218, 185],
|
||||
"peru": [205, 133, 63],
|
||||
"pink": [255, 192, 203],
|
||||
"plum": [221, 160, 221],
|
||||
"powderblue": [176, 224, 230],
|
||||
"purple": [128, 0, 128],
|
||||
"rebeccapurple": [102, 51, 153],
|
||||
"red": [255, 0, 0],
|
||||
"rosybrown": [188, 143, 143],
|
||||
"royalblue": [65, 105, 225],
|
||||
"saddlebrown": [139, 69, 19],
|
||||
"salmon": [250, 128, 114],
|
||||
"sandybrown": [244, 164, 96],
|
||||
"seagreen": [46, 139, 87],
|
||||
"seashell": [255, 245, 238],
|
||||
"sienna": [160, 82, 45],
|
||||
"silver": [192, 192, 192],
|
||||
"skyblue": [135, 206, 235],
|
||||
"slateblue": [106, 90, 205],
|
||||
"slategray": [112, 128, 144],
|
||||
"slategrey": [112, 128, 144],
|
||||
"snow": [255, 250, 250],
|
||||
"springgreen": [0, 255, 127],
|
||||
"steelblue": [70, 130, 180],
|
||||
"tan": [210, 180, 140],
|
||||
"teal": [0, 128, 128],
|
||||
"thistle": [216, 191, 216],
|
||||
"tomato": [255, 99, 71],
|
||||
"turquoise": [64, 224, 208],
|
||||
"violet": [238, 130, 238],
|
||||
"wheat": [245, 222, 179],
|
||||
"white": [255, 255, 255],
|
||||
"whitesmoke": [245, 245, 245],
|
||||
"yellow": [255, 255, 0],
|
||||
"yellowgreen": [154, 205, 50]
|
||||
};
|
||||
|
||||
module.exports = Color;
|
147
src/core.js
Normal file
147
src/core.js
Normal file
@@ -0,0 +1,147 @@
|
||||
var Promise = require('./promise');
|
||||
var Support = require('./support');
|
||||
var CanvasRenderer = require('./renderers/canvas');
|
||||
var ImageLoader = require('./imageloader');
|
||||
var NodeParser = require('./nodeparser');
|
||||
var NodeContainer = require('./nodecontainer');
|
||||
var log = require('./log');
|
||||
var utils = require('./utils');
|
||||
var createWindowClone = require('./clone');
|
||||
var loadUrlDocument = require('./proxy').loadUrlDocument;
|
||||
var getBounds = utils.getBounds;
|
||||
|
||||
var html2canvasNodeAttribute = "data-html2canvas-node";
|
||||
var html2canvasCloneIndex = 0;
|
||||
|
||||
function html2canvas(nodeList, options) {
|
||||
var index = html2canvasCloneIndex++;
|
||||
options = options || {};
|
||||
if (options.logging) {
|
||||
window.html2canvas.logging = true;
|
||||
window.html2canvas.start = Date.now();
|
||||
}
|
||||
|
||||
options.async = typeof(options.async) === "undefined" ? true : options.async;
|
||||
options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
|
||||
options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
|
||||
options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
|
||||
options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
|
||||
options.renderer = typeof(options.renderer) === "function" ? options.renderer : CanvasRenderer;
|
||||
options.strict = !!options.strict;
|
||||
|
||||
if (typeof(nodeList) === "string") {
|
||||
if (typeof(options.proxy) !== "string") {
|
||||
return Promise.reject("Proxy must be used when rendering url");
|
||||
}
|
||||
var width = options.width != null ? options.width : window.innerWidth;
|
||||
var height = options.height != null ? options.height : window.innerHeight;
|
||||
return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) {
|
||||
return renderWindow(container.contentWindow.document.documentElement, container, options, width, height);
|
||||
});
|
||||
}
|
||||
|
||||
var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
|
||||
node.setAttribute(html2canvasNodeAttribute + index, index);
|
||||
return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {
|
||||
if (typeof(options.onrendered) === "function") {
|
||||
log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
|
||||
options.onrendered(canvas);
|
||||
}
|
||||
return canvas;
|
||||
});
|
||||
}
|
||||
|
||||
html2canvas.Promise = Promise;
|
||||
html2canvas.CanvasRenderer = CanvasRenderer;
|
||||
html2canvas.NodeContainer = NodeContainer;
|
||||
html2canvas.log = log;
|
||||
html2canvas.utils = utils;
|
||||
|
||||
module.exports = (typeof(document) === "undefined" || typeof(Object.create) !== "function" || typeof(document.createElement("canvas").getContext) !== "function") ? function() {
|
||||
return Promise.reject("No canvas support");
|
||||
} : html2canvas;
|
||||
|
||||
function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) {
|
||||
return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) {
|
||||
log("Document cloned");
|
||||
var attributeName = html2canvasNodeAttribute + html2canvasIndex;
|
||||
var selector = "[" + attributeName + "='" + html2canvasIndex + "']";
|
||||
document.querySelector(selector).removeAttribute(attributeName);
|
||||
var clonedWindow = container.contentWindow;
|
||||
var node = clonedWindow.document.querySelector(selector);
|
||||
var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true);
|
||||
return oncloneHandler.then(function() {
|
||||
return renderWindow(node, container, options, windowWidth, windowHeight);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderWindow(node, container, options, windowWidth, windowHeight) {
|
||||
var clonedWindow = container.contentWindow;
|
||||
var support = new Support(clonedWindow.document);
|
||||
var imageLoader = new ImageLoader(options, support);
|
||||
var bounds = getBounds(node);
|
||||
var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document);
|
||||
var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document);
|
||||
var renderer = new options.renderer(width, height, imageLoader, options, document);
|
||||
var parser = new NodeParser(node, renderer, support, imageLoader, options);
|
||||
return parser.ready.then(function() {
|
||||
log("Finished rendering");
|
||||
var canvas;
|
||||
|
||||
if (options.type === "view") {
|
||||
canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
|
||||
} else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
|
||||
canvas = renderer.canvas;
|
||||
} else {
|
||||
canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: clonedWindow.pageXOffset, y: clonedWindow.pageYOffset});
|
||||
}
|
||||
|
||||
cleanupContainer(container, options);
|
||||
return canvas;
|
||||
});
|
||||
}
|
||||
|
||||
function cleanupContainer(container, options) {
|
||||
if (options.removeContainer) {
|
||||
container.parentNode.removeChild(container);
|
||||
log("Cleaned up container");
|
||||
}
|
||||
}
|
||||
|
||||
function crop(canvas, bounds) {
|
||||
var croppedCanvas = document.createElement("canvas");
|
||||
var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left));
|
||||
var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width));
|
||||
var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top));
|
||||
var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height));
|
||||
croppedCanvas.width = bounds.width;
|
||||
croppedCanvas.height = bounds.height;
|
||||
log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", (x2-x1), "height:", (y2-y1));
|
||||
log("Resulting crop with width", bounds.width, "and height", bounds.height, " with x", x1, "and y", y1);
|
||||
croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, x2-x1, y2-y1, bounds.x, bounds.y, x2-x1, y2-y1);
|
||||
return croppedCanvas;
|
||||
}
|
||||
|
||||
function documentWidth (doc) {
|
||||
return Math.max(
|
||||
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
|
||||
Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
|
||||
Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
|
||||
);
|
||||
}
|
||||
|
||||
function documentHeight (doc) {
|
||||
return Math.max(
|
||||
Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
|
||||
Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
|
||||
Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
|
||||
);
|
||||
}
|
||||
|
||||
function absoluteUrl(url) {
|
||||
var link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.href = link.href;
|
||||
return link;
|
||||
}
|
23
src/dummyimagecontainer.js
Normal file
23
src/dummyimagecontainer.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var Promise = require('./promise');
|
||||
var log = require('./log');
|
||||
var smallImage = require('./utils').smallImage;
|
||||
|
||||
function DummyImageContainer(src) {
|
||||
this.src = src;
|
||||
log("DummyImageContainer for", src);
|
||||
if (!this.promise || !this.image) {
|
||||
log("Initiating DummyImageContainer");
|
||||
DummyImageContainer.prototype.image = new Image();
|
||||
var image = this.image;
|
||||
DummyImageContainer.prototype.promise = new Promise(function(resolve, reject) {
|
||||
image.onload = resolve;
|
||||
image.onerror = reject;
|
||||
image.src = smallImage();
|
||||
if (image.complete === true) {
|
||||
resolve(image);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DummyImageContainer;
|
1
src/fabric
Submodule
1
src/fabric
Submodule
Submodule src/fabric added at 791c74a82e
52
src/font.js
Normal file
52
src/font.js
Normal file
@@ -0,0 +1,52 @@
|
||||
var smallImage = require('./utils').smallImage;
|
||||
|
||||
function Font(family, size) {
|
||||
var container = document.createElement('div'),
|
||||
img = document.createElement('img'),
|
||||
span = document.createElement('span'),
|
||||
sampleText = 'Hidden Text',
|
||||
baseline,
|
||||
middle;
|
||||
|
||||
container.style.visibility = "hidden";
|
||||
container.style.fontFamily = family;
|
||||
container.style.fontSize = size;
|
||||
container.style.margin = 0;
|
||||
container.style.padding = 0;
|
||||
|
||||
document.body.appendChild(container);
|
||||
|
||||
img.src = smallImage();
|
||||
img.width = 1;
|
||||
img.height = 1;
|
||||
|
||||
img.style.margin = 0;
|
||||
img.style.padding = 0;
|
||||
img.style.verticalAlign = "baseline";
|
||||
|
||||
span.style.fontFamily = family;
|
||||
span.style.fontSize = size;
|
||||
span.style.margin = 0;
|
||||
span.style.padding = 0;
|
||||
|
||||
span.appendChild(document.createTextNode(sampleText));
|
||||
container.appendChild(span);
|
||||
container.appendChild(img);
|
||||
baseline = (img.offsetTop - span.offsetTop) + 1;
|
||||
|
||||
container.removeChild(span);
|
||||
container.appendChild(document.createTextNode(sampleText));
|
||||
|
||||
container.style.lineHeight = "normal";
|
||||
img.style.verticalAlign = "super";
|
||||
|
||||
middle = (img.offsetTop-container.offsetTop) + 1;
|
||||
|
||||
document.body.removeChild(container);
|
||||
|
||||
this.baseline = baseline;
|
||||
this.lineWidth = 1;
|
||||
this.middle = middle;
|
||||
}
|
||||
|
||||
module.exports = Font;
|
14
src/fontmetrics.js
Normal file
14
src/fontmetrics.js
Normal file
@@ -0,0 +1,14 @@
|
||||
var Font = require('./font');
|
||||
|
||||
function FontMetrics() {
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
FontMetrics.prototype.getMetrics = function(family, size) {
|
||||
if (this.data[family + "-" + size] === undefined) {
|
||||
this.data[family + "-" + size] = new Font(family, size);
|
||||
}
|
||||
return this.data[family + "-" + size];
|
||||
};
|
||||
|
||||
module.exports = FontMetrics;
|
32
src/framecontainer.js
Normal file
32
src/framecontainer.js
Normal file
@@ -0,0 +1,32 @@
|
||||
var utils = require('./utils');
|
||||
var Promise = require('./promise');
|
||||
var getBounds = utils.getBounds;
|
||||
var loadUrlDocument = require('./proxy').loadUrlDocument;
|
||||
|
||||
function FrameContainer(container, sameOrigin, options) {
|
||||
this.image = null;
|
||||
this.src = container;
|
||||
var self = this;
|
||||
var bounds = getBounds(container);
|
||||
this.promise = (!sameOrigin ? this.proxyLoad(options.proxy, bounds, options) : new Promise(function(resolve) {
|
||||
if (container.contentWindow.document.URL === "about:blank" || container.contentWindow.document.documentElement == null) {
|
||||
container.contentWindow.onload = container.onload = function() {
|
||||
resolve(container);
|
||||
};
|
||||
} else {
|
||||
resolve(container);
|
||||
}
|
||||
})).then(function(container) {
|
||||
var html2canvas = require('./core');
|
||||
return html2canvas(container.contentWindow.document.documentElement, {type: 'view', width: container.width, height: container.height, proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer, allowTaint: options.allowTaint, imageTimeout: options.imageTimeout / 2});
|
||||
}).then(function(canvas) {
|
||||
return self.image = canvas;
|
||||
});
|
||||
}
|
||||
|
||||
FrameContainer.prototype.proxyLoad = function(proxy, bounds, options) {
|
||||
var container = this.src;
|
||||
return loadUrlDocument(container.src, proxy, container.ownerDocument, bounds.width, bounds.height, options);
|
||||
};
|
||||
|
||||
module.exports = FrameContainer;
|
19
src/gradientcontainer.js
Normal file
19
src/gradientcontainer.js
Normal file
@@ -0,0 +1,19 @@
|
||||
var Promise = require('./promise');
|
||||
|
||||
function GradientContainer(imageData) {
|
||||
this.src = imageData.value;
|
||||
this.colorStops = [];
|
||||
this.type = null;
|
||||
this.x0 = 0.5;
|
||||
this.y0 = 0.5;
|
||||
this.x1 = 0.5;
|
||||
this.y1 = 0.5;
|
||||
this.promise = Promise.resolve(true);
|
||||
}
|
||||
|
||||
GradientContainer.prototype.TYPES = {
|
||||
LINEAR: 1,
|
||||
RADIAL: 2
|
||||
};
|
||||
|
||||
module.exports = GradientContainer;
|
21
src/imagecontainer.js
Normal file
21
src/imagecontainer.js
Normal file
@@ -0,0 +1,21 @@
|
||||
var Promise = require('./promise');
|
||||
|
||||
function ImageContainer(src, cors) {
|
||||
this.src = src;
|
||||
this.image = new Image();
|
||||
var self = this;
|
||||
this.tainted = null;
|
||||
this.promise = new Promise(function(resolve, reject) {
|
||||
self.image.onload = resolve;
|
||||
self.image.onerror = reject;
|
||||
if (cors) {
|
||||
self.image.crossOrigin = "anonymous";
|
||||
}
|
||||
self.image.src = src;
|
||||
if (self.image.complete === true) {
|
||||
resolve(self.image);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ImageContainer;
|
158
src/imageloader.js
Normal file
158
src/imageloader.js
Normal file
@@ -0,0 +1,158 @@
|
||||
var Promise = require('./promise');
|
||||
var log = require('./log');
|
||||
var ImageContainer = require('./imagecontainer');
|
||||
var DummyImageContainer = require('./dummyimagecontainer');
|
||||
var ProxyImageContainer = require('./proxyimagecontainer');
|
||||
var FrameContainer = require('./framecontainer');
|
||||
var SVGContainer = require('./svgcontainer');
|
||||
var SVGNodeContainer = require('./svgnodecontainer');
|
||||
var LinearGradientContainer = require('./lineargradientcontainer');
|
||||
var WebkitGradientContainer = require('./webkitgradientcontainer');
|
||||
var bind = require('./utils').bind;
|
||||
|
||||
function ImageLoader(options, support) {
|
||||
this.link = null;
|
||||
this.options = options;
|
||||
this.support = support;
|
||||
this.origin = this.getOrigin(window.location.href);
|
||||
}
|
||||
|
||||
ImageLoader.prototype.findImages = function(nodes) {
|
||||
var images = [];
|
||||
nodes.reduce(function(imageNodes, container) {
|
||||
switch(container.node.nodeName) {
|
||||
case "IMG":
|
||||
return imageNodes.concat([{
|
||||
args: [container.node.src],
|
||||
method: "url"
|
||||
}]);
|
||||
case "svg":
|
||||
case "IFRAME":
|
||||
return imageNodes.concat([{
|
||||
args: [container.node],
|
||||
method: container.node.nodeName
|
||||
}]);
|
||||
}
|
||||
return imageNodes;
|
||||
}, []).forEach(this.addImage(images, this.loadImage), this);
|
||||
return images;
|
||||
};
|
||||
|
||||
ImageLoader.prototype.findBackgroundImage = function(images, container) {
|
||||
container.parseBackgroundImages().filter(this.hasImageBackground).forEach(this.addImage(images, this.loadImage), this);
|
||||
return images;
|
||||
};
|
||||
|
||||
ImageLoader.prototype.addImage = function(images, callback) {
|
||||
return function(newImage) {
|
||||
newImage.args.forEach(function(image) {
|
||||
if (!this.imageExists(images, image)) {
|
||||
images.splice(0, 0, callback.call(this, newImage));
|
||||
log('Added image #' + (images.length), typeof(image) === "string" ? image.substring(0, 100) : image);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
};
|
||||
|
||||
ImageLoader.prototype.hasImageBackground = function(imageData) {
|
||||
return imageData.method !== "none";
|
||||
};
|
||||
|
||||
ImageLoader.prototype.loadImage = function(imageData) {
|
||||
if (imageData.method === "url") {
|
||||
var src = imageData.args[0];
|
||||
if (this.isSVG(src) && !this.support.svg && !this.options.allowTaint) {
|
||||
return new SVGContainer(src);
|
||||
} else if (src.match(/data:image\/.*;base64,/i)) {
|
||||
return new ImageContainer(src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''), false);
|
||||
} else if (this.isSameOrigin(src) || this.options.allowTaint === true || this.isSVG(src)) {
|
||||
return new ImageContainer(src, false);
|
||||
} else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) {
|
||||
return new ImageContainer(src, true);
|
||||
} else if (this.options.proxy) {
|
||||
return new ProxyImageContainer(src, this.options.proxy);
|
||||
} else {
|
||||
return new DummyImageContainer(src);
|
||||
}
|
||||
} else if (imageData.method === "linear-gradient") {
|
||||
return new LinearGradientContainer(imageData);
|
||||
} else if (imageData.method === "gradient") {
|
||||
return new WebkitGradientContainer(imageData);
|
||||
} else if (imageData.method === "svg") {
|
||||
return new SVGNodeContainer(imageData.args[0], this.support.svg);
|
||||
} else if (imageData.method === "IFRAME") {
|
||||
return new FrameContainer(imageData.args[0], this.isSameOrigin(imageData.args[0].src), this.options);
|
||||
} else {
|
||||
return new DummyImageContainer(imageData);
|
||||
}
|
||||
};
|
||||
|
||||
ImageLoader.prototype.isSVG = function(src) {
|
||||
return src.substring(src.length - 3).toLowerCase() === "svg" || SVGContainer.prototype.isInline(src);
|
||||
};
|
||||
|
||||
ImageLoader.prototype.imageExists = function(images, src) {
|
||||
return images.some(function(image) {
|
||||
return image.src === src;
|
||||
});
|
||||
};
|
||||
|
||||
ImageLoader.prototype.isSameOrigin = function(url) {
|
||||
return (this.getOrigin(url) === this.origin);
|
||||
};
|
||||
|
||||
ImageLoader.prototype.getOrigin = function(url) {
|
||||
var link = this.link || (this.link = document.createElement("a"));
|
||||
link.href = url;
|
||||
link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
|
||||
return link.protocol + link.hostname + link.port;
|
||||
};
|
||||
|
||||
ImageLoader.prototype.getPromise = function(container) {
|
||||
return this.timeout(container, this.options.imageTimeout)['catch'](function() {
|
||||
var dummy = new DummyImageContainer(container.src);
|
||||
return dummy.promise.then(function(image) {
|
||||
container.image = image;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ImageLoader.prototype.get = function(src) {
|
||||
var found = null;
|
||||
return this.images.some(function(img) {
|
||||
return (found = img).src === src;
|
||||
}) ? found : null;
|
||||
};
|
||||
|
||||
ImageLoader.prototype.fetch = function(nodes) {
|
||||
this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));
|
||||
this.images.forEach(function(image, index) {
|
||||
image.promise.then(function() {
|
||||
log("Succesfully loaded image #"+ (index+1), image);
|
||||
}, function(e) {
|
||||
log("Failed loading image #"+ (index+1), image, e);
|
||||
});
|
||||
});
|
||||
this.ready = Promise.all(this.images.map(this.getPromise, this));
|
||||
log("Finished searching images");
|
||||
return this;
|
||||
};
|
||||
|
||||
ImageLoader.prototype.timeout = function(container, timeout) {
|
||||
var timer;
|
||||
var promise = Promise.race([container.promise, new Promise(function(res, reject) {
|
||||
timer = setTimeout(function() {
|
||||
log("Timed out loading image", container);
|
||||
reject(container);
|
||||
}, timeout);
|
||||
})]).then(function(container) {
|
||||
clearTimeout(timer);
|
||||
return container;
|
||||
});
|
||||
promise['catch'](function() {
|
||||
clearTimeout(timer);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
module.exports = ImageLoader;
|
78
src/lineargradientcontainer.js
Normal file
78
src/lineargradientcontainer.js
Normal file
@@ -0,0 +1,78 @@
|
||||
var GradientContainer = require('./gradientcontainer');
|
||||
var Color = require('./color');
|
||||
|
||||
function LinearGradientContainer(imageData) {
|
||||
GradientContainer.apply(this, arguments);
|
||||
this.type = this.TYPES.LINEAR;
|
||||
|
||||
var hasDirection = imageData.args[0].match(this.stepRegExp) === null;
|
||||
|
||||
if (hasDirection) {
|
||||
imageData.args[0].split(" ").reverse().forEach(function(position) {
|
||||
switch(position) {
|
||||
case "left":
|
||||
this.x0 = 0;
|
||||
this.x1 = 1;
|
||||
break;
|
||||
case "top":
|
||||
this.y0 = 0;
|
||||
this.y1 = 1;
|
||||
break;
|
||||
case "right":
|
||||
this.x0 = 1;
|
||||
this.x1 = 0;
|
||||
break;
|
||||
case "bottom":
|
||||
this.y0 = 1;
|
||||
this.y1 = 0;
|
||||
break;
|
||||
case "to":
|
||||
var y0 = this.y0;
|
||||
var x0 = this.x0;
|
||||
this.y0 = this.y1;
|
||||
this.x0 = this.x1;
|
||||
this.x1 = x0;
|
||||
this.y1 = y0;
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
} else {
|
||||
this.y0 = 0;
|
||||
this.y1 = 1;
|
||||
}
|
||||
|
||||
this.colorStops = imageData.args.slice(hasDirection ? 1 : 0).map(function(colorStop) {
|
||||
var colorStopMatch = colorStop.match(this.stepRegExp);
|
||||
return {
|
||||
color: new Color(colorStopMatch[1]),
|
||||
stop: colorStopMatch[3] === "%" ? colorStopMatch[2] / 100 : null
|
||||
};
|
||||
}, this);
|
||||
|
||||
if (this.colorStops[0].stop === null) {
|
||||
this.colorStops[0].stop = 0;
|
||||
}
|
||||
|
||||
if (this.colorStops[this.colorStops.length - 1].stop === null) {
|
||||
this.colorStops[this.colorStops.length - 1].stop = 1;
|
||||
}
|
||||
|
||||
this.colorStops.forEach(function(colorStop, index) {
|
||||
if (colorStop.stop === null) {
|
||||
this.colorStops.slice(index).some(function(find, count) {
|
||||
if (find.stop !== null) {
|
||||
colorStop.stop = ((find.stop - this.colorStops[index - 1].stop) / (count + 1)) + this.colorStops[index - 1].stop;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
LinearGradientContainer.prototype = Object.create(GradientContainer.prototype);
|
||||
|
||||
LinearGradientContainer.prototype.stepRegExp = /((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/;
|
||||
|
||||
module.exports = LinearGradientContainer;
|
5
src/log.js
Normal file
5
src/log.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = function() {
|
||||
if (window.html2canvas.logging && window.console && window.console.log) {
|
||||
Function.prototype.bind.call(window.console.log, (window.console)).apply(window.console, [(Date.now() - window.html2canvas.start) + "ms", "html2canvas:"].concat([].slice.call(arguments, 0)));
|
||||
}
|
||||
};
|
291
src/nodecontainer.js
Normal file
291
src/nodecontainer.js
Normal file
@@ -0,0 +1,291 @@
|
||||
var Color = require('./color');
|
||||
var utils = require('./utils');
|
||||
var getBounds = utils.getBounds;
|
||||
var parseBackgrounds = utils.parseBackgrounds;
|
||||
var offsetBounds = utils.offsetBounds;
|
||||
|
||||
function NodeContainer(node, parent) {
|
||||
this.node = node;
|
||||
this.parent = parent;
|
||||
this.stack = null;
|
||||
this.bounds = null;
|
||||
this.borders = null;
|
||||
this.clip = [];
|
||||
this.backgroundClip = [];
|
||||
this.offsetBounds = null;
|
||||
this.visible = null;
|
||||
this.computedStyles = null;
|
||||
this.colors = {};
|
||||
this.styles = {};
|
||||
this.backgroundImages = null;
|
||||
this.transformData = null;
|
||||
this.transformMatrix = null;
|
||||
this.isPseudoElement = false;
|
||||
this.opacity = null;
|
||||
}
|
||||
|
||||
NodeContainer.prototype.cloneTo = function(stack) {
|
||||
stack.visible = this.visible;
|
||||
stack.borders = this.borders;
|
||||
stack.bounds = this.bounds;
|
||||
stack.clip = this.clip;
|
||||
stack.backgroundClip = this.backgroundClip;
|
||||
stack.computedStyles = this.computedStyles;
|
||||
stack.styles = this.styles;
|
||||
stack.backgroundImages = this.backgroundImages;
|
||||
stack.opacity = this.opacity;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.getOpacity = function() {
|
||||
return this.opacity === null ? (this.opacity = this.cssFloat('opacity')) : this.opacity;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.assignStack = function(stack) {
|
||||
this.stack = stack;
|
||||
stack.children.push(this);
|
||||
};
|
||||
|
||||
NodeContainer.prototype.isElementVisible = function() {
|
||||
return this.node.nodeType === Node.TEXT_NODE ? this.parent.visible : (
|
||||
this.css('display') !== "none" &&
|
||||
this.css('visibility') !== "hidden" &&
|
||||
!this.node.hasAttribute("data-html2canvas-ignore") &&
|
||||
(this.node.nodeName !== "INPUT" || this.node.getAttribute("type") !== "hidden")
|
||||
);
|
||||
};
|
||||
|
||||
NodeContainer.prototype.css = function(attribute) {
|
||||
if (!this.computedStyles) {
|
||||
this.computedStyles = this.isPseudoElement ? this.parent.computedStyle(this.before ? ":before" : ":after") : this.computedStyle(null);
|
||||
}
|
||||
|
||||
return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
|
||||
};
|
||||
|
||||
NodeContainer.prototype.prefixedCss = function(attribute) {
|
||||
var prefixes = ["webkit", "moz", "ms", "o"];
|
||||
var value = this.css(attribute);
|
||||
if (value === undefined) {
|
||||
prefixes.some(function(prefix) {
|
||||
value = this.css(prefix + attribute.substr(0, 1).toUpperCase() + attribute.substr(1));
|
||||
return value !== undefined;
|
||||
}, this);
|
||||
}
|
||||
return value === undefined ? null : value;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.computedStyle = function(type) {
|
||||
return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type);
|
||||
};
|
||||
|
||||
NodeContainer.prototype.cssInt = function(attribute) {
|
||||
var value = parseInt(this.css(attribute), 10);
|
||||
return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
|
||||
};
|
||||
|
||||
NodeContainer.prototype.color = function(attribute) {
|
||||
return this.colors[attribute] || (this.colors[attribute] = new Color(this.css(attribute)));
|
||||
};
|
||||
|
||||
NodeContainer.prototype.cssFloat = function(attribute) {
|
||||
var value = parseFloat(this.css(attribute));
|
||||
return (isNaN(value)) ? 0 : value;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.fontWeight = function() {
|
||||
var weight = this.css("fontWeight");
|
||||
switch(parseInt(weight, 10)){
|
||||
case 401:
|
||||
weight = "bold";
|
||||
break;
|
||||
case 400:
|
||||
weight = "normal";
|
||||
break;
|
||||
}
|
||||
return weight;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseClip = function() {
|
||||
var matches = this.css('clip').match(this.CLIP);
|
||||
if (matches) {
|
||||
return {
|
||||
top: parseInt(matches[1], 10),
|
||||
right: parseInt(matches[2], 10),
|
||||
bottom: parseInt(matches[3], 10),
|
||||
left: parseInt(matches[4], 10)
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseBackgroundImages = function() {
|
||||
return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage")));
|
||||
};
|
||||
|
||||
NodeContainer.prototype.cssList = function(property, index) {
|
||||
var value = (this.css(property) || '').split(',');
|
||||
value = value[index || 0] || value[0] || 'auto';
|
||||
value = value.trim().split(' ');
|
||||
if (value.length === 1) {
|
||||
value = [value[0], isPercentage(value[0]) ? 'auto' : value[0]];
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {
|
||||
var size = this.cssList("backgroundSize", index);
|
||||
var width, height;
|
||||
|
||||
if (isPercentage(size[0])) {
|
||||
width = bounds.width * parseFloat(size[0]) / 100;
|
||||
} else if (/contain|cover/.test(size[0])) {
|
||||
var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;
|
||||
return (targetRatio < currentRatio ^ size[0] === 'contain') ? {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};
|
||||
} else {
|
||||
width = parseInt(size[0], 10);
|
||||
}
|
||||
|
||||
if (size[0] === 'auto' && size[1] === 'auto') {
|
||||
height = image.height;
|
||||
} else if (size[1] === 'auto') {
|
||||
height = width / image.width * image.height;
|
||||
} else if (isPercentage(size[1])) {
|
||||
height = bounds.height * parseFloat(size[1]) / 100;
|
||||
} else {
|
||||
height = parseInt(size[1], 10);
|
||||
}
|
||||
|
||||
if (size[0] === 'auto') {
|
||||
width = height / image.height * image.width;
|
||||
}
|
||||
|
||||
return {width: width, height: height};
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {
|
||||
var position = this.cssList('backgroundPosition', index);
|
||||
var left, top;
|
||||
|
||||
if (isPercentage(position[0])){
|
||||
left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);
|
||||
} else {
|
||||
left = parseInt(position[0], 10);
|
||||
}
|
||||
|
||||
if (position[1] === 'auto') {
|
||||
top = left / image.width * image.height;
|
||||
} else if (isPercentage(position[1])){
|
||||
top = (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;
|
||||
} else {
|
||||
top = parseInt(position[1], 10);
|
||||
}
|
||||
|
||||
if (position[0] === 'auto') {
|
||||
left = top / image.height * image.width;
|
||||
}
|
||||
|
||||
return {left: left, top: top};
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseBackgroundRepeat = function(index) {
|
||||
return this.cssList("backgroundRepeat", index)[0];
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseTextShadows = function() {
|
||||
var textShadow = this.css("textShadow");
|
||||
var results = [];
|
||||
|
||||
if (textShadow && textShadow !== 'none') {
|
||||
var shadows = textShadow.match(this.TEXT_SHADOW_PROPERTY);
|
||||
for (var i = 0; shadows && (i < shadows.length); i++) {
|
||||
var s = shadows[i].match(this.TEXT_SHADOW_VALUES);
|
||||
results.push({
|
||||
color: new Color(s[0]),
|
||||
offsetX: s[1] ? parseFloat(s[1].replace('px', '')) : 0,
|
||||
offsetY: s[2] ? parseFloat(s[2].replace('px', '')) : 0,
|
||||
blur: s[3] ? s[3].replace('px', '') : 0
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseTransform = function() {
|
||||
if (!this.transformData) {
|
||||
if (this.hasTransform()) {
|
||||
var offset = this.parseBounds();
|
||||
var origin = this.prefixedCss("transformOrigin").split(" ").map(removePx).map(asFloat);
|
||||
origin[0] += offset.left;
|
||||
origin[1] += offset.top;
|
||||
this.transformData = {
|
||||
origin: origin,
|
||||
matrix: this.parseTransformMatrix()
|
||||
};
|
||||
} else {
|
||||
this.transformData = {
|
||||
origin: [0, 0],
|
||||
matrix: [1, 0, 0, 1, 0, 0]
|
||||
};
|
||||
}
|
||||
}
|
||||
return this.transformData;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseTransformMatrix = function() {
|
||||
if (!this.transformMatrix) {
|
||||
var transform = this.prefixedCss("transform");
|
||||
var matrix = transform ? parseMatrix(transform.match(this.MATRIX_PROPERTY)) : null;
|
||||
this.transformMatrix = matrix ? matrix : [1, 0, 0, 1, 0, 0];
|
||||
}
|
||||
return this.transformMatrix;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.parseBounds = function() {
|
||||
return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));
|
||||
};
|
||||
|
||||
NodeContainer.prototype.hasTransform = function() {
|
||||
return this.parseTransformMatrix().join(",") !== "1,0,0,1,0,0" || (this.parent && this.parent.hasTransform());
|
||||
};
|
||||
|
||||
NodeContainer.prototype.getValue = function() {
|
||||
var value = this.node.value || "";
|
||||
if (this.node.tagName === "SELECT") {
|
||||
value = selectionValue(this.node);
|
||||
} else if (this.node.type === "password") {
|
||||
value = Array(value.length + 1).join('\u2022'); // jshint ignore:line
|
||||
}
|
||||
return value.length === 0 ? (this.node.placeholder || "") : value;
|
||||
};
|
||||
|
||||
NodeContainer.prototype.MATRIX_PROPERTY = /(matrix)\((.+)\)/;
|
||||
NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
|
||||
NodeContainer.prototype.TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
|
||||
NodeContainer.prototype.CLIP = /^rect\((\d+)px,? (\d+)px,? (\d+)px,? (\d+)px\)$/;
|
||||
|
||||
function selectionValue(node) {
|
||||
var option = node.options[node.selectedIndex || 0];
|
||||
return option ? (option.text || "") : "";
|
||||
}
|
||||
|
||||
function parseMatrix(match) {
|
||||
if (match && match[1] === "matrix") {
|
||||
return match[2].split(",").map(function(s) {
|
||||
return parseFloat(s.trim());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function isPercentage(value) {
|
||||
return value.toString().indexOf("%") !== -1;
|
||||
}
|
||||
|
||||
function removePx(str) {
|
||||
return str.replace("px", "");
|
||||
}
|
||||
|
||||
function asFloat(str) {
|
||||
return parseFloat(str);
|
||||
}
|
||||
|
||||
module.exports = NodeContainer;
|
870
src/nodeparser.js
Normal file
870
src/nodeparser.js
Normal file
@@ -0,0 +1,870 @@
|
||||
var log = require('./log');
|
||||
var punycode = require('punycode');
|
||||
var NodeContainer = require('./nodecontainer');
|
||||
var TextContainer = require('./textcontainer');
|
||||
var PseudoElementContainer = require('./pseudoelementcontainer');
|
||||
var FontMetrics = require('./fontmetrics');
|
||||
var Color = require('./color');
|
||||
var Promise = require('./promise');
|
||||
var StackingContext = require('./stackingcontext');
|
||||
var utils = require('./utils');
|
||||
var bind = utils.bind;
|
||||
var getBounds = utils.getBounds;
|
||||
var parseBackgrounds = utils.parseBackgrounds;
|
||||
var offsetBounds = utils.offsetBounds;
|
||||
|
||||
function NodeParser(element, renderer, support, imageLoader, options) {
|
||||
log("Starting NodeParser");
|
||||
this.renderer = renderer;
|
||||
this.options = options;
|
||||
this.range = null;
|
||||
this.support = support;
|
||||
this.renderQueue = [];
|
||||
this.stack = new StackingContext(true, 1, element.ownerDocument, null);
|
||||
var parent = new NodeContainer(element, null);
|
||||
if (options.background) {
|
||||
renderer.rectangle(0, 0, renderer.width, renderer.height, new Color(options.background));
|
||||
}
|
||||
if (element === element.ownerDocument.documentElement) {
|
||||
// http://www.w3.org/TR/css3-background/#special-backgrounds
|
||||
var canvasBackground = new NodeContainer(parent.color('backgroundColor').isTransparent() ? element.ownerDocument.body : element.ownerDocument.documentElement, null);
|
||||
renderer.rectangle(0, 0, renderer.width, renderer.height, canvasBackground.color('backgroundColor'));
|
||||
}
|
||||
parent.visibile = parent.isElementVisible();
|
||||
this.createPseudoHideStyles(element.ownerDocument);
|
||||
this.disableAnimations(element.ownerDocument);
|
||||
this.nodes = flatten([parent].concat(this.getChildren(parent)).filter(function(container) {
|
||||
return container.visible = container.isElementVisible();
|
||||
}).map(this.getPseudoElements, this));
|
||||
this.fontMetrics = new FontMetrics();
|
||||
log("Fetched nodes, total:", this.nodes.length);
|
||||
log("Calculate overflow clips");
|
||||
this.calculateOverflowClips();
|
||||
log("Start fetching images");
|
||||
this.images = imageLoader.fetch(this.nodes.filter(isElement));
|
||||
this.ready = this.images.ready.then(bind(function() {
|
||||
log("Images loaded, starting parsing");
|
||||
log("Creating stacking contexts");
|
||||
this.createStackingContexts();
|
||||
log("Sorting stacking contexts");
|
||||
this.sortStackingContexts(this.stack);
|
||||
this.parse(this.stack);
|
||||
log("Render queue created with " + this.renderQueue.length + " items");
|
||||
return new Promise(bind(function(resolve) {
|
||||
if (!options.async) {
|
||||
this.renderQueue.forEach(this.paint, this);
|
||||
resolve();
|
||||
} else if (typeof(options.async) === "function") {
|
||||
options.async.call(this, this.renderQueue, resolve);
|
||||
} else if (this.renderQueue.length > 0){
|
||||
this.renderIndex = 0;
|
||||
this.asyncRenderer(this.renderQueue, resolve);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}, this));
|
||||
}, this));
|
||||
}
|
||||
|
||||
NodeParser.prototype.calculateOverflowClips = function() {
|
||||
this.nodes.forEach(function(container) {
|
||||
if (isElement(container)) {
|
||||
if (isPseudoElement(container)) {
|
||||
container.appendToDOM();
|
||||
}
|
||||
container.borders = this.parseBorders(container);
|
||||
var clip = (container.css('overflow') === "hidden") ? [container.borders.clip] : [];
|
||||
var cssClip = container.parseClip();
|
||||
if (cssClip && ["absolute", "fixed"].indexOf(container.css('position')) !== -1) {
|
||||
clip.push([["rect",
|
||||
container.bounds.left + cssClip.left,
|
||||
container.bounds.top + cssClip.top,
|
||||
cssClip.right - cssClip.left,
|
||||
cssClip.bottom - cssClip.top
|
||||
]]);
|
||||
}
|
||||
container.clip = hasParentClip(container) ? container.parent.clip.concat(clip) : clip;
|
||||
container.backgroundClip = (container.css('overflow') !== "hidden") ? container.clip.concat([container.borders.clip]) : container.clip;
|
||||
if (isPseudoElement(container)) {
|
||||
container.cleanDOM();
|
||||
}
|
||||
} else if (isTextNode(container)) {
|
||||
container.clip = hasParentClip(container) ? container.parent.clip : [];
|
||||
}
|
||||
if (!isPseudoElement(container)) {
|
||||
container.bounds = null;
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
function hasParentClip(container) {
|
||||
return container.parent && container.parent.clip.length;
|
||||
}
|
||||
|
||||
NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
|
||||
asyncTimer = asyncTimer || Date.now();
|
||||
this.paint(queue[this.renderIndex++]);
|
||||
if (queue.length === this.renderIndex) {
|
||||
resolve();
|
||||
} else if (asyncTimer + 20 > Date.now()) {
|
||||
this.asyncRenderer(queue, resolve, asyncTimer);
|
||||
} else {
|
||||
setTimeout(bind(function() {
|
||||
this.asyncRenderer(queue, resolve);
|
||||
}, this), 0);
|
||||
}
|
||||
};
|
||||
|
||||
NodeParser.prototype.createPseudoHideStyles = function(document) {
|
||||
this.createStyles(document, '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + ':before { content: "" !important; display: none !important; }' +
|
||||
'.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER + ':after { content: "" !important; display: none !important; }');
|
||||
};
|
||||
|
||||
NodeParser.prototype.disableAnimations = function(document) {
|
||||
this.createStyles(document, '* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; ' +
|
||||
'-webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}');
|
||||
};
|
||||
|
||||
NodeParser.prototype.createStyles = function(document, styles) {
|
||||
var hidePseudoElements = document.createElement('style');
|
||||
hidePseudoElements.innerHTML = styles;
|
||||
document.body.appendChild(hidePseudoElements);
|
||||
};
|
||||
|
||||
NodeParser.prototype.getPseudoElements = function(container) {
|
||||
var nodes = [[container]];
|
||||
if (container.node.nodeType === Node.ELEMENT_NODE) {
|
||||
var before = this.getPseudoElement(container, ":before");
|
||||
var after = this.getPseudoElement(container, ":after");
|
||||
|
||||
if (before) {
|
||||
nodes.push(before);
|
||||
}
|
||||
|
||||
if (after) {
|
||||
nodes.push(after);
|
||||
}
|
||||
}
|
||||
return flatten(nodes);
|
||||
};
|
||||
|
||||
function toCamelCase(str) {
|
||||
return str.replace(/(\-[a-z])/g, function(match){
|
||||
return match.toUpperCase().replace('-','');
|
||||
});
|
||||
}
|
||||
|
||||
NodeParser.prototype.getPseudoElement = function(container, type) {
|
||||
var style = container.computedStyle(type);
|
||||
if(!style || !style.content || style.content === "none" || style.content === "-moz-alt-content" || style.display === "none") {
|
||||
return null;
|
||||
}
|
||||
|
||||
var content = stripQuotes(style.content);
|
||||
var isImage = content.substr(0, 3) === 'url';
|
||||
var pseudoNode = document.createElement(isImage ? 'img' : 'html2canvaspseudoelement');
|
||||
var pseudoContainer = new PseudoElementContainer(pseudoNode, container, type);
|
||||
|
||||
for (var i = style.length-1; i >= 0; i--) {
|
||||
var property = toCamelCase(style.item(i));
|
||||
pseudoNode.style[property] = style[property];
|
||||
}
|
||||
|
||||
pseudoNode.className = PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + " " + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER;
|
||||
|
||||
if (isImage) {
|
||||
pseudoNode.src = parseBackgrounds(content)[0].args[0];
|
||||
return [pseudoContainer];
|
||||
} else {
|
||||
var text = document.createTextNode(content);
|
||||
pseudoNode.appendChild(text);
|
||||
return [pseudoContainer, new TextContainer(text, pseudoContainer)];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
NodeParser.prototype.getChildren = function(parentContainer) {
|
||||
return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) {
|
||||
var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
|
||||
return node.nodeType === Node.ELEMENT_NODE && container.length && node.tagName !== "TEXTAREA" ? (container[0].isElementVisible() ? container.concat(this.getChildren(container[0])) : []) : container;
|
||||
}, this));
|
||||
};
|
||||
|
||||
NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
|
||||
var stack = new StackingContext(hasOwnStacking, container.getOpacity(), container.node, container.parent);
|
||||
container.cloneTo(stack);
|
||||
var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
|
||||
parentStack.contexts.push(stack);
|
||||
container.stack = stack;
|
||||
};
|
||||
|
||||
NodeParser.prototype.createStackingContexts = function() {
|
||||
this.nodes.forEach(function(container) {
|
||||
if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container) || container.hasTransform())) {
|
||||
this.newStackingContext(container, true);
|
||||
} else if (isElement(container) && ((isPositioned(container) && zIndex0(container)) || isInlineBlock(container) || isFloating(container))) {
|
||||
this.newStackingContext(container, false);
|
||||
} else {
|
||||
container.assignStack(container.parent.stack);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
NodeParser.prototype.isBodyWithTransparentRoot = function(container) {
|
||||
return container.node.nodeName === "BODY" && container.parent.color('backgroundColor').isTransparent();
|
||||
};
|
||||
|
||||
NodeParser.prototype.isRootElement = function(container) {
|
||||
return container.parent === null;
|
||||
};
|
||||
|
||||
NodeParser.prototype.sortStackingContexts = function(stack) {
|
||||
stack.contexts.sort(zIndexSort(stack.contexts.slice(0)));
|
||||
stack.contexts.forEach(this.sortStackingContexts, this);
|
||||
};
|
||||
|
||||
NodeParser.prototype.parseTextBounds = function(container) {
|
||||
return function(text, index, textList) {
|
||||
if (container.parent.css("textDecoration").substr(0, 4) !== "none" || text.trim().length !== 0) {
|
||||
if (this.support.rangeBounds && !container.parent.hasTransform()) {
|
||||
var offset = textList.slice(0, index).join("").length;
|
||||
return this.getRangeBounds(container.node, offset, text.length);
|
||||
} else if (container.node && typeof(container.node.data) === "string") {
|
||||
var replacementNode = container.node.splitText(text.length);
|
||||
var bounds = this.getWrapperBounds(container.node, container.parent.hasTransform());
|
||||
container.node = replacementNode;
|
||||
return bounds;
|
||||
}
|
||||
} else if(!this.support.rangeBounds || container.parent.hasTransform()){
|
||||
container.node = container.node.splitText(text.length);
|
||||
}
|
||||
return {};
|
||||
};
|
||||
};
|
||||
|
||||
NodeParser.prototype.getWrapperBounds = function(node, transform) {
|
||||
var wrapper = node.ownerDocument.createElement('html2canvaswrapper');
|
||||
var parent = node.parentNode,
|
||||
backupText = node.cloneNode(true);
|
||||
|
||||
wrapper.appendChild(node.cloneNode(true));
|
||||
parent.replaceChild(wrapper, node);
|
||||
var bounds = transform ? offsetBounds(wrapper) : getBounds(wrapper);
|
||||
parent.replaceChild(backupText, wrapper);
|
||||
return bounds;
|
||||
};
|
||||
|
||||
NodeParser.prototype.getRangeBounds = function(node, offset, length) {
|
||||
var range = this.range || (this.range = node.ownerDocument.createRange());
|
||||
range.setStart(node, offset);
|
||||
range.setEnd(node, offset + length);
|
||||
return range.getBoundingClientRect();
|
||||
};
|
||||
|
||||
function ClearTransform() {}
|
||||
|
||||
NodeParser.prototype.parse = function(stack) {
|
||||
// http://www.w3.org/TR/CSS21/visuren.html#z-index
|
||||
var negativeZindex = stack.contexts.filter(negativeZIndex); // 2. the child stacking contexts with negative stack levels (most negative first).
|
||||
var descendantElements = stack.children.filter(isElement);
|
||||
var descendantNonFloats = descendantElements.filter(not(isFloating));
|
||||
var nonInlineNonPositionedDescendants = descendantNonFloats.filter(not(isPositioned)).filter(not(inlineLevel)); // 3 the in-flow, non-inline-level, non-positioned descendants.
|
||||
var nonPositionedFloats = descendantElements.filter(not(isPositioned)).filter(isFloating); // 4. the non-positioned floats.
|
||||
var inFlow = descendantNonFloats.filter(not(isPositioned)).filter(inlineLevel); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
|
||||
var stackLevel0 = stack.contexts.concat(descendantNonFloats.filter(isPositioned)).filter(zIndex0); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
|
||||
var text = stack.children.filter(isTextNode).filter(hasText);
|
||||
var positiveZindex = stack.contexts.filter(positiveZIndex); // 7. the child stacking contexts with positive stack levels (least positive first).
|
||||
negativeZindex.concat(nonInlineNonPositionedDescendants).concat(nonPositionedFloats)
|
||||
.concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) {
|
||||
this.renderQueue.push(container);
|
||||
if (isStackingContext(container)) {
|
||||
this.parse(container);
|
||||
this.renderQueue.push(new ClearTransform());
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
NodeParser.prototype.paint = function(container) {
|
||||
try {
|
||||
if (container instanceof ClearTransform) {
|
||||
this.renderer.ctx.restore();
|
||||
} else if (isTextNode(container)) {
|
||||
if (isPseudoElement(container.parent)) {
|
||||
container.parent.appendToDOM();
|
||||
}
|
||||
this.paintText(container);
|
||||
if (isPseudoElement(container.parent)) {
|
||||
container.parent.cleanDOM();
|
||||
}
|
||||
} else {
|
||||
this.paintNode(container);
|
||||
}
|
||||
} catch(e) {
|
||||
log(e);
|
||||
if (this.options.strict) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NodeParser.prototype.paintNode = function(container) {
|
||||
if (isStackingContext(container)) {
|
||||
this.renderer.setOpacity(container.opacity);
|
||||
this.renderer.ctx.save();
|
||||
if (container.hasTransform()) {
|
||||
this.renderer.setTransform(container.parseTransform());
|
||||
}
|
||||
}
|
||||
|
||||
if (container.node.nodeName === "INPUT" && container.node.type === "checkbox") {
|
||||
this.paintCheckbox(container);
|
||||
} else if (container.node.nodeName === "INPUT" && container.node.type === "radio") {
|
||||
this.paintRadio(container);
|
||||
} else {
|
||||
this.paintElement(container);
|
||||
}
|
||||
};
|
||||
|
||||
NodeParser.prototype.paintElement = function(container) {
|
||||
var bounds = container.parseBounds();
|
||||
this.renderer.clip(container.backgroundClip, function() {
|
||||
this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
|
||||
}, this);
|
||||
|
||||
this.renderer.clip(container.clip, function() {
|
||||
this.renderer.renderBorders(container.borders.borders);
|
||||
}, this);
|
||||
|
||||
this.renderer.clip(container.backgroundClip, function() {
|
||||
switch (container.node.nodeName) {
|
||||
case "svg":
|
||||
case "IFRAME":
|
||||
var imgContainer = this.images.get(container.node);
|
||||
if (imgContainer) {
|
||||
this.renderer.renderImage(container, bounds, container.borders, imgContainer);
|
||||
} else {
|
||||
log("Error loading <" + container.node.nodeName + ">", container.node);
|
||||
}
|
||||
break;
|
||||
case "IMG":
|
||||
var imageContainer = this.images.get(container.node.src);
|
||||
if (imageContainer) {
|
||||
this.renderer.renderImage(container, bounds, container.borders, imageContainer);
|
||||
} else {
|
||||
log("Error loading <img>", container.node.src);
|
||||
}
|
||||
break;
|
||||
case "CANVAS":
|
||||
this.renderer.renderImage(container, bounds, container.borders, {image: container.node});
|
||||
break;
|
||||
case "SELECT":
|
||||
case "INPUT":
|
||||
case "TEXTAREA":
|
||||
this.paintFormValue(container);
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
NodeParser.prototype.paintCheckbox = function(container) {
|
||||
var b = container.parseBounds();
|
||||
|
||||
var size = Math.min(b.width, b.height);
|
||||
var bounds = {width: size - 1, height: size - 1, top: b.top, left: b.left};
|
||||
var r = [3, 3];
|
||||
var radius = [r, r, r, r];
|
||||
var borders = [1,1,1,1].map(function(w) {
|
||||
return {color: new Color('#A5A5A5'), width: w};
|
||||
});
|
||||
|
||||
var borderPoints = calculateCurvePoints(bounds, radius, borders);
|
||||
|
||||
this.renderer.clip(container.backgroundClip, function() {
|
||||
this.renderer.rectangle(bounds.left + 1, bounds.top + 1, bounds.width - 2, bounds.height - 2, new Color("#DEDEDE"));
|
||||
this.renderer.renderBorders(calculateBorders(borders, bounds, borderPoints, radius));
|
||||
if (container.node.checked) {
|
||||
this.renderer.font(new Color('#424242'), 'normal', 'normal', 'bold', (size - 3) + "px", 'arial');
|
||||
this.renderer.text("\u2714", bounds.left + size / 6, bounds.top + size - 1);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
NodeParser.prototype.paintRadio = function(container) {
|
||||
var bounds = container.parseBounds();
|
||||
|
||||
var size = Math.min(bounds.width, bounds.height) - 2;
|
||||
|
||||
this.renderer.clip(container.backgroundClip, function() {
|
||||
this.renderer.circleStroke(bounds.left + 1, bounds.top + 1, size, new Color('#DEDEDE'), 1, new Color('#A5A5A5'));
|
||||
if (container.node.checked) {
|
||||
this.renderer.circle(Math.ceil(bounds.left + size / 4) + 1, Math.ceil(bounds.top + size / 4) + 1, Math.floor(size / 2), new Color('#424242'));
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
NodeParser.prototype.paintFormValue = function(container) {
|
||||
var value = container.getValue();
|
||||
if (value.length > 0) {
|
||||
var document = container.node.ownerDocument;
|
||||
var wrapper = document.createElement('html2canvaswrapper');
|
||||
var properties = ['lineHeight', 'textAlign', 'fontFamily', 'fontWeight', 'fontSize', 'color',
|
||||
'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom',
|
||||
'width', 'height', 'borderLeftStyle', 'borderTopStyle', 'borderLeftWidth', 'borderTopWidth',
|
||||
'boxSizing', 'whiteSpace', 'wordWrap'];
|
||||
|
||||
properties.forEach(function(property) {
|
||||
try {
|
||||
wrapper.style[property] = container.css(property);
|
||||
} catch(e) {
|
||||
// Older IE has issues with "border"
|
||||
log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
|
||||
}
|
||||
});
|
||||
var bounds = container.parseBounds();
|
||||
wrapper.style.position = "fixed";
|
||||
wrapper.style.left = bounds.left + "px";
|
||||
wrapper.style.top = bounds.top + "px";
|
||||
wrapper.textContent = value;
|
||||
document.body.appendChild(wrapper);
|
||||
this.paintText(new TextContainer(wrapper.firstChild, container));
|
||||
document.body.removeChild(wrapper);
|
||||
}
|
||||
};
|
||||
|
||||
NodeParser.prototype.paintText = function(container) {
|
||||
container.applyTextTransform();
|
||||
var characters = punycode.ucs2.decode(container.node.data);
|
||||
var textList = (!this.options.letterRendering || noLetterSpacing(container)) && !hasUnicode(container.node.data) ? getWords(characters) : characters.map(function(character) {
|
||||
return punycode.ucs2.encode([character]);
|
||||
});
|
||||
|
||||
var weight = container.parent.fontWeight();
|
||||
var size = container.parent.css('fontSize');
|
||||
var family = container.parent.css('fontFamily');
|
||||
var shadows = container.parent.parseTextShadows();
|
||||
|
||||
this.renderer.font(container.parent.color('color'), container.parent.css('fontStyle'), container.parent.css('fontVariant'), weight, size, family);
|
||||
if (shadows.length) {
|
||||
// TODO: support multiple text shadows
|
||||
this.renderer.fontShadow(shadows[0].color, shadows[0].offsetX, shadows[0].offsetY, shadows[0].blur);
|
||||
} else {
|
||||
this.renderer.clearShadow();
|
||||
}
|
||||
|
||||
this.renderer.clip(container.parent.clip, function() {
|
||||
textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
|
||||
if (bounds) {
|
||||
this.renderer.text(textList[index], bounds.left, bounds.bottom);
|
||||
this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
|
||||
}
|
||||
}, this);
|
||||
}, this);
|
||||
};
|
||||
|
||||
NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
|
||||
switch(container.css("textDecoration").split(" ")[0]) {
|
||||
case "underline":
|
||||
// Draws a line at the baseline of the font
|
||||
// TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size
|
||||
this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.color("color"));
|
||||
break;
|
||||
case "overline":
|
||||
this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.color("color"));
|
||||
break;
|
||||
case "line-through":
|
||||
// TODO try and find exact position for line-through
|
||||
this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.color("color"));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var borderColorTransforms = {
|
||||
inset: [
|
||||
["darken", 0.60],
|
||||
["darken", 0.10],
|
||||
["darken", 0.10],
|
||||
["darken", 0.60]
|
||||
]
|
||||
};
|
||||
|
||||
NodeParser.prototype.parseBorders = function(container) {
|
||||
var nodeBounds = container.parseBounds();
|
||||
var radius = getBorderRadiusData(container);
|
||||
var borders = ["Top", "Right", "Bottom", "Left"].map(function(side, index) {
|
||||
var style = container.css('border' + side + 'Style');
|
||||
var color = container.color('border' + side + 'Color');
|
||||
if (style === "inset" && color.isBlack()) {
|
||||
color = new Color([255, 255, 255, color.a]); // this is wrong, but
|
||||
}
|
||||
var colorTransform = borderColorTransforms[style] ? borderColorTransforms[style][index] : null;
|
||||
return {
|
||||
width: container.cssInt('border' + side + 'Width'),
|
||||
color: colorTransform ? color[colorTransform[0]](colorTransform[1]) : color,
|
||||
args: null
|
||||
};
|
||||
});
|
||||
var borderPoints = calculateCurvePoints(nodeBounds, radius, borders);
|
||||
|
||||
return {
|
||||
clip: this.parseBackgroundClip(container, borderPoints, borders, radius, nodeBounds),
|
||||
borders: calculateBorders(borders, nodeBounds, borderPoints, radius)
|
||||
};
|
||||
};
|
||||
|
||||
function calculateBorders(borders, nodeBounds, borderPoints, radius) {
|
||||
return borders.map(function(border, borderSide) {
|
||||
if (border.width > 0) {
|
||||
var bx = nodeBounds.left;
|
||||
var by = nodeBounds.top;
|
||||
var bw = nodeBounds.width;
|
||||
var bh = nodeBounds.height - (borders[2].width);
|
||||
|
||||
switch(borderSide) {
|
||||
case 0:
|
||||
// top border
|
||||
bh = borders[0].width;
|
||||
border.args = drawSide({
|
||||
c1: [bx, by],
|
||||
c2: [bx + bw, by],
|
||||
c3: [bx + bw - borders[1].width, by + bh],
|
||||
c4: [bx + borders[3].width, by + bh]
|
||||
}, radius[0], radius[1],
|
||||
borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
|
||||
break;
|
||||
case 1:
|
||||
// right border
|
||||
bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
|
||||
bw = borders[1].width;
|
||||
|
||||
border.args = drawSide({
|
||||
c1: [bx + bw, by],
|
||||
c2: [bx + bw, by + bh + borders[2].width],
|
||||
c3: [bx, by + bh],
|
||||
c4: [bx, by + borders[0].width]
|
||||
}, radius[1], radius[2],
|
||||
borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
|
||||
break;
|
||||
case 2:
|
||||
// bottom border
|
||||
by = (by + nodeBounds.height) - (borders[2].width);
|
||||
bh = borders[2].width;
|
||||
border.args = drawSide({
|
||||
c1: [bx + bw, by + bh],
|
||||
c2: [bx, by + bh],
|
||||
c3: [bx + borders[3].width, by],
|
||||
c4: [bx + bw - borders[3].width, by]
|
||||
}, radius[2], radius[3],
|
||||
borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
|
||||
break;
|
||||
case 3:
|
||||
// left border
|
||||
bw = borders[3].width;
|
||||
border.args = drawSide({
|
||||
c1: [bx, by + bh + borders[2].width],
|
||||
c2: [bx, by],
|
||||
c3: [bx + bw, by + borders[0].width],
|
||||
c4: [bx + bw, by + bh]
|
||||
}, radius[3], radius[0],
|
||||
borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return border;
|
||||
});
|
||||
}
|
||||
|
||||
NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, borders, radius, bounds) {
|
||||
var backgroundClip = container.css('backgroundClip'),
|
||||
borderArgs = [];
|
||||
|
||||
switch(backgroundClip) {
|
||||
case "content-box":
|
||||
case "padding-box":
|
||||
parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
|
||||
parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
|
||||
parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
|
||||
parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
|
||||
break;
|
||||
|
||||
default:
|
||||
parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
|
||||
parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
|
||||
parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
|
||||
parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
|
||||
break;
|
||||
}
|
||||
|
||||
return borderArgs;
|
||||
};
|
||||
|
||||
function getCurvePoints(x, y, r1, r2) {
|
||||
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
|
||||
var ox = (r1) * kappa, // control point offset horizontal
|
||||
oy = (r2) * kappa, // control point offset vertical
|
||||
xm = x + r1, // x-middle
|
||||
ym = y + r2; // y-middle
|
||||
return {
|
||||
topLeft: bezierCurve({x: x, y: ym}, {x: x, y: ym - oy}, {x: xm - ox, y: y}, {x: xm, y: y}),
|
||||
topRight: bezierCurve({x: x, y: y}, {x: x + ox,y: y}, {x: xm, y: ym - oy}, {x: xm, y: ym}),
|
||||
bottomRight: bezierCurve({x: xm, y: y}, {x: xm, y: y + oy}, {x: x + ox, y: ym}, {x: x, y: ym}),
|
||||
bottomLeft: bezierCurve({x: xm, y: ym}, {x: xm - ox, y: ym}, {x: x, y: y + oy}, {x: x, y:y})
|
||||
};
|
||||
}
|
||||
|
||||
function calculateCurvePoints(bounds, borderRadius, borders) {
|
||||
var x = bounds.left,
|
||||
y = bounds.top,
|
||||
width = bounds.width,
|
||||
height = bounds.height,
|
||||
|
||||
tlh = borderRadius[0][0],
|
||||
tlv = borderRadius[0][1],
|
||||
trh = borderRadius[1][0],
|
||||
trv = borderRadius[1][1],
|
||||
brh = borderRadius[2][0],
|
||||
brv = borderRadius[2][1],
|
||||
blh = borderRadius[3][0],
|
||||
blv = borderRadius[3][1];
|
||||
|
||||
var topWidth = width - trh,
|
||||
rightHeight = height - brv,
|
||||
bottomWidth = width - brh,
|
||||
leftHeight = height - blv;
|
||||
|
||||
return {
|
||||
topLeftOuter: getCurvePoints(x, y, tlh, tlv).topLeft.subdivide(0.5),
|
||||
topLeftInner: getCurvePoints(x + borders[3].width, y + borders[0].width, Math.max(0, tlh - borders[3].width), Math.max(0, tlv - borders[0].width)).topLeft.subdivide(0.5),
|
||||
topRightOuter: getCurvePoints(x + topWidth, y, trh, trv).topRight.subdivide(0.5),
|
||||
topRightInner: getCurvePoints(x + Math.min(topWidth, width + borders[3].width), y + borders[0].width, (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width, trv - borders[0].width).topRight.subdivide(0.5),
|
||||
bottomRightOuter: getCurvePoints(x + bottomWidth, y + rightHeight, brh, brv).bottomRight.subdivide(0.5),
|
||||
bottomRightInner: getCurvePoints(x + Math.min(bottomWidth, width - borders[3].width), y + Math.min(rightHeight, height + borders[0].width), Math.max(0, brh - borders[1].width), brv - borders[2].width).bottomRight.subdivide(0.5),
|
||||
bottomLeftOuter: getCurvePoints(x, y + leftHeight, blh, blv).bottomLeft.subdivide(0.5),
|
||||
bottomLeftInner: getCurvePoints(x + borders[3].width, y + leftHeight, Math.max(0, blh - borders[3].width), blv - borders[2].width).bottomLeft.subdivide(0.5)
|
||||
};
|
||||
}
|
||||
|
||||
function bezierCurve(start, startControl, endControl, end) {
|
||||
var lerp = function (a, b, t) {
|
||||
return {
|
||||
x: a.x + (b.x - a.x) * t,
|
||||
y: a.y + (b.y - a.y) * t
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
start: start,
|
||||
startControl: startControl,
|
||||
endControl: endControl,
|
||||
end: end,
|
||||
subdivide: function(t) {
|
||||
var ab = lerp(start, startControl, t),
|
||||
bc = lerp(startControl, endControl, t),
|
||||
cd = lerp(endControl, end, t),
|
||||
abbc = lerp(ab, bc, t),
|
||||
bccd = lerp(bc, cd, t),
|
||||
dest = lerp(abbc, bccd, t);
|
||||
return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
|
||||
},
|
||||
curveTo: function(borderArgs) {
|
||||
borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
|
||||
},
|
||||
curveToReversed: function(borderArgs) {
|
||||
borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
|
||||
var borderArgs = [];
|
||||
|
||||
if (radius1[0] > 0 || radius1[1] > 0) {
|
||||
borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
|
||||
outer1[1].curveTo(borderArgs);
|
||||
} else {
|
||||
borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
|
||||
}
|
||||
|
||||
if (radius2[0] > 0 || radius2[1] > 0) {
|
||||
borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
|
||||
outer2[0].curveTo(borderArgs);
|
||||
borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
|
||||
inner2[0].curveToReversed(borderArgs);
|
||||
} else {
|
||||
borderArgs.push(["line", borderData.c2[0], borderData.c2[1]]);
|
||||
borderArgs.push(["line", borderData.c3[0], borderData.c3[1]]);
|
||||
}
|
||||
|
||||
if (radius1[0] > 0 || radius1[1] > 0) {
|
||||
borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
|
||||
inner1[1].curveToReversed(borderArgs);
|
||||
} else {
|
||||
borderArgs.push(["line", borderData.c4[0], borderData.c4[1]]);
|
||||
}
|
||||
|
||||
return borderArgs;
|
||||
}
|
||||
|
||||
function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
|
||||
if (radius1[0] > 0 || radius1[1] > 0) {
|
||||
borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
|
||||
corner1[0].curveTo(borderArgs);
|
||||
corner1[1].curveTo(borderArgs);
|
||||
} else {
|
||||
borderArgs.push(["line", x, y]);
|
||||
}
|
||||
|
||||
if (radius2[0] > 0 || radius2[1] > 0) {
|
||||
borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
|
||||
}
|
||||
}
|
||||
|
||||
function negativeZIndex(container) {
|
||||
return container.cssInt("zIndex") < 0;
|
||||
}
|
||||
|
||||
function positiveZIndex(container) {
|
||||
return container.cssInt("zIndex") > 0;
|
||||
}
|
||||
|
||||
function zIndex0(container) {
|
||||
return container.cssInt("zIndex") === 0;
|
||||
}
|
||||
|
||||
function inlineLevel(container) {
|
||||
return ["inline", "inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
|
||||
}
|
||||
|
||||
function isStackingContext(container) {
|
||||
return (container instanceof StackingContext);
|
||||
}
|
||||
|
||||
function hasText(container) {
|
||||
return container.node.data.trim().length > 0;
|
||||
}
|
||||
|
||||
function noLetterSpacing(container) {
|
||||
return (/^(normal|none|0px)$/.test(container.parent.css("letterSpacing")));
|
||||
}
|
||||
|
||||
function getBorderRadiusData(container) {
|
||||
return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
|
||||
var value = container.css('border' + side + 'Radius');
|
||||
var arr = value.split(" ");
|
||||
if (arr.length <= 1) {
|
||||
arr[1] = arr[0];
|
||||
}
|
||||
return arr.map(asInt);
|
||||
});
|
||||
}
|
||||
|
||||
function renderableNode(node) {
|
||||
return (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE);
|
||||
}
|
||||
|
||||
function isPositionedForStacking(container) {
|
||||
var position = container.css("position");
|
||||
var zIndex = (["absolute", "relative", "fixed"].indexOf(position) !== -1) ? container.css("zIndex") : "auto";
|
||||
return zIndex !== "auto";
|
||||
}
|
||||
|
||||
function isPositioned(container) {
|
||||
return container.css("position") !== "static";
|
||||
}
|
||||
|
||||
function isFloating(container) {
|
||||
return container.css("float") !== "none";
|
||||
}
|
||||
|
||||
function isInlineBlock(container) {
|
||||
return ["inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
|
||||
}
|
||||
|
||||
function not(callback) {
|
||||
var context = this;
|
||||
return function() {
|
||||
return !callback.apply(context, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
function isElement(container) {
|
||||
return container.node.nodeType === Node.ELEMENT_NODE;
|
||||
}
|
||||
|
||||
function isPseudoElement(container) {
|
||||
return container.isPseudoElement === true;
|
||||
}
|
||||
|
||||
function isTextNode(container) {
|
||||
return container.node.nodeType === Node.TEXT_NODE;
|
||||
}
|
||||
|
||||
function zIndexSort(contexts) {
|
||||
return function(a, b) {
|
||||
return (a.cssInt("zIndex") + (contexts.indexOf(a) / contexts.length)) - (b.cssInt("zIndex") + (contexts.indexOf(b) / contexts.length));
|
||||
};
|
||||
}
|
||||
|
||||
function hasOpacity(container) {
|
||||
return container.getOpacity() < 1;
|
||||
}
|
||||
|
||||
function asInt(value) {
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
|
||||
function getWidth(border) {
|
||||
return border.width;
|
||||
}
|
||||
|
||||
function nonIgnoredElement(nodeContainer) {
|
||||
return (nodeContainer.node.nodeType !== Node.ELEMENT_NODE || ["SCRIPT", "HEAD", "TITLE", "OBJECT", "BR", "OPTION"].indexOf(nodeContainer.node.nodeName) === -1);
|
||||
}
|
||||
|
||||
function flatten(arrays) {
|
||||
return [].concat.apply([], arrays);
|
||||
}
|
||||
|
||||
function stripQuotes(content) {
|
||||
var first = content.substr(0, 1);
|
||||
return (first === content.substr(content.length - 1) && first.match(/'|"/)) ? content.substr(1, content.length - 2) : content;
|
||||
}
|
||||
|
||||
function getWords(characters) {
|
||||
var words = [], i = 0, onWordBoundary = false, word;
|
||||
while(characters.length) {
|
||||
if (isWordBoundary(characters[i]) === onWordBoundary) {
|
||||
word = characters.splice(0, i);
|
||||
if (word.length) {
|
||||
words.push(punycode.ucs2.encode(word));
|
||||
}
|
||||
onWordBoundary =! onWordBoundary;
|
||||
i = 0;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= characters.length) {
|
||||
word = characters.splice(0, i);
|
||||
if (word.length) {
|
||||
words.push(punycode.ucs2.encode(word));
|
||||
}
|
||||
}
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
function isWordBoundary(characterCode) {
|
||||
return [
|
||||
32, // <space>
|
||||
13, // \r
|
||||
10, // \n
|
||||
9, // \t
|
||||
45 // -
|
||||
].indexOf(characterCode) !== -1;
|
||||
}
|
||||
|
||||
function hasUnicode(string) {
|
||||
return (/[^\u0000-\u00ff]/).test(string);
|
||||
}
|
||||
|
||||
module.exports = NodeParser;
|
1
src/promise.js
Normal file
1
src/promise.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('es6-promise').Promise;
|
96
src/proxy.js
Normal file
96
src/proxy.js
Normal file
@@ -0,0 +1,96 @@
|
||||
var Promise = require('./promise');
|
||||
var XHR = require('./xhr');
|
||||
var utils = require('./utils');
|
||||
var log = require('./log');
|
||||
var createWindowClone = require('./clone');
|
||||
var decode64 = utils.decode64;
|
||||
|
||||
function Proxy(src, proxyUrl, document) {
|
||||
var supportsCORS = ('withCredentials' in new XMLHttpRequest());
|
||||
if (!proxyUrl) {
|
||||
return Promise.reject("No proxy configured");
|
||||
}
|
||||
var callback = createCallback(supportsCORS);
|
||||
var url = createProxyUrl(proxyUrl, src, callback);
|
||||
|
||||
return supportsCORS ? XHR(url) : (jsonp(document, url, callback).then(function(response) {
|
||||
return decode64(response.content);
|
||||
}));
|
||||
}
|
||||
var proxyCount = 0;
|
||||
|
||||
function ProxyURL(src, proxyUrl, document) {
|
||||
var supportsCORSImage = ('crossOrigin' in new Image());
|
||||
var callback = createCallback(supportsCORSImage);
|
||||
var url = createProxyUrl(proxyUrl, src, callback);
|
||||
return (supportsCORSImage ? Promise.resolve(url) : jsonp(document, url, callback).then(function(response) {
|
||||
return "data:" + response.type + ";base64," + response.content;
|
||||
}));
|
||||
}
|
||||
|
||||
function jsonp(document, url, callback) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var s = document.createElement("script");
|
||||
var cleanup = function() {
|
||||
delete window.html2canvas.proxy[callback];
|
||||
document.body.removeChild(s);
|
||||
};
|
||||
window.html2canvas.proxy[callback] = function(response) {
|
||||
cleanup();
|
||||
resolve(response);
|
||||
};
|
||||
s.src = url;
|
||||
s.onerror = function(e) {
|
||||
cleanup();
|
||||
reject(e);
|
||||
};
|
||||
document.body.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
function createCallback(useCORS) {
|
||||
return !useCORS ? "html2canvas_" + Date.now() + "_" + (++proxyCount) + "_" + Math.round(Math.random() * 100000) : "";
|
||||
}
|
||||
|
||||
function createProxyUrl(proxyUrl, src, callback) {
|
||||
return proxyUrl + "?url=" + encodeURIComponent(src) + (callback.length ? "&callback=html2canvas.proxy." + callback : "");
|
||||
}
|
||||
|
||||
function documentFromHTML(src) {
|
||||
return function(html) {
|
||||
var parser = new DOMParser(), doc;
|
||||
try {
|
||||
doc = parser.parseFromString(html, "text/html");
|
||||
} catch(e) {
|
||||
log("DOMParser not supported, falling back to createHTMLDocument");
|
||||
doc = document.implementation.createHTMLDocument("");
|
||||
try {
|
||||
doc.open();
|
||||
doc.write(html);
|
||||
doc.close();
|
||||
} catch(ee) {
|
||||
log("createHTMLDocument write not supported, falling back to document.body.innerHTML");
|
||||
doc.body.innerHTML = html; // ie9 doesnt support writing to documentElement
|
||||
}
|
||||
}
|
||||
|
||||
var b = doc.querySelector("base");
|
||||
if (!b || !b.href.host) {
|
||||
var base = doc.createElement("base");
|
||||
base.href = src;
|
||||
doc.head.insertBefore(base, doc.head.firstChild);
|
||||
}
|
||||
|
||||
return doc;
|
||||
};
|
||||
}
|
||||
|
||||
function loadUrlDocument(src, proxy, document, width, height, options) {
|
||||
return new Proxy(src, proxy, window.document).then(documentFromHTML(src)).then(function(doc) {
|
||||
return createWindowClone(doc, document, width, height, options, 0, 0);
|
||||
});
|
||||
}
|
||||
|
||||
exports.Proxy = Proxy;
|
||||
exports.ProxyURL = ProxyURL;
|
||||
exports.loadUrlDocument = loadUrlDocument;
|
22
src/proxyimagecontainer.js
Normal file
22
src/proxyimagecontainer.js
Normal file
@@ -0,0 +1,22 @@
|
||||
var ProxyURL = require('./proxy').ProxyURL;
|
||||
var Promise = require('./promise');
|
||||
|
||||
function ProxyImageContainer(src, proxy) {
|
||||
var link = document.createElement("a");
|
||||
link.href = src;
|
||||
src = link.href;
|
||||
this.src = src;
|
||||
this.image = new Image();
|
||||
var self = this;
|
||||
this.promise = new Promise(function(resolve, reject) {
|
||||
self.image.crossOrigin = "Anonymous";
|
||||
self.image.onload = resolve;
|
||||
self.image.onerror = reject;
|
||||
|
||||
new ProxyURL(src, proxy, document).then(function(url) {
|
||||
self.image.src = url;
|
||||
})['catch'](reject);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ProxyImageContainer;
|
38
src/pseudoelementcontainer.js
Normal file
38
src/pseudoelementcontainer.js
Normal file
@@ -0,0 +1,38 @@
|
||||
var NodeContainer = require('./nodecontainer');
|
||||
|
||||
function PseudoElementContainer(node, parent, type) {
|
||||
NodeContainer.call(this, node, parent);
|
||||
this.isPseudoElement = true;
|
||||
this.before = type === ":before";
|
||||
}
|
||||
|
||||
PseudoElementContainer.prototype.cloneTo = function(stack) {
|
||||
PseudoElementContainer.prototype.cloneTo.call(this, stack);
|
||||
stack.isPseudoElement = true;
|
||||
stack.before = this.before;
|
||||
};
|
||||
|
||||
PseudoElementContainer.prototype = Object.create(NodeContainer.prototype);
|
||||
|
||||
PseudoElementContainer.prototype.appendToDOM = function() {
|
||||
if (this.before) {
|
||||
this.parent.node.insertBefore(this.node, this.parent.node.firstChild);
|
||||
} else {
|
||||
this.parent.node.appendChild(this.node);
|
||||
}
|
||||
this.parent.node.className += " " + this.getHideClass();
|
||||
};
|
||||
|
||||
PseudoElementContainer.prototype.cleanDOM = function() {
|
||||
this.node.parentNode.removeChild(this.node);
|
||||
this.parent.node.className = this.parent.node.className.replace(this.getHideClass(), "");
|
||||
};
|
||||
|
||||
PseudoElementContainer.prototype.getHideClass = function() {
|
||||
return this["PSEUDO_HIDE_ELEMENT_CLASS_" + (this.before ? "BEFORE" : "AFTER")];
|
||||
};
|
||||
|
||||
PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = "___html2canvas___pseudoelement_before";
|
||||
PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER = "___html2canvas___pseudoelement_after";
|
||||
|
||||
module.exports = PseudoElementContainer;
|
108
src/renderer.js
Normal file
108
src/renderer.js
Normal file
@@ -0,0 +1,108 @@
|
||||
var log = require('./log');
|
||||
|
||||
function Renderer(width, height, images, options, document) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.images = images;
|
||||
this.options = options;
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
Renderer.prototype.renderImage = function(container, bounds, borderData, imageContainer) {
|
||||
var paddingLeft = container.cssInt('paddingLeft'),
|
||||
paddingTop = container.cssInt('paddingTop'),
|
||||
paddingRight = container.cssInt('paddingRight'),
|
||||
paddingBottom = container.cssInt('paddingBottom'),
|
||||
borders = borderData.borders;
|
||||
|
||||
var width = bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight);
|
||||
var height = bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom);
|
||||
this.drawImage(
|
||||
imageContainer,
|
||||
0,
|
||||
0,
|
||||
imageContainer.image.width || width,
|
||||
imageContainer.image.height || height,
|
||||
bounds.left + paddingLeft + borders[3].width,
|
||||
bounds.top + paddingTop + borders[0].width,
|
||||
width,
|
||||
height
|
||||
);
|
||||
};
|
||||
|
||||
Renderer.prototype.renderBackground = function(container, bounds, borderData) {
|
||||
if (bounds.height > 0 && bounds.width > 0) {
|
||||
this.renderBackgroundColor(container, bounds);
|
||||
this.renderBackgroundImage(container, bounds, borderData);
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.prototype.renderBackgroundColor = function(container, bounds) {
|
||||
var color = container.color("backgroundColor");
|
||||
if (!color.isTransparent()) {
|
||||
this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, color);
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.prototype.renderBorders = function(borders) {
|
||||
borders.forEach(this.renderBorder, this);
|
||||
};
|
||||
|
||||
Renderer.prototype.renderBorder = function(data) {
|
||||
if (!data.color.isTransparent() && data.args !== null) {
|
||||
this.drawShape(data.args, data.color);
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.prototype.renderBackgroundImage = function(container, bounds, borderData) {
|
||||
var backgroundImages = container.parseBackgroundImages();
|
||||
backgroundImages.reverse().forEach(function(backgroundImage, index, arr) {
|
||||
switch(backgroundImage.method) {
|
||||
case "url":
|
||||
var image = this.images.get(backgroundImage.args[0]);
|
||||
if (image) {
|
||||
this.renderBackgroundRepeating(container, bounds, image, arr.length - (index+1), borderData);
|
||||
} else {
|
||||
log("Error loading background-image", backgroundImage.args[0]);
|
||||
}
|
||||
break;
|
||||
case "linear-gradient":
|
||||
case "gradient":
|
||||
var gradientImage = this.images.get(backgroundImage.value);
|
||||
if (gradientImage) {
|
||||
this.renderBackgroundGradient(gradientImage, bounds, borderData);
|
||||
} else {
|
||||
log("Error loading background-image", backgroundImage.args[0]);
|
||||
}
|
||||
break;
|
||||
case "none":
|
||||
break;
|
||||
default:
|
||||
log("Unknown background-image type", backgroundImage.args[0]);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index, borderData) {
|
||||
var size = container.parseBackgroundSize(bounds, imageContainer.image, index);
|
||||
var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size);
|
||||
var repeat = container.parseBackgroundRepeat(index);
|
||||
switch (repeat) {
|
||||
case "repeat-x":
|
||||
case "repeat no-repeat":
|
||||
this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + borderData[3], bounds.top + position.top + borderData[0], 99999, size.height, borderData);
|
||||
break;
|
||||
case "repeat-y":
|
||||
case "no-repeat repeat":
|
||||
this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + borderData[0], size.width, 99999, borderData);
|
||||
break;
|
||||
case "no-repeat":
|
||||
this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + position.top + borderData[0], size.width, size.height, borderData);
|
||||
break;
|
||||
default:
|
||||
this.renderBackgroundRepeat(imageContainer, position, size, {top: bounds.top, left: bounds.left}, borderData[3], borderData[0]);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Renderer;
|
@@ -1,138 +0,0 @@
|
||||
_html2canvas.Renderer.Canvas = function(options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var doc = document,
|
||||
safeImages = [],
|
||||
testCanvas = document.createElement("canvas"),
|
||||
testctx = testCanvas.getContext("2d"),
|
||||
canvas = options.canvas || doc.createElement('canvas');
|
||||
|
||||
|
||||
function createShape(ctx, args) {
|
||||
ctx.beginPath();
|
||||
args.forEach(function(arg) {
|
||||
ctx[arg.name].apply(ctx, arg['arguments']);
|
||||
});
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
function safeImage(item) {
|
||||
if (safeImages.indexOf(item['arguments'][0].src ) === -1) {
|
||||
testctx.drawImage(item['arguments'][0], 0, 0);
|
||||
try {
|
||||
testctx.getImageData(0, 0, 1, 1);
|
||||
} catch(e) {
|
||||
testCanvas = doc.createElement("canvas");
|
||||
testctx = testCanvas.getContext("2d");
|
||||
return false;
|
||||
}
|
||||
safeImages.push(item['arguments'][0].src);
|
||||
}
|
||||
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");
|
||||
}
|
||||
catch(e) {
|
||||
h2clog("html2canvas: Renderer: Error creating pattern", e.message);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
return function(zStack, options, doc, queue, _html2canvas) {
|
||||
|
||||
var ctx = canvas.getContext("2d"),
|
||||
storageContext,
|
||||
i,
|
||||
queueLen,
|
||||
newCanvas,
|
||||
bounds,
|
||||
fstyle;
|
||||
|
||||
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.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = fstyle;
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h2clog("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
|
||||
|
||||
queueLen = options.elements.length;
|
||||
|
||||
if (queueLen === 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;
|
||||
ctx = newCanvas.getContext("2d");
|
||||
|
||||
ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
|
||||
canvas = null;
|
||||
return newCanvas;
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
};
|
||||
};
|
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
html2canvas @VERSION@ <http://html2canvas.hertzen.com>
|
||||
Copyright (c) 2011 Niklas von Hertzen. All rights reserved.
|
||||
http://www.twitter.com/niklasvh
|
||||
|
||||
Released under MIT License
|
||||
*/
|
||||
|
||||
|
||||
// WARNING THIS file is outdated, and hasn't been tested in quite a while
|
||||
|
||||
_html2canvas.Renderer.SVG = function( options ) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var doc = document,
|
||||
svgNS = "http://www.w3.org/2000/svg",
|
||||
svg = doc.createElementNS(svgNS, "svg"),
|
||||
xlinkNS = "http://www.w3.org/1999/xlink",
|
||||
defs = doc.createElementNS(svgNS, "defs"),
|
||||
i,
|
||||
a,
|
||||
queueLen,
|
||||
storageLen,
|
||||
storageContext,
|
||||
renderItem,
|
||||
el,
|
||||
settings = {},
|
||||
text,
|
||||
fontStyle,
|
||||
clipId = 0,
|
||||
methods;
|
||||
|
||||
|
||||
methods = {
|
||||
_create: function( zStack, options, doc, queue, _html2canvas ) {
|
||||
svg.setAttribute("version", "1.1");
|
||||
svg.setAttribute("baseProfile", "full");
|
||||
|
||||
svg.setAttribute("viewBox", "0 0 " + Math.max(zStack.ctx.width, options.width) + " " + Math.max(zStack.ctx.height, options.height));
|
||||
svg.setAttribute("width", Math.max(zStack.ctx.width, options.width) + "px");
|
||||
svg.setAttribute("height", Math.max(zStack.ctx.height, options.height) + "px");
|
||||
svg.setAttribute("preserveAspectRatio", "none");
|
||||
svg.appendChild(defs);
|
||||
|
||||
|
||||
|
||||
for (i = 0, queueLen = queue.length; i < queueLen; i+=1){
|
||||
|
||||
storageContext = queue.splice(0, 1)[0];
|
||||
storageContext.canvasPosition = storageContext.canvasPosition || {};
|
||||
|
||||
//this.canvasRenderContext(storageContext,parentctx);
|
||||
|
||||
|
||||
/*
|
||||
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){
|
||||
|
||||
for (a = 0, storageLen = storageContext.ctx.storage.length; a < storageLen; a+=1){
|
||||
|
||||
renderItem = storageContext.ctx.storage[a];
|
||||
|
||||
|
||||
|
||||
switch(renderItem.type){
|
||||
case "variable":
|
||||
settings[renderItem.name] = renderItem['arguments'];
|
||||
break;
|
||||
case "function":
|
||||
if (renderItem.name === "fillRect") {
|
||||
|
||||
el = doc.createElementNS(svgNS, "rect");
|
||||
el.setAttribute("x", renderItem['arguments'][0]);
|
||||
el.setAttribute("y", renderItem['arguments'][1]);
|
||||
el.setAttribute("width", renderItem['arguments'][2]);
|
||||
el.setAttribute("height", renderItem['arguments'][3]);
|
||||
el.setAttribute("fill", settings.fillStyle);
|
||||
svg.appendChild(el);
|
||||
|
||||
} else if(renderItem.name === "fillText") {
|
||||
el = doc.createElementNS(svgNS, "text");
|
||||
|
||||
fontStyle = settings.font.split(" ");
|
||||
|
||||
el.style.fontVariant = fontStyle.splice(0, 1)[0];
|
||||
el.style.fontWeight = fontStyle.splice(0, 1)[0];
|
||||
el.style.fontStyle = fontStyle.splice(0, 1)[0];
|
||||
el.style.fontSize = fontStyle.splice(0, 1)[0];
|
||||
|
||||
el.setAttribute("x", renderItem['arguments'][1]);
|
||||
el.setAttribute("y", renderItem['arguments'][2] - (parseInt(el.style.fontSize, 10) + 3));
|
||||
|
||||
el.setAttribute("fill", settings.fillStyle);
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO get proper baseline
|
||||
el.style.dominantBaseline = "text-before-edge";
|
||||
el.style.fontFamily = fontStyle.join(" ");
|
||||
|
||||
text = doc.createTextNode(renderItem['arguments'][0]);
|
||||
el.appendChild(text);
|
||||
|
||||
|
||||
svg.appendChild(el);
|
||||
|
||||
|
||||
|
||||
} else if(renderItem.name === "drawImage") {
|
||||
|
||||
if (renderItem['arguments'][8] > 0 && renderItem['arguments'][7]){
|
||||
|
||||
// TODO check whether even any clipping is necessary for this particular image
|
||||
el = doc.createElementNS(svgNS, "clipPath");
|
||||
el.setAttribute("id", "clipId" + clipId);
|
||||
|
||||
text = doc.createElementNS(svgNS, "rect");
|
||||
text.setAttribute("x", renderItem['arguments'][5] );
|
||||
text.setAttribute("y", renderItem['arguments'][6]);
|
||||
|
||||
text.setAttribute("width", renderItem['arguments'][3]);
|
||||
text.setAttribute("height", renderItem['arguments'][4]);
|
||||
el.appendChild(text);
|
||||
defs.appendChild(el);
|
||||
|
||||
el = doc.createElementNS(svgNS, "image");
|
||||
el.setAttributeNS(xlinkNS, "xlink:href", renderItem['arguments'][0].src);
|
||||
el.setAttribute("width", renderItem['arguments'][7]);
|
||||
el.setAttribute("height", renderItem['arguments'][8]);
|
||||
el.setAttribute("x", renderItem['arguments'][5]);
|
||||
el.setAttribute("y", renderItem['arguments'][6]);
|
||||
el.setAttribute("clip-path", "url(#clipId" + clipId + ")");
|
||||
// el.setAttribute("xlink:href", );
|
||||
|
||||
|
||||
el.setAttribute("preserveAspectRatio", "none");
|
||||
|
||||
svg.appendChild(el);
|
||||
|
||||
|
||||
clipId += 1;
|
||||
/*
|
||||
ctx.drawImage(
|
||||
renderItem['arguments'][0],
|
||||
renderItem['arguments'][1],
|
||||
renderItem['arguments'][2],
|
||||
renderItem['arguments'][3],
|
||||
renderItem['arguments'][4],
|
||||
renderItem['arguments'][5],
|
||||
renderItem['arguments'][6],
|
||||
renderItem['arguments'][7],
|
||||
renderItem['arguments'][8]
|
||||
);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
if (storageContext.clip){
|
||||
ctx.restore();
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
h2clog("html2canvas: Renderer: SVG Renderer done - returning SVG DOM obj");
|
||||
|
||||
return svg;
|
||||
}
|
||||
};
|
||||
|
||||
return methods;
|
||||
|
||||
|
||||
};
|
181
src/renderers/canvas.js
Normal file
181
src/renderers/canvas.js
Normal file
@@ -0,0 +1,181 @@
|
||||
var Renderer = require('../renderer');
|
||||
var LinearGradientContainer = require('../lineargradientcontainer');
|
||||
var log = require('../log');
|
||||
|
||||
function CanvasRenderer(width, height) {
|
||||
Renderer.apply(this, arguments);
|
||||
this.canvas = this.options.canvas || this.document.createElement("canvas");
|
||||
if (!this.options.canvas) {
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
}
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
this.taintCtx = this.document.createElement("canvas").getContext("2d");
|
||||
this.ctx.textBaseline = "bottom";
|
||||
this.variables = {};
|
||||
log("Initialized CanvasRenderer with size", width, "x", height);
|
||||
}
|
||||
|
||||
CanvasRenderer.prototype = Object.create(Renderer.prototype);
|
||||
|
||||
CanvasRenderer.prototype.setFillStyle = function(fillStyle) {
|
||||
this.ctx.fillStyle = typeof(fillStyle) === "object" && !!fillStyle.isColor ? fillStyle.toString() : fillStyle;
|
||||
return this.ctx;
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.rectangle = function(left, top, width, height, color) {
|
||||
this.setFillStyle(color).fillRect(left, top, width, height);
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.circle = function(left, top, size, color) {
|
||||
this.setFillStyle(color);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(left + size / 2, top + size / 2, size / 2, 0, Math.PI*2, true);
|
||||
this.ctx.closePath();
|
||||
this.ctx.fill();
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.circleStroke = function(left, top, size, color, stroke, strokeColor) {
|
||||
this.circle(left, top, size, color);
|
||||
this.ctx.strokeStyle = strokeColor.toString();
|
||||
this.ctx.stroke();
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.drawShape = function(shape, color) {
|
||||
this.shape(shape);
|
||||
this.setFillStyle(color).fill();
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.taints = function(imageContainer) {
|
||||
if (imageContainer.tainted === null) {
|
||||
this.taintCtx.drawImage(imageContainer.image, 0, 0);
|
||||
try {
|
||||
this.taintCtx.getImageData(0, 0, 1, 1);
|
||||
imageContainer.tainted = false;
|
||||
} catch(e) {
|
||||
this.taintCtx = document.createElement("canvas").getContext("2d");
|
||||
imageContainer.tainted = true;
|
||||
}
|
||||
}
|
||||
|
||||
return imageContainer.tainted;
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx, dy, dw, dh) {
|
||||
if (!this.taints(imageContainer) || this.options.allowTaint) {
|
||||
this.ctx.drawImage(imageContainer.image, sx, sy, sw, sh, dx, dy, dw, dh);
|
||||
}
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.clip = function(shapes, callback, context) {
|
||||
this.ctx.save();
|
||||
shapes.filter(hasEntries).forEach(function(shape) {
|
||||
this.shape(shape).clip();
|
||||
}, this);
|
||||
callback.call(context);
|
||||
this.ctx.restore();
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.shape = function(shape) {
|
||||
this.ctx.beginPath();
|
||||
shape.forEach(function(point, index) {
|
||||
if (point[0] === "rect") {
|
||||
this.ctx.rect.apply(this.ctx, point.slice(1));
|
||||
} else {
|
||||
this.ctx[(index === 0) ? "moveTo" : point[0] + "To" ].apply(this.ctx, point.slice(1));
|
||||
}
|
||||
}, this);
|
||||
this.ctx.closePath();
|
||||
return this.ctx;
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.font = function(color, style, variant, weight, size, family) {
|
||||
this.setFillStyle(color).font = [style, variant, weight, size, family].join(" ").split(",")[0];
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.fontShadow = function(color, offsetX, offsetY, blur) {
|
||||
this.setVariable("shadowColor", color.toString())
|
||||
.setVariable("shadowOffsetY", offsetX)
|
||||
.setVariable("shadowOffsetX", offsetY)
|
||||
.setVariable("shadowBlur", blur);
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.clearShadow = function() {
|
||||
this.setVariable("shadowColor", "rgba(0,0,0,0)");
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.setOpacity = function(opacity) {
|
||||
this.ctx.globalAlpha = opacity;
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.setTransform = function(transform) {
|
||||
this.ctx.translate(transform.origin[0], transform.origin[1]);
|
||||
this.ctx.transform.apply(this.ctx, transform.matrix);
|
||||
this.ctx.translate(-transform.origin[0], -transform.origin[1]);
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.setVariable = function(property, value) {
|
||||
if (this.variables[property] !== value) {
|
||||
this.variables[property] = this.ctx[property] = value;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.text = function(text, left, bottom) {
|
||||
this.ctx.fillText(text, left, bottom);
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, size, bounds, left, top, width, height, borderData) {
|
||||
var shape = [
|
||||
["line", Math.round(left), Math.round(top)],
|
||||
["line", Math.round(left + width), Math.round(top)],
|
||||
["line", Math.round(left + width), Math.round(height + top)],
|
||||
["line", Math.round(left), Math.round(height + top)]
|
||||
];
|
||||
this.clip([shape], function() {
|
||||
this.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);
|
||||
}, this);
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, size, bounds, borderLeft, borderTop) {
|
||||
var offsetX = Math.round(bounds.left + backgroundPosition.left + borderLeft), offsetY = Math.round(bounds.top + backgroundPosition.top + borderTop);
|
||||
this.setFillStyle(this.ctx.createPattern(this.resizeImage(imageContainer, size), "repeat"));
|
||||
this.ctx.translate(offsetX, offsetY);
|
||||
this.ctx.fill();
|
||||
this.ctx.translate(-offsetX, -offsetY);
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.renderBackgroundGradient = function(gradientImage, bounds) {
|
||||
if (gradientImage instanceof LinearGradientContainer) {
|
||||
var gradient = this.ctx.createLinearGradient(
|
||||
bounds.left + bounds.width * gradientImage.x0,
|
||||
bounds.top + bounds.height * gradientImage.y0,
|
||||
bounds.left + bounds.width * gradientImage.x1,
|
||||
bounds.top + bounds.height * gradientImage.y1);
|
||||
gradientImage.colorStops.forEach(function(colorStop) {
|
||||
gradient.addColorStop(colorStop.stop, colorStop.color.toString());
|
||||
});
|
||||
this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, gradient);
|
||||
}
|
||||
};
|
||||
|
||||
CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {
|
||||
var image = imageContainer.image;
|
||||
if(image.width === size.width && image.height === size.height) {
|
||||
return image;
|
||||
}
|
||||
|
||||
var ctx, canvas = document.createElement('canvas');
|
||||
canvas.width = size.width;
|
||||
canvas.height = size.height;
|
||||
ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height );
|
||||
return canvas;
|
||||
};
|
||||
|
||||
function hasEntries(array) {
|
||||
return array.length > 0;
|
||||
}
|
||||
|
||||
module.exports = CanvasRenderer;
|
18
src/stackingcontext.js
Normal file
18
src/stackingcontext.js
Normal file
@@ -0,0 +1,18 @@
|
||||
var NodeContainer = require('./nodecontainer');
|
||||
|
||||
function StackingContext(hasOwnStacking, opacity, element, parent) {
|
||||
NodeContainer.call(this, element, parent);
|
||||
this.ownStacking = hasOwnStacking;
|
||||
this.contexts = [];
|
||||
this.children = [];
|
||||
this.opacity = (this.parent ? this.parent.stack.opacity : 1) * opacity;
|
||||
}
|
||||
|
||||
StackingContext.prototype = Object.create(NodeContainer.prototype);
|
||||
|
||||
StackingContext.prototype.getParentStack = function(context) {
|
||||
var parentStack = (this.parent) ? this.parent.stack : null;
|
||||
return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack;
|
||||
};
|
||||
|
||||
module.exports = StackingContext;
|
51
src/support.js
Normal file
51
src/support.js
Normal file
@@ -0,0 +1,51 @@
|
||||
function Support(document) {
|
||||
this.rangeBounds = this.testRangeBounds(document);
|
||||
this.cors = this.testCORS();
|
||||
this.svg = this.testSVG();
|
||||
}
|
||||
|
||||
Support.prototype.testRangeBounds = function(document) {
|
||||
var range, testElement, rangeBounds, rangeHeight, support = false;
|
||||
|
||||
if (document.createRange) {
|
||||
range = document.createRange();
|
||||
if (range.getBoundingClientRect) {
|
||||
testElement = document.createElement('boundtest');
|
||||
testElement.style.height = "123px";
|
||||
testElement.style.display = "block";
|
||||
document.body.appendChild(testElement);
|
||||
|
||||
range.selectNode(testElement);
|
||||
rangeBounds = range.getBoundingClientRect();
|
||||
rangeHeight = rangeBounds.height;
|
||||
|
||||
if (rangeHeight === 123) {
|
||||
support = true;
|
||||
}
|
||||
document.body.removeChild(testElement);
|
||||
}
|
||||
}
|
||||
|
||||
return support;
|
||||
};
|
||||
|
||||
Support.prototype.testCORS = function() {
|
||||
return typeof((new Image()).crossOrigin) !== "undefined";
|
||||
};
|
||||
|
||||
Support.prototype.testSVG = function() {
|
||||
var img = new Image();
|
||||
var canvas = document.createElement("canvas");
|
||||
var ctx = canvas.getContext("2d");
|
||||
img.src = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>";
|
||||
|
||||
try {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
canvas.toDataURL();
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = Support;
|
53
src/svgcontainer.js
Normal file
53
src/svgcontainer.js
Normal file
@@ -0,0 +1,53 @@
|
||||
var Promise = require('./promise');
|
||||
var XHR = require('./xhr');
|
||||
var decode64 = require('./utils').decode64;
|
||||
|
||||
function SVGContainer(src) {
|
||||
this.src = src;
|
||||
this.image = null;
|
||||
var self = this;
|
||||
|
||||
this.promise = this.hasFabric().then(function() {
|
||||
return (self.isInline(src) ? Promise.resolve(self.inlineFormatting(src)) : XHR(src));
|
||||
}).then(function(svg) {
|
||||
return new Promise(function(resolve) {
|
||||
window.html2canvas.svg.fabric.loadSVGFromString(svg, self.createCanvas.call(self, resolve));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SVGContainer.prototype.hasFabric = function() {
|
||||
return !window.html2canvas.svg || !window.html2canvas.svg.fabric ? Promise.reject(new Error("html2canvas.svg.js is not loaded, cannot render svg")) : Promise.resolve();
|
||||
};
|
||||
|
||||
SVGContainer.prototype.inlineFormatting = function(src) {
|
||||
return (/^data:image\/svg\+xml;base64,/.test(src)) ? this.decode64(this.removeContentType(src)) : this.removeContentType(src);
|
||||
};
|
||||
|
||||
SVGContainer.prototype.removeContentType = function(src) {
|
||||
return src.replace(/^data:image\/svg\+xml(;base64)?,/,'');
|
||||
};
|
||||
|
||||
SVGContainer.prototype.isInline = function(src) {
|
||||
return (/^data:image\/svg\+xml/i.test(src));
|
||||
};
|
||||
|
||||
SVGContainer.prototype.createCanvas = function(resolve) {
|
||||
var self = this;
|
||||
return function (objects, options) {
|
||||
var canvas = new window.html2canvas.svg.fabric.StaticCanvas('c');
|
||||
self.image = canvas.lowerCanvasEl;
|
||||
canvas
|
||||
.setWidth(options.width)
|
||||
.setHeight(options.height)
|
||||
.add(window.html2canvas.svg.fabric.util.groupSVGElements(objects, options))
|
||||
.renderAll();
|
||||
resolve(canvas.lowerCanvasEl);
|
||||
};
|
||||
};
|
||||
|
||||
SVGContainer.prototype.decode64 = function(str) {
|
||||
return (typeof(window.atob) === "function") ? window.atob(str) : decode64(str);
|
||||
};
|
||||
|
||||
module.exports = SVGContainer;
|
26
src/svgnodecontainer.js
Normal file
26
src/svgnodecontainer.js
Normal file
@@ -0,0 +1,26 @@
|
||||
var SVGContainer = require('./svgcontainer');
|
||||
var Promise = require('./promise');
|
||||
|
||||
function SVGNodeContainer(node, _native) {
|
||||
this.src = node;
|
||||
this.image = null;
|
||||
var self = this;
|
||||
|
||||
this.promise = _native ? new Promise(function(resolve, reject) {
|
||||
self.image = new Image();
|
||||
self.image.onload = resolve;
|
||||
self.image.onerror = reject;
|
||||
self.image.src = "data:image/svg+xml," + (new XMLSerializer()).serializeToString(node);
|
||||
if (self.image.complete === true) {
|
||||
resolve(self.image);
|
||||
}
|
||||
}) : this.hasFabric().then(function() {
|
||||
return new Promise(function(resolve) {
|
||||
window.html2canvas.svg.fabric.parseSVGDocument(node, self.createCanvas.call(self, resolve));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SVGNodeContainer.prototype = Object.create(SVGContainer.prototype);
|
||||
|
||||
module.exports = SVGNodeContainer;
|
33
src/textcontainer.js
Normal file
33
src/textcontainer.js
Normal file
@@ -0,0 +1,33 @@
|
||||
var NodeContainer = require('./nodecontainer');
|
||||
|
||||
function TextContainer(node, parent) {
|
||||
NodeContainer.call(this, node, parent);
|
||||
}
|
||||
|
||||
TextContainer.prototype = Object.create(NodeContainer.prototype);
|
||||
|
||||
TextContainer.prototype.applyTextTransform = function() {
|
||||
this.node.data = this.transform(this.parent.css("textTransform"));
|
||||
};
|
||||
|
||||
TextContainer.prototype.transform = function(transform) {
|
||||
var text = this.node.data;
|
||||
switch(transform){
|
||||
case "lowercase":
|
||||
return text.toLowerCase();
|
||||
case "capitalize":
|
||||
return text.replace(/(^|\s|:|-|\(|\))([a-z])/g, capitalize);
|
||||
case "uppercase":
|
||||
return text.toUpperCase();
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
function capitalize(m, p1, p2) {
|
||||
if (m.length > 0) {
|
||||
return p1 + p2.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextContainer;
|
169
src/utils.js
Normal file
169
src/utils.js
Normal file
@@ -0,0 +1,169 @@
|
||||
exports.smallImage = function smallImage() {
|
||||
return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
};
|
||||
|
||||
exports.bind = function(callback, context) {
|
||||
return function() {
|
||||
return callback.apply(context, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* base64-arraybuffer
|
||||
* https://github.com/niklasvh/base64-arraybuffer
|
||||
*
|
||||
* Copyright (c) 2012 Niklas von Hertzen
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
exports.decode64 = function(base64) {
|
||||
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
var len = base64.length, i, encoded1, encoded2, encoded3, encoded4, byte1, byte2, byte3;
|
||||
|
||||
var output = "";
|
||||
|
||||
for (i = 0; i < len; i+=4) {
|
||||
encoded1 = chars.indexOf(base64[i]);
|
||||
encoded2 = chars.indexOf(base64[i+1]);
|
||||
encoded3 = chars.indexOf(base64[i+2]);
|
||||
encoded4 = chars.indexOf(base64[i+3]);
|
||||
|
||||
byte1 = (encoded1 << 2) | (encoded2 >> 4);
|
||||
byte2 = ((encoded2 & 15) << 4) | (encoded3 >> 2);
|
||||
byte3 = ((encoded3 & 3) << 6) | encoded4;
|
||||
if (encoded3 === 64) {
|
||||
output += String.fromCharCode(byte1);
|
||||
} else if (encoded4 === 64 || encoded4 === -1) {
|
||||
output += String.fromCharCode(byte1, byte2);
|
||||
} else{
|
||||
output += String.fromCharCode(byte1, byte2, byte3);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
exports.getBounds = function(node) {
|
||||
if (node.getBoundingClientRect) {
|
||||
var clientRect = node.getBoundingClientRect();
|
||||
var width = node.offsetWidth == null ? clientRect.width : node.offsetWidth;
|
||||
return {
|
||||
top: clientRect.top,
|
||||
bottom: clientRect.bottom || (clientRect.top + clientRect.height),
|
||||
right: clientRect.left + width,
|
||||
left: clientRect.left,
|
||||
width: width,
|
||||
height: node.offsetHeight == null ? clientRect.height : node.offsetHeight
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
exports.offsetBounds = function(node) {
|
||||
var parent = node.offsetParent ? exports.offsetBounds(node.offsetParent) : {top: 0, left: 0};
|
||||
|
||||
return {
|
||||
top: node.offsetTop + parent.top,
|
||||
bottom: node.offsetTop + node.offsetHeight + parent.top,
|
||||
right: node.offsetLeft + parent.left + node.offsetWidth,
|
||||
left: node.offsetLeft + parent.left,
|
||||
width: node.offsetWidth,
|
||||
height: node.offsetHeight
|
||||
};
|
||||
};
|
||||
|
||||
exports.parseBackgrounds = function(backgroundImage) {
|
||||
var whitespace = ' \r\n\t',
|
||||
method, definition, prefix, prefix_i, block, results = [],
|
||||
mode = 0, numParen = 0, quote, args;
|
||||
var appendResult = function() {
|
||||
if(method) {
|
||||
if (definition.substr(0, 1) === '"') {
|
||||
definition = definition.substr(1, definition.length - 2);
|
||||
}
|
||||
if (definition) {
|
||||
args.push(definition);
|
||||
}
|
||||
if (method.substr(0, 1) === '-' && (prefix_i = method.indexOf('-', 1 ) + 1) > 0) {
|
||||
prefix = method.substr(0, prefix_i);
|
||||
method = method.substr(prefix_i);
|
||||
}
|
||||
results.push({
|
||||
prefix: prefix,
|
||||
method: method.toLowerCase(),
|
||||
value: block,
|
||||
args: args,
|
||||
image: null
|
||||
});
|
||||
}
|
||||
args = [];
|
||||
method = prefix = definition = block = '';
|
||||
};
|
||||
args = [];
|
||||
method = prefix = definition = block = '';
|
||||
backgroundImage.split("").forEach(function(c) {
|
||||
if (mode === 0 && whitespace.indexOf(c) > -1) {
|
||||
return;
|
||||
}
|
||||
switch(c) {
|
||||
case '"':
|
||||
if(!quote) {
|
||||
quote = c;
|
||||
} else if(quote === c) {
|
||||
quote = null;
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
if(quote) {
|
||||
break;
|
||||
} else if(mode === 0) {
|
||||
mode = 1;
|
||||
block += c;
|
||||
return;
|
||||
} else {
|
||||
numParen++;
|
||||
}
|
||||
break;
|
||||
case ')':
|
||||
if (quote) {
|
||||
break;
|
||||
} else if(mode === 1) {
|
||||
if(numParen === 0) {
|
||||
mode = 0;
|
||||
block += c;
|
||||
appendResult();
|
||||
return;
|
||||
} else {
|
||||
numParen--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ',':
|
||||
if (quote) {
|
||||
break;
|
||||
} else if(mode === 0) {
|
||||
appendResult();
|
||||
return;
|
||||
} else if (mode === 1) {
|
||||
if (numParen === 0 && !method.match(/^url$/i)) {
|
||||
args.push(definition);
|
||||
definition = '';
|
||||
block += c;
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
block += c;
|
||||
if (mode === 0) {
|
||||
method += c;
|
||||
} else {
|
||||
definition += c;
|
||||
}
|
||||
});
|
||||
|
||||
appendResult();
|
||||
return results;
|
||||
};
|
10
src/webkitgradientcontainer.js
Normal file
10
src/webkitgradientcontainer.js
Normal file
@@ -0,0 +1,10 @@
|
||||
var GradientContainer = require('./gradientcontainer');
|
||||
|
||||
function WebkitGradientContainer(imageData) {
|
||||
GradientContainer.apply(this, arguments);
|
||||
this.type = (imageData.args[0] === "linear") ? this.TYPES.LINEAR : this.TYPES.RADIAL;
|
||||
}
|
||||
|
||||
WebkitGradientContainer.prototype = Object.create(GradientContainer.prototype);
|
||||
|
||||
module.exports = WebkitGradientContainer;
|
24
src/xhr.js
Normal file
24
src/xhr.js
Normal file
@@ -0,0 +1,24 @@
|
||||
var Promise = require('./promise');
|
||||
|
||||
function XHR(url) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
resolve(xhr.responseText);
|
||||
} else {
|
||||
reject(new Error(xhr.statusText));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
reject(new Error("Network Error"));
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = XHR;
|
16
tests/assets/iframe/frame1.html
Normal file
16
tests/assets/iframe/frame1.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>frame 1</title>
|
||||
<style>
|
||||
body {
|
||||
background: green;
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
this is the content of frame1
|
||||
</body>
|
||||
</html>
|
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* jQuery helper plugin for examples and tests
|
||||
*/
|
||||
(function( $ ){
|
||||
$.fn.html2canvas = function(options) {
|
||||
if (options && options.profile && window.console && window.console.profile && window.location.search !== "?selenium2") {
|
||||
console.profile();
|
||||
}
|
||||
var date = new Date(),
|
||||
html2obj,
|
||||
$message = null,
|
||||
timeoutTimer = false,
|
||||
timer = date.getTime();
|
||||
options = options || {};
|
||||
|
||||
options.onrendered = options.onrendered || function( canvas ) {
|
||||
var $canvas = $(canvas),
|
||||
finishTime = new Date();
|
||||
|
||||
if (options && options.profile && window.console && window.console.profileEnd) {
|
||||
console.profileEnd();
|
||||
}
|
||||
$canvas.addClass("html2canvas")
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0
|
||||
}).appendTo(document.body);
|
||||
|
||||
if (window.location.search !== "?selenium") {
|
||||
$canvas.siblings().toggle();
|
||||
$(window).click(function(){
|
||||
$canvas.toggle().siblings().toggle();
|
||||
throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden"));
|
||||
});
|
||||
throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms<br />",4000);
|
||||
} else {
|
||||
$canvas.css('display', 'none');
|
||||
}
|
||||
// test if canvas is read-able
|
||||
try {
|
||||
$canvas[0].toDataURL();
|
||||
} catch(e) {
|
||||
if ($canvas[0].nodeName.toLowerCase() === "canvas") {
|
||||
// TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed
|
||||
alert("Canvas is tainted, unable to read data");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
html2obj = html2canvas(this, options);
|
||||
|
||||
function throwMessage(msg,duration){
|
||||
window.clearTimeout(timeoutTimer);
|
||||
timeoutTimer = window.setTimeout(function(){
|
||||
$message.fadeOut(function(){
|
||||
$message.remove();
|
||||
$message = null;
|
||||
});
|
||||
},duration || 2000);
|
||||
if ($message)
|
||||
$message.remove();
|
||||
$message = $('<div />').html(msg).css({
|
||||
margin:0,
|
||||
padding:10,
|
||||
background: "#000",
|
||||
opacity:0.7,
|
||||
position:"fixed",
|
||||
top:10,
|
||||
right:10,
|
||||
fontFamily: 'Tahoma',
|
||||
color:'#fff',
|
||||
fontSize:12,
|
||||
borderRadius:12,
|
||||
width:'auto',
|
||||
height:'auto',
|
||||
textAlign:'center',
|
||||
textDecoration:'none',
|
||||
display:'none'
|
||||
}).appendTo(document.body).fadeIn();
|
||||
html2obj.log(msg);
|
||||
}
|
||||
};
|
||||
})( jQuery );
|
@@ -41,7 +41,6 @@
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="medium">
|
||||
<div style="background:url(../../assets/image.jpg);background-clip: border-box;"></div>
|
||||
<div style="background:url(../../assets/image.jpg);background-clip: padding-box;"></div>
|
||||
@@ -49,6 +48,13 @@
|
||||
<div style="background:url(../../assets/image.jpg);"></div>
|
||||
</div>
|
||||
|
||||
<div class="medium">
|
||||
<div style="background:url(../../assets/image.jpg);background-clip: border-box; background-repeat: no-repeat;"></div>
|
||||
<div style="background:url(../../assets/image.jpg);background-clip: padding-box; background-repeat: repeat-y;"></div>
|
||||
<div style="background:url(../../assets/image.jpg);background-clip: content-box; background-repeat: repeat-x;"></div>
|
||||
<div style="background:url(../../assets/image.jpg); background-repeat: no-repeat;"></div>
|
||||
</div>
|
||||
|
||||
<div class="medium">
|
||||
<div style="background-clip: border-box;"></div>
|
||||
<div style="background-clip: padding-box;"></div>
|
||||
|
@@ -8,9 +8,6 @@
|
||||
html {
|
||||
background-color: red;
|
||||
}
|
||||
body {
|
||||
background-color: lime;
|
||||
}
|
||||
.small div{
|
||||
width:100px;
|
||||
height:100px;
|
||||
@@ -90,6 +87,31 @@
|
||||
background: linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
|
||||
}
|
||||
|
||||
.linearGradient6 {
|
||||
/* FF 3.6+ */
|
||||
background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
|
||||
/* Chrome 10+, Safari 5.1+ */
|
||||
background: -webkit-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: -o-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
|
||||
/* IE10+ */
|
||||
background: -ms-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
|
||||
/* W3C */
|
||||
background: linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
|
||||
}
|
||||
.linearGradient7 {
|
||||
background: #cce5f4;
|
||||
background: -moz-linear-gradient(top, #cce5f4 0%, #00263c 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cce5f4), color-stop(100%, #00263c));
|
||||
background: -webkit-linear-gradient(top, #cce5f4 0%, #00263c 100%);
|
||||
background: -o-linear-gradient(top, #cce5f4 0%, #00263c 100%);
|
||||
background: -ms-linear-gradient(top, #cce5f4 0%, #00263c 100%);
|
||||
background: linear-gradient(to bottom, #cce5f4 0%, #00263c 100%);
|
||||
}
|
||||
|
||||
.linearGradient8 {
|
||||
background: linear-gradient(to top left, #fff 0%, #00263c 100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
@@ -100,6 +122,9 @@
|
||||
<div class="linearGradient3"> </div>
|
||||
<div class="linearGradient4"> </div>
|
||||
<div class="linearGradient5"> </div>
|
||||
<div class="linearGradient6"> </div>
|
||||
<div class="linearGradient7"> </div>
|
||||
<div class="linearGradient8"> </div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
61
tests/cases/background/size.html
Normal file
61
tests/cases/background/size.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Background size tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
.horizontal div, .vertical div {
|
||||
display: block;
|
||||
background:url("../../assets/image.jpg") center center;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.horizontal div {
|
||||
width: 400px; height: 100px;
|
||||
}
|
||||
|
||||
.vertical div {
|
||||
width: 200px; height: 200px;
|
||||
}
|
||||
|
||||
.container {
|
||||
float: left;
|
||||
border: 1px solid black;
|
||||
width: 150px;
|
||||
height: 200px;
|
||||
background-image: url(../../assets/image.jpg);
|
||||
background-size: 34px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container"></div>
|
||||
<div class="container" style="background-repeat: repeat-y"></div>
|
||||
<div class="container" style="background-repeat: repeat-x"></div>
|
||||
<div class="container" style="background-size: 150% auto"></div>
|
||||
<div class="horizontal">
|
||||
<div style='background-size: cover;'></div>
|
||||
<div style='background-size: contain;'></div>
|
||||
<div style='background-size: auto 100%;'></div>
|
||||
<div style='background-size: auto;'></div>
|
||||
</div>
|
||||
|
||||
<div class="vertical">
|
||||
<div style='background-size: cover;'></div>
|
||||
<div style='background-size: contain;'></div>
|
||||
<div style='background-size: auto 100%;'></div>
|
||||
<div style='background-size: auto;'></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
58
tests/cases/border/inset.html
Normal file
58
tests/cases/border/inset.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Borders tests</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: 200px;
|
||||
height: 200px;
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
background:#6F428C;
|
||||
border-style: inset;
|
||||
}
|
||||
|
||||
.box1 {
|
||||
border-width: 1px;
|
||||
border-color: #00b5e2;
|
||||
}
|
||||
|
||||
.box2 {
|
||||
border-width: 3px;
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.box3 {
|
||||
border-width: 10px;
|
||||
}
|
||||
|
||||
.box4 {
|
||||
border-width: 50px;
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
.box5 {
|
||||
border-width: 50px;
|
||||
border-color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
input {
|
||||
border-width: 50px;
|
||||
}
|
||||
|
||||
html {
|
||||
background: #3a84c3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box1"> </div>
|
||||
<div class="box2"> </div>
|
||||
<div class="box3"> </div>
|
||||
<div class="box4"> </div>
|
||||
<div class="box5"> </div>
|
||||
<input type="text" />
|
||||
</body>
|
||||
</html>
|
@@ -46,15 +46,28 @@
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.box5 {
|
||||
margin: 100px;
|
||||
border-width: 50px;
|
||||
border-left-color: transparent;
|
||||
border-top-color: red;
|
||||
border-top-width: 10px;
|
||||
border-right-color: green;
|
||||
border-radius: 25px;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
html {
|
||||
background: #3a84c3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="box1"> </div>
|
||||
<div class="box2"> </div>
|
||||
<div class="box3"> </div>
|
||||
<div class="box4"> </div>
|
||||
<div class="box5"> </div>
|
||||
</body>
|
||||
</html>
|
||||
|
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>
|
43
tests/cases/clip.html
Normal file
43
tests/cases/clip.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!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;
|
||||
}
|
||||
div {
|
||||
background: red;
|
||||
border: 5px solid blue;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div style="clip: rect(0px, 400px, 50px, 200px); ">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.</div>
|
||||
|
||||
<div style="clip: rect(0px, 400px, 50px, 200px); position: relative; ">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.</div>
|
||||
|
||||
<div style="clip: rect(0px, 400px, 50px, 200px); position: fixed; ">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.</div>
|
||||
|
||||
<div style="clip: rect(0px, 400px, 50px, 200px); position: absolute; top: 250px; left: 500px;">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.</div>
|
||||
</body>
|
||||
|
||||
<div style="clip: rect(0px, 400px, 50px, 200px); position: absolute; top: 500px;">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.</div>
|
||||
</body>
|
||||
</html>
|
13
tests/cases/crossorigin-iframe.html
Normal file
13
tests/cases/crossorigin-iframe.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>cross-origin iframe test</title>
|
||||
<script>
|
||||
var h2cOptions = {proxy: "http://localhost:8082"};
|
||||
</script>
|
||||
<script type="text/javascript" src="../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="http://hertzen.com/" width="800" height="800"></iframe>
|
||||
</body>
|
||||
</html>
|
@@ -5,12 +5,17 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../test.js"></script>
|
||||
<style>
|
||||
|
||||
input[type="radio"], input[type="checkbox"] {
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<input type="hidden" value="THIS SHOULD NOT BE VISIBLE!" />
|
||||
<input type="text" value="textbox" />
|
||||
<input type="password" value="textbox" />
|
||||
<input type="text" value="textbox" style="border:5px solid navy;" />
|
||||
<input type="text" value="textbox" style="border:5px solid navy;height:40px;" />
|
||||
|
||||
@@ -54,5 +59,23 @@
|
||||
|
||||
<textarea> text </textarea>
|
||||
<textarea style="border-width:10px;">text</textarea>
|
||||
<hr />
|
||||
<input type="radio" value="radio1" />
|
||||
<input type="radio" value="radio2" style="transform:scale(3)" />
|
||||
<input type="RADIO" value="radio3" checked />
|
||||
<input type="radio" value="radio4" style="transform:scale(3)" checked />
|
||||
<input type="radio" value="radio5" style="transform:scale(3); background: red;" />
|
||||
<input type="radio" value="radio6" style="width: 200px;" />
|
||||
<input type="radio" value="radio6" style="width: 200px; height: 200px;" />
|
||||
<input type="radio" value="radio6" style="width: 200px; height: 200px;" checked />
|
||||
<input type="checkbox" value="checkbox1" />
|
||||
<input type="chECKbox" value="checkbox2" style="transform:scale(3)" />
|
||||
<input type="checkbox" value="checkbox3" checked />
|
||||
<input type="checkbox" value="checkbox4" style="transform:scale(3)" checked />
|
||||
<input type="checkbox" value="checkbox5" style="transform:scale(3); background: red;" />
|
||||
<input type="checkbox" value="checkbox6" style="width: 200px;" />
|
||||
<input type="checkbox" value="checkbox6" style="width: 200px; height: 200px;" />
|
||||
<input type="checkbox" value="checkbox6" style="width: 200px; height: 200px;" checked />
|
||||
<div style="position: absolute; width: 10px; height: 10px; border:1px solid rgb(165,165,165); border-radius: 3px; background: rgb(222,222,222)"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
41
tests/cases/google-maps.html
Normal file
41
tests/cases/google-maps.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>google maps</title>
|
||||
<script>
|
||||
var dontRun = true;
|
||||
</script>
|
||||
<script type="text/javascript" src="../test.js"></script>
|
||||
<script src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>
|
||||
<style>
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
body, html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h2>Google maps</h2>
|
||||
<div id="map"></div>
|
||||
<script>
|
||||
var mapOptions = { zoom: 13, center: new google.maps.LatLng(60.1656623, 24.9477731) };
|
||||
var map = new google.maps.Map(document.querySelector('#map'), mapOptions);
|
||||
google.maps.event.addListenerOnce(map, 'idle', function(){
|
||||
if (typeof(window.run) === "function") {
|
||||
window.run();
|
||||
} else {
|
||||
dontRun = undefined;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</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="/tests/assets/iframe/frame1.html" width="500" height="500"></iframe>
|
||||
</body>
|
||||
</html>
|
17
tests/cases/images/base.html
Normal file
17
tests/cases/images/base.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>External content tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
|
||||
<base href="http://www.google.com/" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>External image</h1>
|
||||
<img src="http://www.google.com/logos/2011/gregormendel11-hp.jpg" style="border:5px solid black;" />
|
||||
|
||||
<h1>External image (using <base> href)</h1>
|
||||
<img src="/logos/2011/gregormendel11-res.jpg" />
|
||||
</body>
|
||||
</html>
|
@@ -3,19 +3,13 @@
|
||||
<head>
|
||||
<title>External content tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script>
|
||||
h2cOptions = {useCORS: true, proxy: null};
|
||||
</script>
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
|
||||
<base href="http://www.google.com/" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>External image</h1>
|
||||
<img src="http://www.google.com/logos/2011/gregormendel11-hp.jpg" style="border:5px solid black;" />
|
||||
|
||||
<h1>External image (using <base> href)</h1>
|
||||
<img src="/logos/2011/gregormendel11-res.jpg" />
|
||||
|
||||
<h1>External image (CORS)</h1>
|
||||
<img src="http://publishmydata.com/assets/home/blue_bg.png" />
|
||||
|
||||
<img src="http://localhost:8081/tests/assets/image2.jpg" />
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -8,6 +8,9 @@
|
||||
<body>
|
||||
Image without src attribute, should not crash:
|
||||
<img style="width:50px;height:50px;border:1px solid red;display:block;" />
|
||||
Image with broken src attribute, should not crash:
|
||||
<img src="404" style="width:50px;height:50px;border:1px solid red;display:block;" />
|
||||
<img src="404_2" style="width:50px;height:50px;border:1px solid red;display:block;" />
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -9,6 +9,7 @@
|
||||
<img src="../../assets/image.jpg" />
|
||||
<img src="../../assets/image.jpg" style="width:50px;height:400px;" />
|
||||
<img src="../../assets/image.jpg" style="width:500px;" />
|
||||
<img src="../../assets/image.jpg" style="width:100px;border-radius:50px;" />
|
||||
<img src="../../assets/image.jpg" style="width:500px;height:40px;" />
|
||||
|
||||
|
||||
|
13
tests/cases/images/svg/base64.html
Normal file
13
tests/cases/images/svg/base64.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Base64 svg</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
Inline svg image: <br />
|
||||
<img width="200" height="200" src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjMwNiIgaGVpZ2h0PSIyOTYiPjxkZWZzIGlkPSJkZWZzNCIgLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYyLjQ2OTk1LC00NzcuMjg2MykiIGlkPSJsYXllcjEiPjxwYXRoIGQ9Im0gMzE0LjE1NzQ1LDQ4MS42OTU1OCBjIC01OS4yMDA4OSwwLjUzNzc0IC0xMTQuODA5NzksMzYuNzIyMTkgLTEzNy4zMTI1LDk1LjM0Mzc1IC0yOS4zOTEyOSw3Ni41NjY5MyA4LjgzOTMyLDE2Mi40NTI0NiA4NS40MDYyNSwxOTEuODQzNzUgbCAzNC4wMzEyNSwtODguNjg3NSBjIC0yMC4wNjc4LC03LjcxMzU4IC0zNC4zMTI1LC0yNy4xNTMyNCAtMzQuMzEyNSwtNDkuOTM3NSAwLC0yOS41NDcyMyAyMy45NTI3NywtNTMuNSA1My41LC01My41IDI5LjU0NzIzLDAgNTMuNSwyMy45NTI3NyA1My41LDUzLjUgMCwyMi43ODQyNiAtMTQuMjQ0Nyw0Mi4yMjM5MiAtMzQuMzEyNSw0OS45Mzc1IGwgMzQuMDMxMjUsODguNjg3NSBjIDM5LjI5MDg1LC0xNS4wODIzNCA3MC4zMjM5LC00Ni4xMTU0IDg1LjQwNjI1LC04NS40MDYyNSAyOS4zOTEyOSwtNzYuNTY2OTMgLTguODM5MzIsLTE2Mi40ODM3MSAtODUuNDA2MjUsLTE5MS44NzUgLTE3Ljk0NTM3LC02Ljg4ODU5IC0zNi40MDg1MywtMTAuMDcwODcgLTU0LjUzMTI1LC05LjkwNjI1IHoiIGlkPSJwYXRoMjgzMCIgc3R5bGU9ImZpbGw6IzQwYWE1NDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzIwNTUyYTtzdHJva2Utd2lkdGg6Ny45OTk5OTk1MjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lIiAvPjwvZz48L3N2Zz4=" /></div>
|
||||
</body>
|
||||
</html>
|
@@ -3,10 +3,10 @@
|
||||
<head>
|
||||
<title>Image tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<script type="text/javascript" src="../../../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
SVG taints image:<br /> <!-- http://fi.wikipedia.org/wiki/Tiedosto:Svg.svg -->
|
||||
<img src="../../assets/image.svg" />
|
||||
<img src="../../../assets/image.svg" />
|
||||
</body>
|
||||
</html>
|
14
tests/cases/images/svg/inline.html
Normal file
14
tests/cases/images/svg/inline.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Inline svg</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
Inline svg image: <br />
|
||||
<img width="200" height="200" src='data:image/svg+xml,<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="296"><defs id="defs4" /><g transform="translate(-162.46995,-477.2863)" id="layer1"><path d="m 314.15745,481.69558 c -59.20089,0.53774 -114.80979,36.72219 -137.3125,95.34375 -29.39129,76.56693 8.83932,162.45246 85.40625,191.84375 l 34.03125,-88.6875 c -20.0678,-7.71358 -34.3125,-27.15324 -34.3125,-49.9375 0,-29.54723 23.95277,-53.5 53.5,-53.5 29.54723,0 53.5,23.95277 53.5,53.5 0,22.78426 -14.2447,42.22392 -34.3125,49.9375 l 34.03125,88.6875 c 39.29085,-15.08234 70.3239,-46.1154 85.40625,-85.40625 29.39129,-76.56693 -8.83932,-162.48371 -85.40625,-191.875 -17.94537,-6.88859 -36.40853,-10.07087 -54.53125,-9.90625 z" id="path2830" style="fill:#40aa54;fill-opacity:1;stroke:#20552a;stroke-width:7.99999952;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></svg>' />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
25
tests/cases/images/svg/native_only.html
Normal file
25
tests/cases/images/svg/native_only.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Native svg only</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script>
|
||||
var noFabric = true;
|
||||
</script>
|
||||
<script type="text/javascript" src="../../../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<img src="../../../assets/image.svg" />
|
||||
<img width="200" height="200" src='data:image/svg+xml,<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="296"><defs id="defs4" /><g transform="translate(-162.46995,-477.2863)" id="layer1"><path d="m 314.15745,481.69558 c -59.20089,0.53774 -114.80979,36.72219 -137.3125,95.34375 -29.39129,76.56693 8.83932,162.45246 85.40625,191.84375 l 34.03125,-88.6875 c -20.0678,-7.71358 -34.3125,-27.15324 -34.3125,-49.9375 0,-29.54723 23.95277,-53.5 53.5,-53.5 29.54723,0 53.5,23.95277 53.5,53.5 0,22.78426 -14.2447,42.22392 -34.3125,49.9375 l 34.03125,88.6875 c 39.29085,-15.08234 70.3239,-46.1154 85.40625,-85.40625 29.39129,-76.56693 -8.83932,-162.48371 -85.40625,-191.875 -17.94537,-6.88859 -36.40853,-10.07087 -54.53125,-9.90625 z" id="path2830" style="fill:#40aa54;fill-opacity:1;stroke:#20552a;stroke-width:7.99999952;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></svg>' />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="296" id="svg2">
|
||||
<defs id="defs4"/>
|
||||
<g transform="translate(-162.46995,-477.2863)" id="layer1">
|
||||
<path d="m 314.15745,481.69558 c -59.20089,0.53774 -114.80979,36.72219 -137.3125,95.34375 -29.39129,76.56693 8.83932,162.45246 85.40625,191.84375 l 34.03125,-88.6875 c -20.0678,-7.71358 -34.3125,-27.15324 -34.3125,-49.9375 0,-29.54723 23.95277,-53.5 53.5,-53.5 29.54723,0 53.5,23.95277 53.5,53.5 0,22.78426 -14.2447,42.22392 -34.3125,49.9375 l 34.03125,88.6875 c 39.29085,-15.08234 70.3239,-46.1154 85.40625,-85.40625 29.39129,-76.56693 -8.83932,-162.48371 -85.40625,-191.875 -17.94537,-6.88859 -36.40853,-10.07087 -54.53125,-9.90625 z"
|
||||
id="path2830"
|
||||
style="fill:#40aa54;fill-opacity:1;stroke:#20552a;stroke-width:7.99999952;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
|
||||
</g>
|
||||
</svg>
|
||||
<img width="200" height="200" src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjMwNiIgaGVpZ2h0PSIyOTYiPjxkZWZzIGlkPSJkZWZzNCIgLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYyLjQ2OTk1LC00NzcuMjg2MykiIGlkPSJsYXllcjEiPjxwYXRoIGQ9Im0gMzE0LjE1NzQ1LDQ4MS42OTU1OCBjIC01OS4yMDA4OSwwLjUzNzc0IC0xMTQuODA5NzksMzYuNzIyMTkgLTEzNy4zMTI1LDk1LjM0Mzc1IC0yOS4zOTEyOSw3Ni41NjY5MyA4LjgzOTMyLDE2Mi40NTI0NiA4NS40MDYyNSwxOTEuODQzNzUgbCAzNC4wMzEyNSwtODguNjg3NSBjIC0yMC4wNjc4LC03LjcxMzU4IC0zNC4zMTI1LC0yNy4xNTMyNCAtMzQuMzEyNSwtNDkuOTM3NSAwLC0yOS41NDcyMyAyMy45NTI3NywtNTMuNSA1My41LC01My41IDI5LjU0NzIzLDAgNTMuNSwyMy45NTI3NyA1My41LDUzLjUgMCwyMi43ODQyNiAtMTQuMjQ0Nyw0Mi4yMjM5MiAtMzQuMzEyNSw0OS45Mzc1IGwgMzQuMDMxMjUsODguNjg3NSBjIDM5LjI5MDg1LC0xNS4wODIzNCA3MC4zMjM5LC00Ni4xMTU0IDg1LjQwNjI1LC04NS40MDYyNSAyOS4zOTEyOSwtNzYuNTY2OTMgLTguODM5MzIsLTE2Mi40ODM3MSAtODUuNDA2MjUsLTE5MS44NzUgLTE3Ljk0NTM3LC02Ljg4ODU5IC0zNi40MDg1MywtMTAuMDcwODcgLTU0LjUzMTI1LC05LjkwNjI1IHoiIGlkPSJwYXRoMjgzMCIgc3R5bGU9ImZpbGw6IzQwYWE1NDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzIwNTUyYTtzdHJva2Utd2lkdGg6Ny45OTk5OTk1MjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lIiAvPjwvZz48L3N2Zz4=" /></div>
|
||||
</body>
|
||||
</html>
|
20
tests/cases/images/svg/node.html
Normal file
20
tests/cases/images/svg/node.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SVG node</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../../test.js"></script>
|
||||
</head>
|
||||
<body><div>
|
||||
SVG node image: <br/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="296" id="svg2">
|
||||
<defs id="defs4"/>
|
||||
<g transform="translate(-162.46995,-477.2863)" id="layer1">
|
||||
<path d="m 314.15745,481.69558 c -59.20089,0.53774 -114.80979,36.72219 -137.3125,95.34375 -29.39129,76.56693 8.83932,162.45246 85.40625,191.84375 l 34.03125,-88.6875 c -20.0678,-7.71358 -34.3125,-27.15324 -34.3125,-49.9375 0,-29.54723 23.95277,-53.5 53.5,-53.5 29.54723,0 53.5,23.95277 53.5,53.5 0,22.78426 -14.2447,42.22392 -34.3125,49.9375 l 34.03125,88.6875 c 39.29085,-15.08234 70.3239,-46.1154 85.40625,-85.40625 29.39129,-76.56693 -8.83932,-162.48371 -85.40625,-191.875 -17.94537,-6.88859 -36.40853,-10.07087 -54.53125,-9.90625 z"
|
||||
id="path2830"
|
||||
style="fill:#40aa54;fill-opacity:1;stroke:#20552a;stroke-width:7.99999952;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -37,7 +37,7 @@
|
||||
<div style="overflow:hidden;">
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
|
||||
|
||||
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
|
||||
|
||||
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
|
||||
|
@@ -5,6 +5,10 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../test.js"></script>
|
||||
<style>
|
||||
:root .text *::before {
|
||||
content:" root before!";
|
||||
}
|
||||
|
||||
.text *::before {
|
||||
content:" before!";
|
||||
}
|
||||
@@ -23,6 +27,7 @@
|
||||
|
||||
.background *::before{
|
||||
background: url(../assets/image_1.jpg);
|
||||
border: 5px solid red;
|
||||
}
|
||||
|
||||
.background *::after{
|
||||
|
@@ -4,11 +4,6 @@
|
||||
<title>Chinese text</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<script>
|
||||
h2cOptions = {
|
||||
chinese: true
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.chn-text-block {
|
||||
width: 500px;
|
||||
@@ -38,4 +33,4 @@
|
||||
</p><p> 〔14〕 见鲁迅《集外集·自嘲》(《鲁迅全集》第7卷,人民文学出版社1981年版,第147页)。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
46
tests/cases/text/fontawesome.html
Normal file
46
tests/cases/text/fontawesome.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>fontawesome icons</title>
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
Fontawesome icons
|
||||
<hr />
|
||||
<i class="fa fa-camera-retro fa-5x"></i> fa-5x
|
||||
|
||||
<ul class="fa-ul">
|
||||
<li><i class="fa-li fa fa-check-square"></i>List icons</li>
|
||||
<li><i class="fa-li fa fa-check-square"></i>can be used</li>
|
||||
<li><i class="fa-li fa fa-spinner fa-spin"></i>as bullets</li>
|
||||
<li><i class="fa-li fa fa-square"></i>in lists</li>
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<span class="fa-stack fa-lg">
|
||||
<i class="fa fa-square-o fa-stack-2x"></i>
|
||||
<i class="fa fa-twitter fa-stack-1x"></i>
|
||||
</span>
|
||||
fa-twitter on fa-square-o<br>
|
||||
<span class="fa-stack fa-lg">
|
||||
<i class="fa fa-circle fa-stack-2x"></i>
|
||||
<i class="fa fa-flag fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
fa-flag on fa-circle<br>
|
||||
<span class="fa-stack fa-lg">
|
||||
<i class="fa fa-square fa-stack-2x"></i>
|
||||
<i class="fa fa-terminal fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
fa-terminal on fa-square<br>
|
||||
<span class="fa-stack fa-lg">
|
||||
<i class="fa fa-camera fa-stack-1x"></i>
|
||||
<i class="fa fa-ban fa-stack-2x text-danger"></i>
|
||||
</span>
|
||||
fa-ban on fa-camera
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
43
tests/cases/text/shadow.html
Normal file
43
tests/cases/text/shadow.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!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>
|
||||
.shadow1 span{
|
||||
text-shadow: 1px 1px 3px #888;
|
||||
}
|
||||
.shadow1 strong {
|
||||
text-shadow: 1px 1px 2px black, 0 0 1em blue, 0 0 0.2em blue;
|
||||
}
|
||||
|
||||
.shadow2 {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.shadow2 span{
|
||||
color: transparent;
|
||||
text-shadow: 0 0 5px #00f, 2px 2px 0 #f00;
|
||||
}
|
||||
|
||||
.shadow2 strong {
|
||||
color: rgba(0, 255, 0, 0.5);
|
||||
text-shadow: 0 0 5px #00f, 2px 2px 0 #f00;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="shadow1">
|
||||
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
|
||||
</div>
|
||||
<div class="shadow2">
|
||||
<span>testing with transparent</span>
|
||||
<strong>testing with low opacity</strong>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
45
tests/cases/transform/nested.html
Normal file
45
tests/cases/transform/nested.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!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: 15px 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+ */
|
||||
|
||||
}
|
||||
#fourth {
|
||||
background: #bc8f8f;
|
||||
}
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
</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>
|
||||
<div id="fourth">something else</div>
|
||||
</body>
|
||||
</html>
|
54
tests/cases/transform/rotate.html
Normal file
54
tests/cases/transform/rotate.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Rotation transform tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
.image1 {
|
||||
position: absolute;
|
||||
left: 100px;
|
||||
-webkit-transform: rotate(-45deg); /* Chrome, Safari 3.1+ */
|
||||
-moz-transform: rotate(-45deg); /* Firefox 3.5-15 */
|
||||
-ms-transform: rotate(-45deg); /* IE 9 */
|
||||
-o-transform: rotate(-45deg); /* Opera 10.50-12.00 */
|
||||
transform:rotate(-45deg);
|
||||
}
|
||||
.image2 {
|
||||
position: absolute;
|
||||
left: 634px;
|
||||
-webkit-transform: rotate(90deg); /* Chrome, Safari 3.1+ */
|
||||
-moz-transform: rotate(90deg); /* Firefox 3.5-15 */
|
||||
-ms-transform: rotate(90deg); /* IE 9 */
|
||||
-o-transform: rotate(90deg); /* Opera 10.50-12.00 */
|
||||
transform:rotate(90deg);
|
||||
}
|
||||
.image3 {
|
||||
position: absolute;
|
||||
top: 250px;
|
||||
left: 100px;
|
||||
-webkit-transform: rotate(45deg); /* Chrome, Safari 3.1+ */
|
||||
-moz-transform: rotate(45deg); /* Firefox 3.5-15 */
|
||||
-ms-transform: rotate(45deg); /* IE 9 */
|
||||
-o-transform: rotate(45deg); /* Opera 10.50-12.00 */
|
||||
transform:rotate(45deg);
|
||||
}
|
||||
</style
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="image1">
|
||||
<img src="../../assets/image.jpg" style="width: 200px; height: 200px;">
|
||||
</div>
|
||||
<div class="image2">
|
||||
<img src="../../assets/image2.jpg" style="width: 50px; height: 200px;">
|
||||
</div>
|
||||
<div class="image3">
|
||||
<img src="../../assets/image.jpg" style="width: 100px; height: 200px;">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
45
tests/cases/transform/translate.html
Normal file
45
tests/cases/transform/translate.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!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: translate(125px); /* Chrome, Safari 3.1+ */
|
||||
-moz-transform: translate(125px); /* Firefox 3.5-15 */
|
||||
-ms-transform: translate(125px); /* IE 9 */
|
||||
-o-transform: translate(125px); /* Opera 10.50-12.00 */
|
||||
transform: translate(125px);
|
||||
}
|
||||
#third {
|
||||
background: cadetblue;
|
||||
-webkit-transform: translate(-100px, -25px); /* Chrome, Safari 3.1+ */
|
||||
-moz-transform: translate(100px, -25px); /* Firefox 3.5-15 */
|
||||
-ms-transform: translate(100px, -25px); /* IE 9 */
|
||||
-o-transform: translate(100px, -25px); /* Opera 10.50-12.00 */
|
||||
transform: translate(100px, -25px);
|
||||
-webkit-transform-origin: 100px 50px;
|
||||
-moz-transform-origin: 100px 50px;
|
||||
-ms-transform-origin: 100px 50px;
|
||||
-o-transform-origin: 100px 50px;
|
||||
transform-origin: 100px 50px;
|
||||
|
||||
}
|
||||
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>
|
36
tests/cases/zindex/z-index16.html
Normal file
36
tests/cases/zindex/z-index16.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Z-order positioning</TITLE>
|
||||
<STYLE type="text/css">
|
||||
.pile {
|
||||
position: absolute;
|
||||
left: 2in;
|
||||
top: 2in;
|
||||
width: 3in;
|
||||
height: 3in;
|
||||
}
|
||||
</STYLE>
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<P>
|
||||
<IMG id="image" class="pile"
|
||||
src="../../assets/image.jpg" alt="A butterfly image"
|
||||
style="z-index: 1">
|
||||
|
||||
<DIV id="text1" class="pile"
|
||||
style="z-index: 3">
|
||||
This text will overlay the butterfly image.
|
||||
</DIV>
|
||||
|
||||
<DIV id="text2">
|
||||
This text will be beneath everything.
|
||||
</DIV>
|
||||
|
||||
<DIV id="text3" class="pile"
|
||||
style="z-index: 2">
|
||||
This text will underlay text1, but overlay the butterfly image
|
||||
</DIV>
|
||||
</BODY>
|
||||
</HTML>
|
25
tests/cases/zindex/z-index17.html
Normal file
25
tests/cases/zindex/z-index17.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>z-index17</title>
|
||||
<style>
|
||||
.z {
|
||||
background: darkolivegreen;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 100px;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
}
|
||||
body {
|
||||
background: violet;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="z">fixed z-index 10</div>
|
||||
</body>
|
||||
</html>
|
79
tests/cases/zindex/z-index18.html
Normal file
79
tests/cases/zindex/z-index18.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>z-index18</title>
|
||||
<style>
|
||||
body {
|
||||
background: violet;
|
||||
}
|
||||
|
||||
#container {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.base {
|
||||
background: green;
|
||||
}
|
||||
|
||||
#container div {
|
||||
width: 800px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.div1 {
|
||||
background: red;
|
||||
}
|
||||
|
||||
#container .div2 {
|
||||
width: 400px;
|
||||
background: blue;
|
||||
}
|
||||
|
||||
#container .div3 {
|
||||
background: orange;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
#container .highlight1 {
|
||||
background: purple;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
#container .highlight2 {
|
||||
background: pink;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
#container .highlight3 {
|
||||
background: navy;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
#container .last {
|
||||
background: brown;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" class="jqplot-target" style="position: relative; height: 300px;">
|
||||
<div class="base" style="position: absolute; left: 0px; top: 0px;">a</div>
|
||||
<div class="base" style="position: absolute; left: 0px; top: 0px;">b</div>
|
||||
<div style="position: absolute; left: 16px; top: 10px;">c</div>
|
||||
<div style="position: absolute; left: 16px; top: 10px;">d</div>
|
||||
<div style="position: absolute; left: 16px; top: 10px;">e</div>
|
||||
<div class="div1" style="position: absolute; left: 16px; top: 10px;">f</div>
|
||||
<div class="div2" style="position: absolute; left: 16px; top: 10px;">g</div>
|
||||
<div class="div3" style="position: absolute; left: 16px; top: 10px;">h</div>
|
||||
<div class="highlight1" style="position: absolute; right: 16px; top: 10px;">i</div>
|
||||
<div class="highlight2" style="position: absolute; left: 16px; top: 60px;">j</div>
|
||||
<div class="highlight3" style="position: absolute; left: 56px; top: 90px;">k</div>
|
||||
<div class="last" style="position: absolute; left: 16px; top: 10px;">l</div>
|
||||
</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>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user