Compare commits

...

201 Commits

Author SHA1 Message Date
Niklas von Hertzen
9a0d43d60b Bump v0.5.0-alpha2 2015-02-03 22:11:20 +02:00
Niklas von Hertzen
3671de81f9 Fix module name casing 2015-02-03 21:39:57 +02:00
Niklas von Hertzen
f3b6df267e Switch build to use browserify (#502) 2015-02-03 21:34:05 +02:00
Niklas von Hertzen
60619dca72 Fix #517 2015-01-26 22:55:10 +02:00
Niklas von Hertzen
ed299b3db1 Remove dead code 2015-01-20 19:51:52 +02:00
Niklas von Hertzen
c7e484af89 Add npm bower postpublish script 2015-01-19 23:52:50 +02:00
Niklas von Hertzen
19ecd8bd7f Remove version from bower.json 2015-01-19 23:39:18 +02:00
Niklas von Hertzen
edb113c230 Make html2canvas requireable in nodejs 2015-01-19 23:33:29 +02:00
Niklas von Hertzen
33cd2c5ef6 Move Changelog away from Readme 2015-01-19 22:52:04 +02:00
Niklas von Hertzen
3400354d78 Remove old test results 2015-01-19 22:51:45 +02:00
Niklas von Hertzen
fc01263f68 Add comment regarding IE9 clone node 2015-01-19 22:30:10 +02:00
Niklas von Hertzen
399ae9f33d Fix #503 2015-01-19 22:28:10 +02:00
Niklas von Hertzen
db88784655 Add test for adding class prior to rendering (#508) 2015-01-19 20:31:31 +02:00
Niklas von Hertzen
77c73c478f Fix iframe proxy 2015-01-18 21:38:05 +02:00
Niklas von Hertzen
37b39ec716 Add task for running mocha tests with webdriver for testing proxies 2015-01-18 14:31:53 +02:00
Niklas von Hertzen
6d53461a68 Update bluebird to 2.7.1 2015-01-17 22:12:48 +02:00
Niklas von Hertzen
33844129e9 Update .gitignore 2015-01-17 22:12:28 +02:00
Niklas von Hertzen
0df71b34f3 Add travis notification hook for gitter 2015-01-13 23:18:58 +02:00
Niklas von Hertzen
3996449e88 Add gitter badge to README 2015-01-13 23:13:00 +02:00
Niklas von Hertzen
2699670bf1 Improve text-shadow tests 2015-01-13 22:50:57 +02:00
Niklas von Hertzen
7a58ad019f Fix background rendering regression #496 2015-01-10 20:53:06 +02:00
Niklas von Hertzen
9b372a4399 Correctly clone <select> and <textarea> value property 2015-01-05 22:58:36 +02:00
Niklas von Hertzen
498527918c Correctly render input[type="password"] value 2015-01-05 21:36:53 +02:00
Niklas von Hertzen
e363f3813e Rename mocha/index.html to mocha/parsing.html 2015-01-05 21:22:58 +02:00
Niklas von Hertzen
433dc98177 Reduce render count for multiple render mocha test 2015-01-05 20:54:07 +02:00
Niklas von Hertzen
b6a73b635a Merge pull request #495 from usmonster/update-promise-polyfill
Temporarily add manual update to es6-promise polyfill
2014-12-29 21:35:22 +02:00
Usman Akeju
e2713cd6f9 Temporarily add manual update to es6-promise polyfill
This replaces an old and outdated Promise polyfill that was included
with previous versions of html2canvas, and which consistently overwrote
native browser implementations when it shouldn't have. It is essentially
a (slightly modified) paste of the most current minified version of
es6-promise, found here:
https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js

The plan is to eventually include this polyfill as a submodule
dependency, but the build process for es6-promise is a bit
cumbersome/opaque/painful at the moment, so this manual update was an
easier interim solution.

Please note that I had to manually add the actual call to
`ES6Promise.polyfill()` to load the polyfill--this will become
unnecessarry in future versions of es6-promise. Note also that I had to
manually pass `window` to the polyfill's IIFE, since `this` is an empty
object in the current scope.

Ref niklasvh/html2canvas#494.
2014-12-29 16:35:39 +01:00
Niklas von Hertzen
4b771d558a Increase mocha timeout for multiple-render tests 2014-12-24 17:45:04 +02:00
Niklas von Hertzen
2f5f9f6e59 Allow multiple renders to occur simultaneously 2014-12-24 17:34:31 +02:00
Niklas von Hertzen
60368d4670 Update build instructions with grunt and uglifyjs details 2014-12-24 17:24:07 +02:00
Niklas von Hertzen
f5e318d968 Manually call toString for colors 2014-12-13 19:23:00 +02:00
Niklas von Hertzen
612e59c3d3 Implement border-inset color transforms 2014-12-13 19:00:29 +02:00
Niklas von Hertzen
fcbcb9bfaa Add color object to accept array of rgb(a) 2014-12-13 18:30:52 +02:00
Niklas von Hertzen
3b0352a3d7 Use Color objects for colors 2014-12-13 18:10:41 +02:00
Niklas von Hertzen
313c227a1f Add color parsing 2014-12-13 17:24:54 +02:00
Niklas von Hertzen
893ce74a33 Implement checkbox and radio input element rendering 2014-12-06 18:17:04 +02:00
Niklas von Hertzen
e4329c4d37 Fix phantomjs scrolling test failure 2014-12-06 16:19:38 +02:00
Niklas von Hertzen
069140974b Don't scroll owner document after cloned document load 2014-11-30 14:23:52 +02:00
Niklas von Hertzen
1e826e32ae Fix scroll to top with pages using hashtag anchors 2014-11-30 14:16:02 +02:00
Niklas von Hertzen
0579804cbb Merge pull request #478 from ClickWithMeNow/master
add classname to html2canvas iframe
2014-11-21 00:31:01 +02:00
Dan Alstadt
f15666e156 adding submodules 2014-11-20 14:31:21 -06:00
Dan Alstadt
d4cb7e8868 add html2canvas-container classname to iframe 2014-11-20 14:27:34 -06:00
Niklas von Hertzen
657eb983cf Add options.background option 2014-11-04 21:53:26 +02:00
Niklas von Hertzen
fc800bff9d Partial fix for borders 2014-11-04 21:40:09 +02:00
Niklas von Hertzen
36648afc3d Add test case for border-style: inset 2014-11-03 23:49:39 +02:00
Niklas von Hertzen
aa3aafbc0c Fix race condition for content load 2014-11-03 23:29:57 +02:00
Niklas von Hertzen
525b5c4f36 Fix font rendering for IE with multiple inherited fonts 2014-11-02 22:15:48 +02:00
Niklas von Hertzen
706b2abb19 Update readme with information regarding submodules 2014-11-02 20:58:01 +02:00
Niklas von Hertzen
6b1d116a5e Add build warning regarding missing source files 2014-11-02 20:57:48 +02:00
Niklas von Hertzen
772767d0d9 Disable transitions for cloned page 2014-11-02 20:26:25 +02:00
Niklas von Hertzen
7ebd191488 Fix form placeholder/value positioning for scrolled pages 2014-11-02 19:18:39 +02:00
Niklas von Hertzen
6ece2a3d5a Fix svg matching performance issue 2014-11-02 18:54:39 +02:00
Niklas von Hertzen
1793b802b1 Add onclone callback option to allow modifying cloned document prior to rendering 2014-10-26 19:10:09 +02:00
Niklas von Hertzen
8184b7cf51 Refactor test 2014-10-19 22:38:08 +03:00
Niklas von Hertzen
04bdb48cba Fix layer ordering with multiple stacks on same auto z-index 2014-10-19 22:29:34 +03:00
Niklas von Hertzen
7e13231807 Fix canvas cropping with type: 'view' 2014-10-15 20:28:26 +03:00
Niklas von Hertzen
199685ebd1 Disable animations on rendered page 2014-10-12 20:44:39 +03:00
Niklas von Hertzen
444869e3ca Implement css clipping for rect() (with pixel values) 2014-10-12 20:32:47 +03:00
Niklas von Hertzen
717f69d99a Don't remove frame container in tests 2014-10-12 20:32:10 +03:00
Niklas von Hertzen
541f6a62d8 Expand clip test case 2014-10-12 20:32:00 +03:00
Niklas von Hertzen
d8dcbdedd8 Add test case for css clip 2014-10-07 20:11:46 +03:00
Niklas von Hertzen
5712b621ca Correctly clip content that has border-radius (#399) 2014-10-07 19:11:24 +03:00
Niklas von Hertzen
6073928978 Fix origin check for IE (#460) 2014-10-07 18:59:26 +03:00
Niklas von Hertzen
e103ad8219 Fix pseudoelement rendering (with nth-child selectors etc.) 2014-10-06 22:46:43 +03:00
Niklas von Hertzen
d5430070a2 Ignore exports and module assignments within scope 2014-09-29 19:33:01 +03:00
Niklas von Hertzen
f3d45e005e Don't leak punycode to global scope 2014-09-29 19:15:50 +03:00
Niklas von Hertzen
b60b4b2a45 Add option to use existing canvas for rendering 2014-09-28 22:51:11 +03:00
Niklas von Hertzen
bd1abe1857 Provide fallbacks for html parsing 2014-09-28 00:11:54 +03:00
Niklas von Hertzen
8a3d1d7f22 Fix firefox cross-origin iframe rendering 2014-09-27 23:03:57 +03:00
Niklas von Hertzen
e6d31ada4a Don't load javascript on cloned content by default 2014-09-27 21:20:08 +03:00
Niklas von Hertzen
19777c6623 Add simpler api for rendering external urls 2014-09-27 21:07:25 +03:00
Niklas von Hertzen
33d84c82b0 Add simpler api for rendering external urls 2014-09-27 20:37:12 +03:00
Niklas von Hertzen
1e19832171 Add google maps test 2014-09-27 20:20:08 +03:00
Niklas von Hertzen
fa659ad1df Fix background-size with background-repeat x and y as well (#447) 2014-09-27 18:03:18 +03:00
Niklas von Hertzen
f517a35781 Clone and render canvas content correctly 2014-09-27 18:00:14 +03:00
Niklas von Hertzen
3f3424e49c Fix: Render background-size correctly with no-repeat #447 2014-09-27 17:02:46 +03:00
Niklas von Hertzen
1d8a316f13 Correctly handle overflow content 2014-09-27 16:54:53 +03:00
Niklas von Hertzen
b1f948bb60 Fix z-index stacking with fixed position 2014-09-21 11:44:11 +03:00
Niklas von Hertzen
24d9a22556 Correctly apply canvas background color 2014-09-21 11:24:20 +03:00
Niklas von Hertzen
7ee2f411b0 Continue render with failed svg images 2014-09-20 21:54:03 +03:00
Niklas von Hertzen
9406f76a18 Fix test retry logic 2014-09-20 21:09:18 +03:00
Niklas von Hertzen
6092629679 Add proxy server to travis task 2014-09-20 20:54:43 +03:00
Niklas von Hertzen
349d0a300c Fix tests to use local proxy 2014-09-20 20:25:48 +03:00
Niklas von Hertzen
0a7df6d9b9 Correctly use native rendering with inline <svg> nodes if possible 2014-09-20 20:21:25 +03:00
Niklas von Hertzen
70241a789d Fallback to jsonp proxy if browser doesn't support cors xhr/image 2014-09-20 19:53:22 +03:00
Niklas von Hertzen
075c836d16 Fix missing jsonp feature with grunt-contrib-connect 2014-09-20 18:25:28 +03:00
Niklas von Hertzen
c9993a7237 Revert back to grunt-contrib-connect 2014-09-20 18:18:39 +03:00
Niklas von Hertzen
3b8d4dece2 Use correct window size for cloned iframe 2014-09-20 18:01:57 +03:00
Niklas von Hertzen
440120b087 Merge branch 'ddrscott-fix-doc-measurements' 2014-09-20 17:40:27 +03:00
Niklas von Hertzen
e80fe312ee Merge branch 'fix-doc-measurements' of https://github.com/ddrscott/html2canvas into ddrscott-fix-doc-measurements
Conflicts:
	dist/html2canvas.js
	dist/html2canvas.min.js
	src/core.js
2014-09-20 17:40:09 +03:00
Niklas von Hertzen
b141c9f0d1 Begin implementing cross-origin iframes 2014-09-20 17:36:15 +03:00
Niklas von Hertzen
3a3a61e316 Fix tests 2014-09-18 22:25:37 +03:00
Niklas von Hertzen
6c08c3fa04 Simplify webdriver tests 2014-09-17 00:11:47 +03:00
Niklas von Hertzen
d4c9a41873 Add preliminary support for same-origin iframes 2014-09-16 20:12:13 +03:00
Niklas von Hertzen
6347e7f043 Use correct document context for canvas render 2014-09-14 20:14:38 +03:00
Niklas von Hertzen
9220eb6def Update examples to v0.5 api 2014-09-14 20:04:37 +03:00
Niklas von Hertzen
af965cc3a6 Add fontawesome test 2014-09-14 19:45:58 +03:00
Niklas von Hertzen
9d088fa431 Use punycode to parse unicode characters correctly 2014-09-14 19:32:26 +03:00
Niklas von Hertzen
b8d3688c29 upd 2014-09-14 18:55:14 +03:00
Niklas von Hertzen
9907149513 Don't parse/render input hidden nodes 2014-09-14 17:44:55 +03:00
Niklas von Hertzen
645fcd60b3 Ensure webdriver session is closed before exiting 2014-09-09 19:54:13 +03:00
Niklas von Hertzen
0325a9b836 Add atob polyfill for ie9 for SVG base64 rendering 2014-09-09 18:42:00 +03:00
Niklas von Hertzen
36052c2765 Add support for inline, base64 and node svg's 2014-09-08 23:44:10 +03:00
Niklas von Hertzen
281efd2d5e Use correct build with ci and default grunt tasks 2014-09-08 23:43:00 +03:00
Niklas von Hertzen
44b958beaf Default to element dimension if image size cannot be determined (vector images) 2014-09-08 21:25:21 +03:00
Niklas von Hertzen
2a020e5a21 Allow tainting images to be drawn if option enabled 2014-09-08 21:24:26 +03:00
Niklas von Hertzen
c20e679f2c Prefer native svg rendering if available 2014-09-08 21:16:30 +03:00
Scott Pierce
52c669fe5b Merge branch 'master' of https://github.com/niklasvh/html2canvas
Conflicts:
	build/html2canvas.min.js
	dist/html2canvas.js
	src/core.js
2014-09-04 16:24:23 -05:00
Scott Pierce
382c16a522 use cloned document for measurements 2014-09-04 14:02:44 -05:00
Niklas von Hertzen
ba9d5201cf Add svg rendering with fabric.js 2014-09-04 20:50:31 +03:00
Niklas von Hertzen
2e2d722e3d Fix rendering for safari 6 2014-09-04 19:29:42 +03:00
Niklas von Hertzen
b9edf0b1c5 Add indentation rule to jshintrc 2014-09-04 18:47:17 +03:00
Niklas von Hertzen
07f793b0ed Preliminary support for svg rendering 2014-09-04 18:47:06 +03:00
Niklas von Hertzen
180b624cb3 reformat testing 2014-09-04 18:46:17 +03:00
Niklas von Hertzen
b2280bc8ec rename build folder to dist 2014-09-01 20:22:56 +03:00
Niklas von Hertzen
f3f92ab425 Fix travis segfault 2014-08-27 21:35:48 +03:00
Niklas von Hertzen
503add6e2f Refactor webdriver test running 2014-08-26 21:03:54 +03:00
Niklas von Hertzen
d2bfb810d4 Update webdriver configs to GruntFile 2014-08-26 20:01:53 +03:00
Niklas von Hertzen
649cdd4e37 Update readme regarding 0.5 2014-08-25 18:23:18 +03:00
Niklas von Hertzen
73a34493ac Queue webdriver tests 2014-05-18 23:31:49 +03:00
Niklas von Hertzen
ce1c4c84f5 Fix CSS gradients fail to render when non-vendor prefix property is included #388 2014-05-18 23:20:45 +03:00
Niklas von Hertzen
44beaf2989 Update webdriver browser versions to test 2014-05-18 21:37:56 +03:00
Niklas von Hertzen
d716210509 Update travis webdriver settings 2014-05-18 21:17:59 +03:00
Niklas von Hertzen
be5d1f8665 Don't use proxy for cors test 2014-05-18 21:17:46 +03:00
Niklas von Hertzen
340b125b19 Replace saucelabs travis script with sauce_connect addon 2014-05-18 20:31:53 +03:00
Niklas von Hertzen
ad1f0d418c Add travis env parameters to webdriver tests 2014-05-18 20:25:51 +03:00
Niklas von Hertzen
08373f0bd4 Update travis webdriver host details 2014-05-18 19:28:50 +03:00
Niklas von Hertzen
6959058560 Fix linear gradient rendering 2014-05-18 19:14:22 +03:00
Niklas von Hertzen
d6ed6c0194 Fallback to DummyImageContainer if no suitable ImageContainer found 2014-05-18 17:48:32 +03:00
Niklas von Hertzen
9ee87339a3 Fix cors loading of images 2014-05-18 17:40:01 +03:00
Niklas von Hertzen
b7595e19e9 Fix webdriver tests 2014-05-18 17:39:24 +03:00
Niklas von Hertzen
281e6bbedf Use backgroundColor from documentElement if rendered element lacks backgroundColor 2014-04-06 21:43:20 +03:00
Niklas von Hertzen
fee91055b2 Fix bug with iframe not being loaded in-time 2014-03-24 19:05:32 +02:00
Niklas von Hertzen
650ead63e5 Fix transform nesting 2014-03-15 14:30:26 +02:00
Niklas von Hertzen
b35fcaeaf9 Move non-canvas supported fallback to seperate file 2014-03-15 13:22:55 +02:00
Niklas von Hertzen
81c22866bc Don't fail on broken images 2014-03-15 13:20:05 +02:00
Niklas von Hertzen
25d892f525 Fix IE9 https origin check bug 2014-03-15 13:01:04 +02:00
Niklas von Hertzen
9db1ecfdfc Fix Qunit tests 2014-03-10 19:56:23 +02:00
Niklas von Hertzen
12d85e3c04 Fail with a Promise.reject when no canvas support 2014-03-10 18:46:25 +02:00
Niklas von Hertzen
3101f2007a Fix Firefox DOM clone not being ready 2014-03-05 22:24:53 +02:00
Niklas von Hertzen
85b77ca49f Fix cropping bug 2014-03-05 19:19:24 +02:00
Niklas von Hertzen
bb8c5a973b Limit crop to the maximum size of outputted canvas 2014-03-04 21:54:24 +02:00
Niklas von Hertzen
0187fcab42 Fix internal method name for spec 2014-03-04 20:42:57 +02:00
Niklas von Hertzen
cfe4137bcc Account for negative bounds in cropping 2014-03-04 20:42:34 +02:00
Niklas von Hertzen
d2c3378c3e Correctly crop content 2014-03-03 21:19:28 +02:00
Niklas von Hertzen
95f4bcea0a Render input elements correctly 2014-03-02 19:51:46 +02:00
Niklas von Hertzen
9bae5b610a Fix IE misalignment of content after scroll 2014-03-02 18:03:30 +02:00
Niklas von Hertzen
7ce46e95cd Fix logging in IE9 2014-03-02 16:55:40 +02:00
Niklas von Hertzen
84c1dc6283 Remove script tags from cloned DOM 2014-03-02 16:03:22 +02:00
Niklas von Hertzen
15ca3381eb Fix text rendering for IE and Opera 2014-03-02 16:00:59 +02:00
Niklas von Hertzen
18d95d669b Partial implementation for transforms 2014-02-23 17:35:46 +02:00
Niklas von Hertzen
5137e5f35a Render correct element 2014-02-23 17:02:49 +02:00
Niklas von Hertzen
314d26f1f1 Hide iframe container from view during render 2014-02-23 16:26:23 +02:00
Niklas von Hertzen
82e5a8a7c0 Fix image loading for cross-origin resources 2014-02-17 00:04:09 +02:00
Niklas von Hertzen
9af96d3812 Fix render ordering of nodes that form fake stacking contexts 2014-02-15 00:33:09 +02:00
Niklas von Hertzen
6f2a775841 Fix background position rendering 2014-02-15 00:31:48 +02:00
Niklas von Hertzen
f2b662801e Add another z-index test 2014-02-15 00:30:38 +02:00
Niklas von Hertzen
60587c72bf Fix unit tests 2014-02-15 00:30:28 +02:00
Niklas von Hertzen
d9d516d27e Force doctype for iframe 2014-02-10 19:53:13 +02:00
Niklas von Hertzen
899d5321d4 Fix pseudoelement rendering for Firefox 2014-02-08 17:42:40 +02:00
Niklas von Hertzen
5d20493f46 Add support for pseudoelements 2014-02-08 16:52:41 +02:00
Niklas von Hertzen
b5891c49b4 Correctly assign image loading method for <img> elements 2014-02-08 14:07:20 +02:00
Niklas von Hertzen
467ff87482 Switch to using Promises 2014-02-03 19:42:42 +02:00
Niklas von Hertzen
9beae48cf0 Start implementing background gradients 2014-02-01 21:48:30 +02:00
Niklas von Hertzen
17731169e9 Add support for single text-shadow 2014-02-01 20:03:23 +02:00
Niklas von Hertzen
e27c41efd3 Add font metrics and text-decorations 2014-02-01 19:36:43 +02:00
Niklas von Hertzen
1f90defbfa Fix text rendering for Opera/IE 2014-02-01 19:11:59 +02:00
Niklas von Hertzen
b4bb34c95b Move NodeParser to seperate file 2014-02-01 18:52:53 +02:00
Niklas von Hertzen
64668fe694 ios window size and scroll fixes 2014-02-01 18:32:05 +02:00
Niklas von Hertzen
9ebae161e2 Fix IE issues 2014-01-26 20:43:50 +02:00
Niklas von Hertzen
729bc88d1f Increase logging 2014-01-26 20:43:36 +02:00
Niklas von Hertzen
b1c2f03ae9 Add option to only render current view 2014-01-26 20:42:57 +02:00
Niklas von Hertzen
74cb3466ec Draw <img> elements 2014-01-26 18:10:04 +02:00
Niklas von Hertzen
2afdcaff35 Add Promise polyfill 2014-01-26 16:06:39 +02:00
Niklas von Hertzen
1070cec852 Implement background rendering 2014-01-26 16:06:16 +02:00
Niklas von Hertzen
ba9a33b1bc Add work around for chrome related background-image issues in iframe 2014-01-26 15:50:13 +02:00
Niklas von Hertzen
f474542382 Begin implementing background-image rendering 2014-01-21 21:41:00 +02:00
Niklas von Hertzen
0cb259f6cd Cache backgroundImages request for nodes 2014-01-20 22:42:58 +02:00
Niklas von Hertzen
1a7f5732bf Filter nodes and render correct size correctly 2014-01-20 22:42:39 +02:00
Niklas von Hertzen
2b8389cb64 Make image loading to work on top of Promises/polyfill 2014-01-19 21:05:07 +02:00
Niklas von Hertzen
8b8c080841 0.5.0 rewrite 2014-01-19 18:04:27 +02:00
Niklas von Hertzen
6201e09118 Merge branch 'patch-1' of github.com:bensho/html2canvas into bensho-patch-1 2013-12-23 16:33:58 +02:00
Niklas von Hertzen
517fd8cd1d Refactor background parsing 2013-12-23 16:07:49 +02:00
Niklas von Hertzen
e228fc57ce Merge branch 'patch-2' of github.com:brcontainer/html2canvas into brcontainer-patch-2 2013-12-23 15:32:18 +02:00
Niklas von Hertzen
46cc8b6975 Fix z-index ordering bug 2013-12-23 15:27:46 +02:00
Niklas von Hertzen
443fd17a12 Add all js files to grunt watch 2013-12-17 20:01:03 +02:00
Guilherme Nascimento
0b213eecef Fix bug in WebKitGTK+, backgroundRepeat return "wrong" values 2013-12-06 09:49:37 -02:00
Ben Sholds
ae1a15f7c5 Update pseudoelements.html 2013-11-25 16:39:35 -05:00
Ben Sholds
cea3005056 Fix invalid selector exception with empty class values
After removing :before and :after pseudo selectors, a class name may be empty, causing an invalid selector string when joined. Remove empty elements before calling querySelectorAll.
2013-11-25 09:48:12 -05:00
Niklas von Hertzen
1d4b1753d6 Merge pull request #297 from Grom-S/patch-1
incorect z-index sorting fixed
2013-11-24 03:32:57 -08:00
Grom-S
f00b23a9ec incorect z-index sorting fixed
If you omit compare function in javascript `sort()` method it will sort  lexicographically (in dictionary order) according to the string conversion of each element. 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Parameters

For example 
[0, 9, 8, 10].sort()
will place element with z-index 10 on 2nd position of the array
2013-11-21 20:37:40 +02:00
Niklas von Hertzen
e9afe03960 Implement background-size cover/contain 2013-11-12 19:35:28 +02:00
Niklas von Hertzen
57d20a9794 Fix missing background color bug 2013-11-12 19:34:20 +02:00
Niklas von Hertzen
35c5ca3340 Add tests for background-size 2013-11-12 19:33:56 +02:00
Niklas von Hertzen
7cc7f80ee2 Add dom depth information to render queue 2013-10-27 22:08:12 +02:00
Niklas von Hertzen
4c75d819a4 Merge pull request #270 from ssafejava/parseSpeed
Speed parsing by as much as 50% & add async parsing option.
2013-09-22 11:32:25 -07:00
Niklas von Hertzen
838f91e156 Merge pull request #269 from ssafejava/jshint
Move jshint rules into jshintrc and include from Gruntfile.
2013-09-17 23:29:37 -07:00
ssafejava
8bea01b81d Speed & accuracy improvements to pseudo element rendering.
Previously, pseudo elements would be processed as they were found in the DOM tree, which was
an expensive operation as each element's computed :before and :after style was checked for
'content' styles.

This commit traverses the user's stylesheets for :before and :after selectors, gathers the classes
affected, selects all elements that likely have a pseudo element present, then checks computed style.
If there is actually an element present, it is created but *not* appended to the DOM until after
all elements have been processed.

After all elements have been found and created, they are added to the DOM in a single batch, and the original
pseudo elements are hidden in a single batch. This prevents the layout invalidation / relayout loop that was
occuring previously, and in my tests speeds parsing by as much as 50% or more, depending on how many
pseudo elements your page uses.

Additionally, this commit contains a bugfix to the handling of ":before" pseudo elements; the browser effectively
inserts them as the first child of the element, not before the element. This fixes a few rendering inconsistencies
and complicated pages look almost perfect in my tests.
2013-09-18 13:38:21 +08:00
ssafejava
e115180731 Add async parsing option.
In my testing, the major time sink is parsing. This commit adds a setTimeout() around parsing
of each item so control can return to the browser. This increases the total time it takes to finish
a screenshot but will not freeze the browser when it does. This is a good option when e.g. doing
error reporting, where you might not want to freeze the browser while sending debugging information
back to your server.
2013-09-18 13:38:21 +08:00
ssafejava
e782efa614 Move jshint rules into jshintrc and include from Gruntfile. 2013-09-18 13:37:06 +08:00
Niklas von Hertzen
806cd60474 Add links to readme 2013-09-07 22:07:27 +03:00
123 changed files with 37451 additions and 9449 deletions

9
.gitignore vendored
View File

@@ -1,19 +1,10 @@
/nbproject/
/images/
/tests/templates/
/tests/cache/
/tests/flashcanvas.html
/lib/
/bin/
image.jpg
/.project
/.settings/
/tests/certificate.pem
node_modules/
.envrc
server.js
*.sublime-workspace
chromedriver.log
*.baseline
*.iml
.idea/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "src/fabric"]
path = src/fabric
url = https://github.com/kangax/fabric.js.git

19
.jshintrc Normal file
View 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"]
}

View File

@@ -7,6 +7,15 @@ env:
- 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
- curl https://gist.github.com/niklasvh/6150144/raw/sauce_connect_setup.sh | bash
- 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
View 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>)

View File

@@ -1,99 +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',
pre: '\n(function(window, document, undefined){\n\n',
post: '\n})(window,document);'
};
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'
};
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
qunit: {
files: ['tests/qunit/index.html']
},
concat: {
dist: {
src: [
'src/Core.js',
'src/Font.js',
'src/Generate.js',
'src/Queue.js',
'src/Parse.js',
'src/Preload.js',
'src/Renderer.js',
'src/Support.js',
'src/Util.js',
'src/renderers/Canvas.js'
],
dest: 'build/<%= pkg.name %>.js'
},
options:{
banner: meta.banner + meta.pre,
footer: meta.post
}
},
uglify: {
dist: {
src: ['<%= concat.dist.dest %>'],
dest: 'build/<%= pkg.name %>.min.js'
},
options: {
banner: meta.banner
}
},
watch: {
files: 'src/*',
tasks: ['build', 'jshint']
},
jshint: {
all: ['<%= concat.dist.dest %>'],
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true,
globals: {
jQuery: true
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({
grunt.registerTask('webdriver', 'Browser render tests', function(arg1) {
var selenium = require("./tests/selenium.js");
var done = this.async();
pkg: grunt.file.readJSON('package.json'),
if (arguments.length) {
selenium[arg1].apply(null, arguments);
} else {
selenium.tests();
}
});
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
});
// Load tasks
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-qunit');
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();
});
});
// Default task.
grunt.registerTask('build', ['concat', 'uglify']);
grunt.registerTask('default', ['concat', 'jshint', 'qunit', 'uglify']);
grunt.registerTask('travis', ['concat', 'jshint', 'qunit', 'uglify', 'webdriver']);
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']);
};

View File

@@ -1,10 +1,9 @@
{
"name": "html2canvas",
"version": "0.4.1",
"description": "Screenshots with JavaScript",
"main": "build/html2canvas.js",
"main": "dist/html2canvas.js",
"ignore": [
"tests",
".travis.yml"
]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View File

@@ -171,12 +171,10 @@
<p style="color: black; font-size: 1em; line-height: 1.3em; clear: both">
This is a nonsensical document, but syntactically valid HTML 4.0. All 100% conformant CSS1 agents should be able to render the document elements above this paragraph <b>indistinguishably</b> (to the pixel) from this reference rendering, (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the <A HREF="sec5526c.htm" style="text-decoration:none">parent page</A>.
</p>
<script type="text/javascript" src="../build/html2canvas.js"></script>
<script type="text/javascript" src="../dist/html2canvas.js"></script>
<script type="text/javascript">
html2canvas(document.body, {
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});
</script>
</body>

View File

@@ -53,12 +53,10 @@
</div>
</div>
<script type="text/javascript" src="../build/html2canvas.js"></script>
<script type="text/javascript" src="../dist/html2canvas.js"></script>
<script type="text/javascript">
html2canvas(document.body, {
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});
</script>
</body>

View 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>

View File

@@ -2,16 +2,19 @@
"title": "html2canvas",
"name": "html2canvas",
"description": "Screenshots with JavaScript",
"version": "0.4.1",
"main": "src/core.js",
"version": "0.5.0-alpha2",
"author": {
"name": "Niklas von Hertzen",
"email": "niklasvh@gmail.com",
"url": "http://hertzen.com"
},
"engines": {
"node": ">=0.8.0"
"node": ">=0.10.0"
},
"dependencies": {
"es6-promise": "^2.0.1"
},
"dependencies": {},
"repository": {
"type": "git",
"url": "git@github.com:niklasvh/html2canvas.git"
@@ -20,22 +23,27 @@
"url": "https://github.com/niklasvh/html2canvas/issues"
},
"devDependencies": {
"grunt": ">=0.4.0",
"grunt-contrib-concat": "*",
"grunt-contrib-uglify": "*",
"grunt-contrib-jshint": "*",
"grunt-contrib-qunit": "*",
"grunt-contrib-watch": "~0.5.1",
"googleapis": "~0.4.3",
"jwt-sign": "~0.1.0",
"base64-arraybuffer": ">= 0.1.0",
"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",
"sync-webdriver": ">=0.1.1",
"express": "~3.2.3",
"baconjs": "~0.3.15"
"wd": "^0.2.21"
},
"scripts": {
"test": "grunt travis --verbose"
"test": "grunt travis --verbose",
"postpublish": "bower register html2canvas git://github.com/niklasvh/html2canvas.git"
},
"homepage": "http://html2canvas.hertzen.com",
"licenses": [

View File

@@ -1,8 +1,9 @@
html2canvas
===========
### Current build status ###
[![Build Status](https://travis-ci.org/niklasvh/html2canvas.png)](https://travis-ci.org/niklasvh/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/)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.org/niklasvh/html2canvas.png)](https://travis-ci.org/niklasvh/html2canvas)
#### JavaScript HTML renderer ####
@@ -30,23 +31,29 @@ The script should work fine on the following browsers:
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 [grunt](http://gruntjs.com/) for building. Alternatively, you can download the latest build from [here](http://html2canvas.hertzen.com/build/html2canvas.js).
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):
@@ -77,46 +84,3 @@ For more information and examples, please visit the [homepage](http://html2canva
### Contributing ###
If you wish to contribute to the project, please send the pull requests to the develop branch. Before submitting any changes, try and test that the changes work with all the support browsers. If some CSS property isn't supported or is incomplete, please create appropriate tests for it as well before submitting any code changes.
### Changelog ###
v0.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>)

View File

@@ -1,410 +0,0 @@
"use strict";
var _html2canvas = {},
previousElement,
computedCSS,
html2canvas;
_html2canvas.Util = {};
_html2canvas.Util.log = function(a) {
if (_html2canvas.logging && window.console && window.console.log) {
window.console.log(a);
}
};
_html2canvas.Util.trimText = (function(isNative){
return function(input) {
return isNative ? isNative.apply(input) : ((input || '') + '').replace( /^\s+|\s+$/g , '' );
};
})(String.prototype.trim);
_html2canvas.Util.asFloat = function(v) {
return parseFloat(v);
};
(function() {
// TODO: support all possible length values
var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
_html2canvas.Util.parseTextShadows = function (value) {
if (!value || value === 'none') {
return [];
}
// find multiple shadow declarations
var shadows = value.match(TEXT_SHADOW_PROPERTY),
results = [];
for (var i = 0; shadows && (i < shadows.length); i++) {
var s = shadows[i].match(TEXT_SHADOW_VALUES);
results.push({
color: s[0],
offsetX: s[1] ? s[1].replace('px', '') : 0,
offsetY: s[2] ? s[2].replace('px', '') : 0,
blur: s[3] ? s[3].replace('px', '') : 0
});
}
return results;
};
})();
_html2canvas.Util.parseBackgroundImage = function (value) {
var whitespace = ' \r\n\t',
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 (element) {
var clientRect, bounds = {};
if (element.getBoundingClientRect){
clientRect = element.getBoundingClientRect();
// TODO add scroll position to bounds, so no scrolling of window necessary
bounds.top = clientRect.top;
bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
bounds.left = clientRect.left;
bounds.width = element.offsetWidth;
bounds.height = element.offsetHeight;
}
return bounds;
};
// TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
// but would require further work to calculate the correct positions for elements with offsetParents
_html2canvas.Util.OffsetBounds = function (element) {
var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {top: 0, left: 0};
return {
top: element.offsetTop + parent.top,
bottom: element.offsetTop + element.offsetHeight + parent.top,
left: element.offsetLeft + parent.left,
width: element.offsetWidth,
height: element.offsetHeight
};
};
function toPX(element, attribute, value ) {
var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
left,
style = element.style;
// Check if we are not dealing with pixels, (Opera has issues with this)
// Ported from jQuery css.js
// 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( value ) && /^-?\d/.test(value) ) {
// Remember the original values
left = style.left;
// Put in the new values to get a computed value out
if (rsLeft) {
element.runtimeStyle.left = element.currentStyle.left;
}
style.left = attribute === "fontSize" ? "1em" : (value || 0);
value = style.pixelLeft + "px";
// Revert the changed values
style.left = left;
if (rsLeft) {
element.runtimeStyle.left = rsLeft;
}
}
if (!/^(thin|medium|thick)$/i.test(value)) {
return Math.round(parseFloat(value)) + "px";
}
return value;
}
function asInt(val) {
return parseInt(val, 10);
}
function parseBackgroundSizePosition(value, element, attribute, index) {
value = (value || '').split(',');
value = value[index || 0] || value[0] || 'auto';
value = _html2canvas.Util.trimText(value).split(' ');
if(attribute === 'backgroundSize' && (!value[0] || value[0].match(/cover|contain|auto/))) {
//these values will be handled in the parent function
} else {
value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
if(value[1] === undefined) {
if(attribute === 'backgroundSize') {
value[1] = 'auto';
return value;
} else {
// IE 9 doesn't return double digit always
value[1] = value[0];
}
}
value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
}
return value;
}
_html2canvas.Util.getCSS = function (element, attribute, index) {
if (previousElement !== element) {
computedCSS = document.defaultView.getComputedStyle(element, null);
}
var value = computedCSS[attribute];
if (/^background(Size|Position)$/.test(attribute)) {
return parseBackgroundSizePosition(value, element, attribute, index);
} else if (/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) {
var arr = value.split(" ");
if (arr.length <= 1) {
arr[1] = arr[0];
}
return arr.map(asInt);
}
return value;
};
_html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){
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 (/contain|cover/.test(bgposition[0])) {
var resized = _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, bgposition[0]);
left = resized.width;
topPos = resized.height;
} else {
left = parseInt(bgposition[0], 10);
}
}
} 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) {
_html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
children = [];
}
return children;
};
_html2canvas.Util.isTransparent = function(backgroundColor) {
return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
};

View File

@@ -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;
};
})();

View File

@@ -1,424 +0,0 @@
(function(){
var Util = _html2canvas.Util,
Generate = {};
_html2canvas.Generate = 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)
*/
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;
};
function addScrollStops(grad) {
return function(colorStop) {
try {
grad.addColorStop(colorStop.stop, colorStop.color);
}
catch(e) {
Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]);
}
};
}
Generate.Gradient = function(src, bounds) {
if(bounds.width === 0 || bounds.height === 0) {
return;
}
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
gradient, grad;
canvas.width = bounds.width;
canvas.height = bounds.height;
// TODO: add support for multi defined background gradients
gradient = _html2canvas.Generate.parseGradient(src, bounds);
if(gradient) {
switch(gradient.type) {
case 'linear':
grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
gradient.colorStops.forEach(addScrollStops(grad));
ctx.fillStyle = grad;
ctx.fillRect(0, 0, bounds.width, bounds.height);
break;
case 'circle':
grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
gradient.colorStops.forEach(addScrollStops(grad));
ctx.fillStyle = grad;
ctx.fillRect(0, 0, bounds.width, bounds.height);
break;
case 'ellipse':
var canvasRadial = document.createElement('canvas'),
ctxRadial = canvasRadial.getContext('2d'),
ri = Math.max(gradient.rx, gradient.ry),
di = ri * 2;
canvasRadial.width = canvasRadial.height = di;
grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
gradient.colorStops.forEach(addScrollStops(grad));
ctxRadial.fillStyle = grad;
ctxRadial.fillRect(0, 0, di, di);
ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
break;
}
}
return canvas;
};
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;
};
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;
};
})();

File diff suppressed because it is too large Load Diff

View File

@@ -1,317 +0,0 @@
_html2canvas.Preload = function( options ) {
var images = {
numLoaded: 0, // also failed are counted here
numFailed: 0,
numTotal: 0,
cleanupDone: false
},
pageOrigin,
Util = _html2canvas.Util,
methods,
i,
count = 0,
element = options.elements[0] || document.body,
doc = element.ownerDocument,
domImages = element.getElementsByTagName('img'), // Fetch images of the present 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(){
Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")");
if (!images.firstRun && images.numLoaded >= images.numTotal){
Util.log("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 {
Util.Children(el).forEach(getImages);
}
catch( e ) {}
try {
elNodeType = el.nodeType;
} catch (ex) {
elNodeType = false;
Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message);
}
if (elNodeType === 1 || elNodeType === undefined) {
loadPseudoElementImages(el);
try {
loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el);
} catch(e) {
Util.log("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;
} 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") {
Util.log("html2canvas: Cleanup because: " + cause);
} else {
Util.log("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++;
Util.log("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);
}
Util.log('html2canvas: Preload starts: finding background-images');
images.firstRun = true;
getImages(element);
Util.log('html2canvas: Preload: Finding images');
// load <img> images
for (i = 0; i < imgLen; i+=1){
methods.loadImage( domImages[i].getAttribute( "src" ) );
}
images.firstRun = false;
Util.log('html2canvas: Preload: Done.');
if (images.numTotal === images.numLoaded) {
start();
}
return methods;
};

View File

@@ -1,123 +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
});
return value;
}
};
}

View File

@@ -1,101 +0,0 @@
_html2canvas.Renderer = function(parseQueue, options){
// http://www.w3.org/TR/CSS21/zindex.html
function createRenderQueue(parseQueue) {
var queue = [],
rootContext;
rootContext = (function buildStackingContext(rootNode) {
var rootContext = {};
function insert(context, node, specialParent) {
var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex),
contextForChildren = context, // the stacking context for children
isPositioned = node.zIndex.isPositioned,
isFloated = node.zIndex.isFloated,
stub = {node: node},
childrenDest = specialParent; // where children without z-index should be pushed into
if (node.zIndex.ownStacking) {
// '!' comes before numbers in sorted array
contextForChildren = stub.context = { '!': [{node:node, children: []}]};
childrenDest = undefined;
} else if (isPositioned || isFloated) {
childrenDest = stub.children = [];
}
if (zi === 0 && specialParent) {
specialParent.push(stub);
} else {
if (!context[zi]) { context[zi] = []; }
context[zi].push(stub);
}
node.zIndex.children.forEach(function(childNode) {
insert(contextForChildren, childNode, childrenDest);
});
}
insert(rootContext, rootNode);
return rootContext;
})(parseQueue);
function sortZ(context) {
Object.keys(context).sort().forEach(function(zi) {
var nonPositioned = [],
floated = [],
positioned = [],
list = [];
// positioned after static
context[zi].forEach(function(v) {
if (v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) {
// http://www.w3.org/TR/css3-color/#transparency
// non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with z-index: 0 and opacity: 1.
positioned.push(v);
} else if (v.node.zIndex.isFloated) {
floated.push(v);
} else {
nonPositioned.push(v);
}
});
(function walk(arr) {
arr.forEach(function(v) {
list.push(v);
if (v.children) { walk(v.children); }
});
})(nonPositioned.concat(floated, positioned));
list.forEach(function(v) {
if (v.context) {
sortZ(v.context);
} else {
queue.push(v.node);
}
});
});
}
sortZ(rootContext);
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.stack), _html2canvas);
};

View File

@@ -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;
}
_html2canvas.Util.log('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()
};
};

View File

@@ -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: _html2canvas.Util.log
};
};
window.html2canvas.log = _html2canvas.Util.log; // for renderers
window.html2canvas.Renderer = {
Canvas: undefined // We are assuming this will be used
};

125
src/clone.js Normal file
View 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
View 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
View 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;
}

View 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

Submodule src/fabric added at 791c74a82e

52
src/font.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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;

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
module.exports = require('es6-promise').Promise;

96
src/proxy.js Normal file
View 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;

View 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;

View 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
View 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;

View File

@@ -1,128 +0,0 @@
_html2canvas.Renderer.Canvas = function(options) {
options = options || {};
var doc = document,
safeImages = [],
testCanvas = document.createElement("canvas"),
testctx = testCanvas.getContext("2d"),
Util = _html2canvas.Util,
canvas = options.canvas || doc.createElement('canvas');
function createShape(ctx, args) {
ctx.beginPath();
args.forEach(function(arg) {
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 renderItem(ctx, item) {
switch(item.type){
case "variable":
ctx[item.name] = item['arguments'];
break;
case "function":
switch(item.name) {
case "createPattern":
if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
try {
ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
}
catch(e) {
Util.log("html2canvas: Renderer: Error creating pattern", e.message);
}
}
break;
case "drawShape":
createShape(ctx, item['arguments']);
break;
case "drawImage":
if (item['arguments'][8] > 0 && item['arguments'][7] > 0) {
if (!options.taintTest || (options.taintTest && safeImage(item))) {
ctx.drawImage.apply( ctx, item['arguments'] );
}
}
break;
default:
ctx[item.name].apply(ctx, item['arguments']);
}
break;
}
}
return function(parsedData, options, document, queue, _html2canvas) {
var ctx = canvas.getContext("2d"),
newCanvas,
bounds,
fstyle,
zStack = parsedData.stack;
canvas.width = canvas.style.width = options.width || zStack.ctx.width;
canvas.height = canvas.style.height = options.height || zStack.ctx.height;
fstyle = ctx.fillStyle;
ctx.fillStyle = (Util.isTransparent(zStack.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = fstyle;
queue.forEach(function(storageContext) {
// set common settings for canvas
ctx.textBaseline = "bottom";
ctx.save();
if (storageContext.transform.matrix) {
ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]);
ctx.transform.apply(ctx, storageContext.transform.matrix);
ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]);
}
if (storageContext.clip){
ctx.beginPath();
ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
ctx.clip();
}
if (storageContext.ctx.storage) {
storageContext.ctx.storage.forEach(function(item) {
renderItem(ctx, item);
});
}
ctx.restore();
});
Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
if (options.elements.length === 1) {
if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
// crop image to the bounds of selected (single) element
bounds = _html2canvas.Util.Bounds(options.elements[0]);
newCanvas = document.createElement('canvas');
newCanvas.width = Math.ceil(bounds.width);
newCanvas.height = Math.ceil(bounds.height);
ctx = newCanvas.getContext("2d");
ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
canvas = null;
return newCanvas;
}
}
return canvas;
};
};

View File

@@ -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();
}
*/
}
_html2canvas.Util.log("html2canvas: Renderer: SVG Renderer done - returning SVG DOM obj");
return svg;
}
};
return methods;
};

181
src/renderers/canvas.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
};

View 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
View 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;

View 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>

View File

@@ -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 );

View File

@@ -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>

View File

@@ -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">&nbsp;</div>
<div class="linearGradient4">&nbsp;</div>
<div class="linearGradient5">&nbsp;</div>
<div class="linearGradient6">&nbsp;</div>
<div class="linearGradient7">&nbsp;</div>
<div class="linearGradient8">&nbsp;</div>
</div>
</body>
</html>

View 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>

View 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">&nbsp;</div>
<div class="box2">&nbsp;</div>
<div class="box3">&nbsp;</div>
<div class="box4">&nbsp;</div>
<div class="box5">&nbsp;</div>
<input type="text" />
</body>
</html>

View File

@@ -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">&nbsp;</div>
<div class="box2">&nbsp;</div>
<div class="box3">&nbsp;</div>
<div class="box4">&nbsp;</div>
<div class="box5">&nbsp;</div>
</body>
</html>

43
tests/cases/clip.html Normal file
View 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>

View 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>

View File

@@ -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>

View 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>

View File

@@ -5,6 +5,6 @@
<script type="text/javascript" src="../test.js"></script>
</head>
<body>
<iframe src="http://experiments.hertzen.com/csstree/" width="500"></iframe>
<iframe src="/tests/assets/iframe/frame1.html" width="500" height="500"></iframe>
</body>
</html>
</html>

View 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 &lt;base&gt; href)</h1>
<img src="/logos/2011/gregormendel11-res.jpg" />
</body>
</html>

View File

@@ -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 &lt;base&gt; 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>

View File

@@ -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>

View File

@@ -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;" />

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View File

@@ -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 />

View File

@@ -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{

View File

@@ -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>&nbsp;&nbsp;&nbsp;&nbsp;14 见鲁迅《集外集·自嘲》《鲁迅全集》第7卷人民文学出版社1981年版第147页</p>
</div>
</body>
</html>
</html>

View 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>

View File

@@ -5,17 +5,39 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="../../test.js"></script>
<style>
span{
.shadow1 span{
text-shadow: 1px 1px 3px #888;
}
strong {
.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>
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 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>

View File

@@ -10,7 +10,7 @@
margin-top: 100px;
}
#second {
border: 10px solid red;
border: 15px solid red;
background: darkseagreen;
-webkit-transform: rotate(7.5deg); /* Chrome, Safari 3.1+ */
-moz-transform: rotate(7.5deg); /* Firefox 3.5-15 */
@@ -27,14 +27,19 @@
transform: rotate(-70.5deg); /* Firefox 16+, IE 10+, Opera 12.10+ */
}
#fourth {
background: #bc8f8f;
}
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>
<div id="fourth">something else</div>
</body>
</html>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -1,43 +0,0 @@
U2FsdGVkX1/1OhTi++Zu8+GzAWDR9xD8SFu4sB/1mvbeKt7eF3z/t217AJii/H52
FYMoPSnwFzEvXuiS/EIpVzRYugcfc4qSkOA2PE726V1b6UQoxbcJnHK4UW2464Yb
a8dw+saN7KFpzaY0+L09QzIGqVMvtYQG2dTQk3dfAZ4Y/hduoGuCDReyRP7DbIJw
WD933bja/p1HL91AnNo7lIGD529nXugBdlCAEeYGkeAfa7/Or+68sw7fhki3MQJw
xWbo/oND/Fr4WzlGDeTQMTVfEVGYmT8K+aNtcKrgAp3SDQf37zuMnxVaKREmlXOF
IRKaoR4jzDEgiwlsMZ7CvASwKDZDckFKjg3v/yKLjg+NKg2eET+VD+3CvTai5PHQ
xRzPqkmyh+gh3Op2ZpTCtqnlgx7CfobAXjL4BOtu8aT6LZsoS/mMmqfbX49SDBw3
wVXFuhPPGsXDJD2F39kG5vX6LeygOGPMOPbzqimUftWlxcUJhb4mGKV5qcLz+WzH
17E2fv6kEwMRTfjGjUR9zMR8JjBBu0GcKGyiW6MqQwjF+BXvWoIRuwv99x2cpOjN
fAHk9Y9IgJlzWmrTpg1i5sqTNKgmQaKqarJqXwvuq86kJuCMkYr0SxleUlAmbzpC
w25JAMcNqabQLDaTJfV6V5EWAh3sHh+lAcL+QKs2Ddx5K9RwwCnNXtIhatECsu0n
oNDeWcFtaLmGVF/zXpkCkKBg0EsEcFIXZM662TaGCqukIktq61/UsuR7hVQdPXyH
/4rJTITQAXbi+FaQXzrrxKrikU7Rdovn6az5YNXBXh0pMQ9FrahxhYhDrpzpCsNc
STL2JN50u3X28ZgfRs7rB5fY9Xr5pERIAFSrNzFPCHzlStoYNSphJP8lO/XjNkcn
CTOzZrzxVpzqqzXlmArCfx3QtVo+WUUUW9hdOBgKLl19Wt0x5ZvYDPm6ODht9BjD
wfbmkRvdP9CTICBR17l2tPiGcu+4paTKBF6W6aSxQU7Z48uZP19/4cuOiITbcYnt
4wHoNk+M5G9SjLSsOlfmYNMcl0D57AOcbyUUTMZ2Wu0zc3/StJ9aotzEcFSxA1Gs
wPZr/2iqeEynJmUQeFjs01JSwxwYlLF+HOStdn6MEbsW4ftxsXO9Mr2NGAOTaO1/
Y8pyMCRJKOH6kRvHPpzza/stIZQQZk+PMRzS3PgMXsnJoF7cuanrWXJTgiNT6B37
y/ARNr3KtZTGOjc5gE+k32hPuqPi5tVKPrFVUrG2l/87Gqjs4JqNDTjTHBlUIViT
y87uB2f8/8/V50lXf+RYGPKaJ+jzbyaUxp6QRZdqJk8CEZQnrGwpUEzBVMREd+Ow
jn99f/Isn5Vadsdmt/aMy7+TnLVyjPqucIo8dUdkWdfLKF1Q7M06rYgu9MCrsbgi
hGp1/YjqSmYxWZuSUZHvd4ps2x9YI6wuwdWXAH/7+L0IVUghpSNoARrsmi/D7Rg5
4zF6eCOqH13z1luMRg1ZMpRyVQcI/Yu3IbyepnuIeYIqPklDJ6jl3kIdubEtXZTV
1XMlTvZjFuRKLeUXsF/ecTmlob+rc06YvOu91vwpxSMkpYUIc/Yt4tTWey1AbmTG
t4pf8q0cVXrVRuXf8HFEEqQlXumJGWi/WCxAawj35znEIquQDu9ERLgSemdTFH2z
ncoJseApHD9Gf2UpM4Vva+XdI9wrlYURr83/gOxenCgMq7wCWH87yuCG1IsOryPx
1DbES6axy6P+XBQJTDHK560zKSNF+3VVBGEeTQRMAnQs/IHlwWu7Af0DTmUAEO2Z
S1R+Ex2HWMM3i8/Sr8P1JDkm0u7gn2wkfI0xEIf5B10md1iniKb0GovMlfMGJTTo
+mytGTp3XUZl8lXDpbLTR9nqfpB+hL5Qi30Dv08CAQdvX9OzOEQz5T//t13wAKlo
bsbzwIuLhyYbua+wz/3uMS6QAbNNVCgIm/J1rqlwLRmzvSTJyhraamjd2UuwwIau
fgdIA5bYqdagzbUuIk8msItfUrAbhxkyBElVb5RgLVJsUbBQTHW3anrbsTxxMrcR
9jnZZV2Is2ahbnmcqqIjr7fxSqTCtwpyBxRkW8Bh+Sfu2R8x+DLcJ+ID25fbxWto
QrDSGEifpW/5bjGx4lM0j8IxoWqOl0600hlzGsD5ynSu5WPpXQ0CMvuuLXOR4CbZ
kzE5kQXAeQzfwwPcWMMhwt5F4hXAXY/+jM0TfuSc7Z9PYYRWdoDg3awRNlQJS5rk
wDwbMHKUcdi4XSwIhEdbZ4zBo0o7xXKn2XSBNy+Ym39pKWMwOrffsPtWDV+qTHpD
P5rhXEW8aD01kMkxjO2pRZ7nEDQ7VGpi3MuW9FAjXZjcc1grnZSxbKz437K9obI7
zHXGcodAXjU4Oxhs/m8JHVaHsDG5LNxLSmArsmbwcu65mlBCXcefcwOUNoVQDmZG
ENSAMlVAUX7RJ9nxXI9AqKKuVAYSMznseY3V22BCDrRR0nbVkAWjuBTKU6ajfjQ6
W2uhAzl8kH+9Xi10jt5Fhn93b8z4VjrcpZmW/gJuEHm8FY2UFF0P1y3YMCDqCPaz
gXsFZVKPPIswg/xBHB9GhiCSHnvivu/K8Ni4FcBW7smBHuqkk0VoN7CdZvNF0CuX
EANCwDD53hSmfyxR/SY6y1PQCEsX5AiRS8rMGV2bqOjP6/oM9+2JmkRZ3I6Jht2f
sxJsRUTyjecCcwnGDgmLDCZDJqLJoOE6zlRuAOvY1U7m2cFrf9EouOlYUXSeBaEh

18
tests/mocha/.jshintrc Normal file
View File

@@ -0,0 +1,18 @@
{
"curly": true,
"eqeqeq": true,
"immed": true,
"latedef": false,
"newcap": true,
"noarg": true,
"sub": true,
"undef": true,
"boss": true,
"eqnull": true,
"browser": true,
"globals": {
"jQuery": true
},
"predef": ["deepEqual", "module", "test", "$", "QUnit", "NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise",
"ImageContainer", "ProxyImageContainer", "DummyImageContainer", "Font", "FontMetrics", "GradientContainer", "LinearGradientContainer", "WebkitGradientContainer", "log", "smallImage", "parseBackgrounds"]
}

135
tests/mocha/background.html Normal file
View File

@@ -0,0 +1,135 @@
<html>
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link rel="stylesheet" href="lib/mocha.css" />
<script src="../../dist/html2canvas.js"></script>
<script src="../assets/jquery-1.6.2.js"></script>
<script src="lib/expect.js"></script>
<script src="lib/mocha.js"></script>
<style>
#block {
width: 200px;
height: 200px;
}
#green-block {
width: 200px;
height: 200px;
background: green;
}
#background-block {
width: 200px;
height: 200px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNgaGD4DwAChAGAJVtEDQAAAABJRU5ErkJggg==) red;
}
#gradient-block {
width: 200px;
height: 200px;
background-image: -webkit-linear-gradient(top, #008000, #008000);
background-image: -moz-linear-gradient(to bottom, #008000, #008000);
background-image: linear-gradient(to bottom, #008000, #008000);
}
</style>
</head>
<body>
<div id="mocha"></div>
<script>mocha.setup('bdd')</script>
<div id="block"></div>
<div id="green-block"></div>
<div id="background-block"></div>
<div id="gradient-block"></div>
<script>
describe("options.background", function() {
it("with hexcolor", function(done) {
html2canvas(document.querySelector("#block"), {background: '#008000'}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
it("with named color", function(done) {
html2canvas(document.querySelector("#block"), {background: 'green'}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
it("with element background", function(done) {
html2canvas(document.querySelector("#green-block"), {background: 'red'}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
});
describe('element background', function() {
it('with background-color', function(done) {
html2canvas(document.querySelector("#green-block")).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
it('with background-image', function(done) {
html2canvas(document.querySelector("#background-block")).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
it('with gradient background-image', function(done) {
html2canvas(document.querySelector("#gradient-block")).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
});
function validCanvasPixels(canvas) {
var ctx = canvas.getContext("2d");
var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
for (var i = 0, len = data.length; i < len; i+=4) {
if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
expect().fail("Invalid canvas data");
}
}
}
mocha.checkLeaks();
mocha.globals(['jQuery']);
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
}
else {
mocha.run();
}
mocha.suite.afterAll(function() {
document.body.setAttribute('data-complete', 'true');
});
</script>
</body>
</html>

1
tests/mocha/clone.js Normal file
View File

@@ -0,0 +1 @@
document.querySelector("#block").className += "class";

144
tests/mocha/cropping.html Normal file
View File

@@ -0,0 +1,144 @@
<html>
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link rel="stylesheet" href="lib/mocha.css" />
<script src="../../dist/html2canvas.js"></script>
<script src="../assets/jquery-1.6.2.js"></script>
<script src="lib/expect.js"></script>
<script src="lib/mocha.js"></script>
</head>
<body>
<div id="mocha"></div>
<script>mocha.setup('bdd')</script>
<div style="background: green; width: 40px; height:40px;" id="block"></div>
<div style="visibility: hidden">
<iframe src="iframe1.htm" style="height: 350px; width: 450px; border: 0;" scrolling="no" id="frame1"></iframe>
<iframe src="iframe2.htm" style="height: 350px; width: 450px; border: 0;" scrolling="yes" id="frame2"></iframe>
</div>
<script>
describe("Cropping", function() {
it("window view with body", function(done) {
html2canvas(document.body, {type: 'view'}).then(function(canvas) {
expect(canvas.width).to.equal(window.innerWidth);
expect(canvas.height).to.equal(window.innerHeight);
done();
}).catch(function(error) {
done(error);
});
});
it("window view with documentElement", function(done) {
html2canvas(document.documentElement, {type: 'view'}).then(function(canvas) {
expect(canvas.width).to.equal(window.innerWidth);
expect(canvas.height).to.equal(window.innerHeight);
done();
}).catch(function(error) {
done(error);
});
});
it("iframe with body", function(done) {
html2canvas(document.querySelector("#frame1").contentWindow.document.body, {type: 'view'}).then(function(canvas) {
expect(canvas.width).to.equal(450);
expect(canvas.height).to.equal(350);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
it("iframe with document element", function(done) {
html2canvas(document.querySelector("#frame1").contentWindow.document.documentElement, {type: 'view'}).then(function(canvas) {
expect(canvas.width).to.equal(450);
expect(canvas.height).to.equal(350);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
it("with node", function(done) {
html2canvas(document.querySelector("#block")).then(function(canvas) {
expect(canvas.width).to.equal(40);
expect(canvas.height).to.equal(40);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
it("with node and size", function(done) {
html2canvas(document.querySelector("#block"), {width: 20, height: 20}).then(function(canvas) {
expect(canvas.width).to.equal(20);
expect(canvas.height).to.equal(20);
validCanvasPixels(canvas);
done();
}).catch(function(error) {
done(error);
});
});
document.querySelector("#frame2").addEventListener("load", function() {
document.querySelector("#frame2").contentWindow.scrollTo(0, 350);
describe("with scrolled content", function() {
it("iframe with body", function(done) {
html2canvas(document.querySelector("#frame2").contentWindow.document.body, {type: 'view'}).then(function(canvas) {
// phantomjs issue https://github.com/ariya/phantomjs/issues/10581
if (canvas.height !== 1200) {
expect(canvas.width).to.equal(450);
expect(canvas.height).to.equal(350);
validCanvasPixels(canvas);
}
done();
}).catch(function(error) {
done(error);
});
});
it("iframe with document element", function(done) {
html2canvas(document.querySelector("#frame2").contentWindow.document.documentElement, {type: 'view'}).then(function(canvas) {
// phantomjs issue https://github.com/ariya/phantomjs/issues/10581
if (canvas.height !== 1200) {
expect(canvas.width).to.equal(450);
expect(canvas.height).to.equal(350);
validCanvasPixels(canvas);
}
done();
}).catch(function(error) {
done(error);
});
});
});
}, false);
});
function validCanvasPixels(canvas) {
var ctx = canvas.getContext("2d");
var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
for (var i = 0, len = data.length; i < len; i+=4) {
if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
expect().fail("Invalid canvas data");
}
}
}
mocha.checkLeaks();
mocha.globals(['jQuery']);
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
}
else {
mocha.run();
}
mocha.suite.afterAll(function() {
document.body.setAttribute('data-complete', 'true');
});
</script>
</body>
</html>

218
tests/mocha/css.js Normal file
View File

@@ -0,0 +1,218 @@
var NodeContainer = html2canvas.NodeContainer;
describe('Borders', function() {
$('#borders div').each(function(i, node) {
it($(this).attr('style'), function() {
["borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth"].forEach(function(prop) {
var result = $(node).css(prop);
// older IE's don't necessarily return px even with jQuery
if (result === "thin") {
result = "1px";
} else if (result === "medium") {
result = "3px";
} else if (result === "thick") {
result = "5px";
}
var container = new NodeContainer(node, null);
expect(container.css(prop)).to.be(result);
});
});
});
});
describe('Padding', function() {
$('#padding div').each(function(i, node) {
it($(this).attr('style'), function() {
["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"].forEach(function(prop) {
var container = new NodeContainer(node, null);
var result = container.css(prop);
expect(result).to.contain("px");
expect(result, $(node).css(prop));
});
});
});
});
describe('Background-position', function() {
$('#backgroundPosition div').each(function(i, node) {
it($(this).attr('style'), function() {
var prop = "backgroundPosition";
var img = new Image();
img.width = 50;
img.height = 50;
var container = new NodeContainer(node, null);
var item = container.css(prop),
backgroundPosition = container.parseBackgroundPosition(html2canvas.utils.getBounds(node), img),
split = (window.getComputedStyle) ? $(node).css(prop).split(" ") : [$(node).css(prop+"X"), $(node).css(prop+"Y")];
var testEl = $('<div />').css({
'position': 'absolute',
'left': split[0],
'top': split[1]
});
testEl.appendTo(node);
expect(backgroundPosition.left).to.equal(Math.floor(parseFloat(testEl.css('left'))));
expect(backgroundPosition.top).to.equal(Math.floor(parseFloat(testEl.css('top'))));
testEl.remove();
});
});
});
describe('Text-shadow', function() {
$('#textShadows div').each(function(i, node) {
var index = i + 1;
var container = new NodeContainer(node, null);
var shadows = container.parseTextShadows();
if (i === 0) {
expect(shadows.length).to.equal(0);
} else {
expect(shadows.length).to.equal(i >= 6 ? 2 : 1);
expect(shadows[0].offsetX).to.equal(i);
expect(shadows[0].offsetY).to.equal(i);
if (i < 2) {
expect(shadows[0].color.toString()).to.equal('rgba(0,0,0,0)');
} else if (i % 2 === 0) {
expect(shadows[0].color.toString()).to.equal('rgb(2,2,2)');
} else {
var opacity = '0.2';
expect(shadows[0].color.toString()).to.match(/rgba\(2,2,2,(0.2|0\.199219)\)/);
}
// only testing blur once
if (i === 1) {
expect(shadows[0].blur).to.equal('1');
}
}
});
});
describe('Background-image', function() {
test_parse_background_image(
'url("te)st")',
{
prefix: '',
method: 'url',
value: 'url("te)st")',
args: ['te)st'],
image: null
},
'test quoted'
);
test_parse_background_image(
'url("te,st")',
{
prefix: '',
method: 'url',
value: 'url("te,st")',
args: ['te,st'],
image: null
},
'test quoted'
);
test_parse_background_image(
'url(te,st)',
{
prefix: '',
method: 'url',
value: 'url(te,st)',
args: ['te,st'],
image: null
},
'test quoted'
);
test_parse_background_image(
'url(test)',
{
prefix: '',
method: 'url',
value: 'url(test)',
args: ['test'],
image: null
},
'basic url'
);
test_parse_background_image(
'url("test")',
{
prefix: '',
method: 'url',
value: 'url("test")',
args: ['test'],
image: null
},
'quoted url'
);
test_parse_background_image(
'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
{
prefix: '',
method: 'url',
value: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
args: ['data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'],
image: null
},
'data url'
);
test_parse_background_image(
'linear-gradient(red,black)',
{
prefix: '',
method: 'linear-gradient',
value: 'linear-gradient(red,black)',
args: ['red','black'],
image: null
},
'linear-gradient'
);
test_parse_background_image(
'linear-gradient(top,rgb(255,0,0),rgb(0,0,0))',
{
prefix: '',
method: 'linear-gradient',
value: 'linear-gradient(top,rgb(255,0,0),rgb(0,0,0))',
args: ['top', 'rgb(255,0,0)', 'rgb(0,0,0)'],
image: null
},
'linear-gradient w/ rgb()'
);
test_parse_background_image(
'-webkit-linear-gradient(red,black)',
{
prefix: '-webkit-',
method: 'linear-gradient',
value: '-webkit-linear-gradient(red,black)',
args: ['red','black'],
image: null
},
'prefixed linear-gradient'
);
test_parse_background_image(
'linear-gradient(red,black), url(test), url("test"),\n none, ', [
{ prefix: '', method: 'linear-gradient', value: 'linear-gradient(red,black)', args: ['red','black'], image: null },
{ prefix: '', method: 'url', value: 'url(test)', args: ['test'], image: null },
{ prefix: '', method: 'url', value: 'url("test")', args: ['test'], image: null },
{ prefix: '', method: 'none', value: 'none', args: [], image: null }
],
'multiple backgrounds'
);
function test_parse_background_image(value, expected, name) {
it(name, function() {
expect(html2canvas.utils.parseBackgrounds(value)).to.eql(Array.isArray(expected) ? expected : [expected]);
});
}
});

View File

@@ -0,0 +1,165 @@
<html>
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link rel="stylesheet" href="lib/mocha.css" />
<script src="../../dist/html2canvas.js"></script>
<script src="../assets/jquery-1.6.2.js"></script>
<script src="lib/expect.js"></script>
<script src="lib/mocha.js"></script>
<style>
.block {
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<div id="mocha"></div>
<script>mocha.setup('bdd')</script>
<div id="block1" class="block">
<input type="text" value="text" />
</div>
<div id="block2" class="block">
<input type="password" value="password" />
</div>
<div id="block3" class="block">
<input type="text" value="text" />
</div>
<div id="block4" class="block">
<textarea>text</textarea>
</div>
<div id="block5" class="block">
<select>
<option value="1">1</option>
<option value="2" selected>2</option>
<option value="3">3</option>
</select>
</div>
<div id="green-block"></div>
<script>
var CanvasRenderer = html2canvas.CanvasRenderer;
describe("Rendering input values", function() {
it("uses default value for input[type='text']", function(done) {
CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('text');
};
html2canvas(document.querySelector("#block1"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
done();
}).catch(function(error) {
done(error);
});
});
it("uses transformed value for input[type='password']", function(done) {
var count = 0;
CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('•');
count++;
};
html2canvas(document.querySelector("#block2"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
expect(count).to.equal("password".length);
done();
}).catch(function(error) {
done(error);
});
});
it("used property and not attribute for rendering", function(done) {
document.querySelector("#block3 input").value = 'updated';
CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('updated');
};
html2canvas(document.querySelector("#block3"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
done();
}).catch(function(error) {
done(error);
});
});
describe("Rendering textarea values", function() {
it("uses default value correctly", function(done) {
CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('text');
};
html2canvas(document.querySelector("#block4"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
done();
}).catch(function(error) {
done(error);
});
});
it("used property and not attribute for rendering", function(done) {
document.querySelector("#block4 textarea").value = 'updated';
CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('updated');
};
html2canvas(document.querySelector("#block4"), {renderer: CanvasRenderer, strict: true, logging: true, removeContainer: false}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
done();
}).catch(function(error) {
done(error);
});
});
});
describe("Select values", function() {
it("uses default value correctly", function(done) {
CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('2');
};
html2canvas(document.querySelector("#block5"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
done();
}).catch(function(error) {
done(error);
});
});
it("used property and not attribute for rendering", function(done) {
document.querySelector("#block5 select").value = '3';
CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('3');
};
html2canvas(document.querySelector("#block5"), {renderer: CanvasRenderer, strict: true, logging: true, removeContainer: false}).then(function(canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
done();
}).catch(function(error) {
done(error);
});
});
});
});
mocha.checkLeaks();
mocha.globals(['jQuery']);
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
}
else {
mocha.run();
}
mocha.suite.afterAll(function() {
document.body.setAttribute('data-complete', 'true');
});
</script>
</body>
</html>

112
tests/mocha/gradients.js Normal file
View File

@@ -0,0 +1,112 @@
describe("Gradients", function() {
var expected = [
{
method: "linear-gradient",
args: [
"left",
" rgb(255, 0, 0)",
" rgb(255, 255, 0)",
" rgb(0, 255, 0)"
]
},
{
method: 'linear-gradient',
args: [
"left",
" rgb(206, 219, 233) 0%",
" rgb(170, 197, 222) 17%",
" rgb(97, 153, 199) 50%",
" rgb(58, 132, 195) 51%",
" rgb(65, 154, 214) 59%",
" rgb(75, 184, 240) 71%",
" rgb(58, 139, 194) 84%",
" rgb(38, 85, 139) 100%"
]
},
{
method: "gradient",
args: [
"linear",
" 50% 0%",
" 50% 100%",
" from(rgb(240, 183, 161))",
" color-stop(0.5, rgb(140, 51, 16))",
" color-stop(0.51, rgb(117, 34, 1))",
" to(rgb(191, 110, 78))"
]
},
{
method: "radial-gradient",
args: [
"75% 19%",
" ellipse closest-side",
" rgb(171, 171, 171)",
" rgb(0, 0, 255) 33%",
" rgb(153, 31, 31) 100%"
]
},
{
method: "radial-gradient",
args: [
"75% 19%",
" ellipse closest-corner",
" rgb(171, 171, 171)",
" rgb(0, 0, 255) 33%",
" rgb(153, 31, 31) 100%"
]
},
{
method: "radial-gradient",
args: [
"75% 19%",
" ellipse farthest-side",
" rgb(171, 171, 171)",
" rgb(0, 0, 255) 33%",
" rgb(153, 31, 31) 100%"
]
},
{
method: "radial-gradient",
args: [
"75% 19%",
" ellipse farthest-corner",
" rgb(171, 171, 171)",
" rgb(0, 0, 255) 33%",
" rgb(153, 31, 31) 100%"
]
},
{
method: "radial-gradient",
args: [
"75% 19%",
" ellipse contain",
" rgb(171, 171, 171)",
" rgb(0, 0, 255) 33%",
" rgb(153, 31, 31) 100%"
]
},
{
method: "radial-gradient",
args: [
"75% 19%",
" ellipse cover",
" rgb(171, 171, 171)",
" rgb(0, 0, 255) 33%",
" rgb(153, 31, 31) 100%"
]
}
];
$('#backgroundGradients div').each(function(i, node) {
var container = new html2canvas.NodeContainer(node, null);
var value = container.css("backgroundImage");
it(value, function() {
var parsedBackground = html2canvas.utils.parseBackgrounds(value);
if (parsedBackground[0].args[0] === "0% 50%") {
parsedBackground[0].args[0] = 'left';
}
expect(parsedBackground[0].args).to.eql(expected[i].args);
expect(parsedBackground[0].method).to.eql(expected[i].method);
});
});
});

View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Proxy tests</title>
<link rel="stylesheet" href="lib/mocha.css" />
<script src="../assets/jquery-1.6.2.js"></script>
<script src="lib/expect.js"></script>
<script src="lib/mocha.js"></script>
<style>
#block {
background: red;
}
#block.class {
background: green;
}
</style>
</head>
<body>
<div style="width: 200px; height:200px;" id="block"></div>
<script src="../../dist/html2canvas.js"></script>
<script src="clone.js"></script>
<div id="mocha"></div>
<script>mocha.setup('bdd')</script>
<script>
// https://github.com/niklasvh/html2canvas/issues/503
describe("Document clone should not re-execute javascript", function() {
it("with mutating className", function (done) {
this.timeout(10000);
html2canvas(document.querySelector("#block")).then(function (canvas) {
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
validCanvasPixels(canvas);
done();
}).catch(function (error) {
done(error);
});
});
});
function validCanvasPixels(canvas) {
var ctx = canvas.getContext("2d");
var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
for (var i = 0, len = data.length; i < len; i+=4) {
if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
expect().fail("Invalid canvas data");
}
}
}
mocha.checkLeaks();
mocha.globals(['jQuery']);
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
}
else {
mocha.run();
}
mocha.suite.afterAll(function() {
document.body.setAttribute('data-complete', 'true');
});
</script>
</body>
</html>

23
tests/mocha/iframe1.htm Normal file
View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
html, body {
background: green;
margin: 0;
padding: 0;
}
.invalid {
margin-top: 350px;
height: 500px;
background: red;
display: block;
}
</style>
</head>
<body>
&nbsp;<div class="invalid">&nbsp;</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More