mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Compare commits
87 Commits
v1.0.0-alp
...
v1.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
b89458611b | |||
4e4a231683 | |||
a63cb3c0f1 | |||
7775d3c0d6 | |||
397595afb5 | |||
7027900f49 | |||
49f87fb680 | |||
238de790a9 | |||
029235a652 | |||
44f3d79f68 | |||
7ebef72e92 | |||
2c018d1987 | |||
5cbe5db351 | |||
3212184146 | |||
349bbf137a | |||
c45ef099fe | |||
24823d0491 | |||
41eb8ab22f | |||
4e02a4c7a1 | |||
ce45c1bbdd | |||
078b388974 | |||
3ae7dd2ebb | |||
dab77acde4 | |||
83e7eaa795 | |||
cb7fbcf33e | |||
9a6e57aa00 | |||
a3e25d71cb | |||
9da0f60551 | |||
f347953042 | |||
48b4c20e24 | |||
6341788edf | |||
0e273418c7 | |||
e6bbc1abb5 | |||
13bbc90048 | |||
102b5a1282 | |||
01e4920876 | |||
da2794f7f7 | |||
9fb9898894 | |||
952eb4cf7c | |||
fad4f837c9 | |||
e6c44afca1 | |||
d023de0b99 | |||
0f01810005 | |||
bf03cf5237 | |||
a555dfc085 | |||
69fb48969e | |||
974c35c368 | |||
0fe9632a32 | |||
c9a60c4ff9 | |||
4c14894a0a | |||
8788a9f458 | |||
e198eae398 | |||
474b5e81a7 | |||
b97972eeb6 | |||
b7c7464c5f | |||
ae019f174c | |||
ea6062c85b | |||
9a4a506366 | |||
cb93b80d0d | |||
79e1c857e6 | |||
cc9d1f89dc | |||
d0f7ecfa9a | |||
1870433307 | |||
3a5ed43e97 | |||
8429761e8f | |||
c4e670addf | |||
0aeb54ca2e | |||
eec84fa39e | |||
22f58d5d1c | |||
9046e0d554 | |||
afa5d7cb8e | |||
3881e3cf96 | |||
0aa973ab0d | |||
baaf9b0701 | |||
02de2ee829 | |||
a570f5df74 | |||
38749bc4b6 | |||
e1d6b4c76f | |||
31f2c22477 | |||
6d0cd2d226 | |||
7335984ab7 | |||
78c3c7fc71 | |||
4551976246 | |||
9e04772b42 | |||
54c4002df7 | |||
91641a3746 | |||
0c8d38d9c0 |
13
.babelrc
13
.babelrc
@ -1,4 +1,13 @@
|
||||
{
|
||||
"plugins": ["transform-object-rest-spread"],
|
||||
"presets": ["es2015", "flow"]
|
||||
"presets": [[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"ie": "9"
|
||||
}
|
||||
}
|
||||
], "@babel/preset-flow"],
|
||||
"plugins": [
|
||||
"add-module-exports"
|
||||
]
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[{.travis.yml,package.json}]
|
||||
[{azure-pipelines.yml,package.json}]
|
||||
# The indent size used in the `package.json` file cannot be changed
|
||||
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
||||
indent_size = 2
|
||||
|
@ -1,5 +1,6 @@
|
||||
[ignore]
|
||||
.*/www/.*
|
||||
.*/node_modules/@webassemblyjs/.*
|
||||
[include]
|
||||
[libs]
|
||||
./flow-typed
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
/dist
|
||||
/tmp
|
||||
/build
|
||||
/nbproject/
|
||||
image.jpg
|
||||
|
@ -5,6 +5,7 @@ scripts/
|
||||
src/
|
||||
tests/
|
||||
www/
|
||||
tmp/
|
||||
.github/
|
||||
*.iml
|
||||
.babelrc
|
||||
@ -13,6 +14,8 @@ www/
|
||||
.npmignore
|
||||
.eslintrc
|
||||
.travis.yml
|
||||
azure-pipelines.yml
|
||||
karma.js
|
||||
karma.conf.js
|
||||
rollup.config.js
|
||||
webpack.config.js
|
||||
|
57
.travis.yml
57
.travis.yml
@ -1,57 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '7'
|
||||
env:
|
||||
global:
|
||||
- secure: eW41gIqOizwO4pTgWnAAbW75AP7F+CK9qfSed/fSh4sJ9HWMIY1YRIaY8gjr+6jV/f7XVHcXuym6ZxgINYSkVKbF1JKxBJNLOXtSgNbVHSic58pYFvUjwxIBI9aPig9uux1+DbnpWqXFDTcACJSevQZE0xwmjdrSkDLgB0G34v8=
|
||||
- secure: Y2Av+Gd3z9uQEB36GwdOOuGka0hx0/HeitASEo59z934O8RxnmN9eNTXS7dDT3XtKtwxIyLTOEpS7qlRdWahH28hr/dS4xJj6ao58C+1xMcDs6NAPGmDxUlcJWpcGEsnjmXjQCc3fBioSTdpIBrK/gdvgpNh77UKG74Sk7Z+YGk=
|
||||
addons:
|
||||
chrome: stable
|
||||
firefox: latest
|
||||
dist: trusty
|
||||
sudo: false
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/2b007d4f86de89588804
|
||||
on_success: always
|
||||
on_failure: always
|
||||
on_start: false
|
||||
script:
|
||||
- npm run build
|
||||
- cd www && npm install && npm run build && cd ..
|
||||
- npm test
|
||||
deploy:
|
||||
- provider: npm
|
||||
email: niklasvh@gmail.com
|
||||
api_key:
|
||||
secure: G/Szpr8q4/D6hp+H/Z9yyluUXtHAwf7LLa1Y07X59/Enlj1h7V5fQ7AW4/iAVM3XbIsrCPWR3dJU9g/ZxpxFg4OovIHVpS2Jr/mahtPYWdHR3pWuSmMW8QD+Twnq2VAFwSgg5Oumq3QxhX3YbCOnZox6+6Uviqk8FO7Z5B0RwW4=
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
||||
repo: niklasvh/html2canvas
|
||||
- provider: releases
|
||||
api_key:
|
||||
secure: "PowO/Jat660k3gHcjgI6DlJz15RM7pLUu11UPsLCtYJ8ZwodppE6Keg0DfVkSFSIZttZor+UssDwP/WOEqfZNLqmXbcj3Gec4xolohet/GOe0KJKKuF/HgggbcxumopxMX6sMVePlMBpkLpHh7tgEAEHBWTlzC1c1a7Xa48fZ7k="
|
||||
file:
|
||||
- "dist/html2canvas.js"
|
||||
- "dist/html2canvas.min.js"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
||||
repo: niklasvh/html2canvas
|
||||
- provider: pages
|
||||
skip_cleanup: true
|
||||
local_dir: www/public
|
||||
target_branch: gh-pages-test
|
||||
fqdn: html2canvas.hertzen.com
|
||||
github_token:
|
||||
secure: "PowO/Jat660k3gHcjgI6DlJz15RM7pLUu11UPsLCtYJ8ZwodppE6Keg0DfVkSFSIZttZor+UssDwP/WOEqfZNLqmXbcj3Gec4xolohet/GOe0KJKKuF/HgggbcxumopxMX6sMVePlMBpkLpHh7tgEAEHBWTlzC1c1a7Xa48fZ7k="
|
||||
on:
|
||||
branch: master
|
||||
repo: niklasvh/html2canvas
|
119
CHANGELOG.md
119
CHANGELOG.md
@ -1,29 +1,114 @@
|
||||
### Changelog ###
|
||||
# Change Log
|
||||
|
||||
#### v1.0.0-alpha4 - 12.12.2017 ####
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
# [1.0.0-rc.1](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.0...v1.0.0-rc.1) (2019-04-10)
|
||||
|
||||
|
||||
### ci
|
||||
|
||||
* add ios simulator tests (#1794) ([a63cb3c0f132b1af915d9ef55a4c174f6e5502ce](https://github.com/niklasvh/html2canvas/commit/a63cb3c0f132b1af915d9ef55a4c174f6e5502ce)), closes [#1794](https://github.com/niklasvh/html2canvas/issues/1794)
|
||||
|
||||
### docs
|
||||
|
||||
* fix release date in changelog ([238de790a9f223becbc8726633c0f2a2dabf2cb7](https://github.com/niklasvh/html2canvas/commit/238de790a9f223becbc8726633c0f2a2dabf2cb7))
|
||||
* remove invalid `async` option from docs (fix #1769) (#1796) ([7775d3c0d6f3efca00611bedd5fc9200689a9f7a](https://github.com/niklasvh/html2canvas/commit/7775d3c0d6f3efca00611bedd5fc9200689a9f7a)), closes [#1769](https://github.com/niklasvh/html2canvas/issues/1769) [#1796](https://github.com/niklasvh/html2canvas/issues/1796)
|
||||
|
||||
### fix
|
||||
|
||||
* context scale for high resolution displays with foreignobjectrendering (#1782) ([7027900f4993dcd00745a4db045ed1c0e3255f8a](https://github.com/niklasvh/html2canvas/commit/7027900f4993dcd00745a4db045ed1c0e3255f8a)), closes [#1782](https://github.com/niklasvh/html2canvas/issues/1782)
|
||||
* don't apply text shadows on elements (#1795) ([397595afb59ee50f0d128abb5945b5b9ddc6650d](https://github.com/niklasvh/html2canvas/commit/397595afb59ee50f0d128abb5945b5b9ddc6650d)), closes [#1795](https://github.com/niklasvh/html2canvas/issues/1795)
|
||||
* safari data url taints (#1797) ([4e4a231683904dfdc1f82472ece5a160a158dbb8](https://github.com/niklasvh/html2canvas/commit/4e4a231683904dfdc1f82472ece5a160a158dbb8)), closes [#1797](https://github.com/niklasvh/html2canvas/issues/1797)
|
||||
|
||||
### test
|
||||
|
||||
* fix RefTestRenderer.js inclusion with karma ([49f87fb680dbfe1898b3aeb60f2f5c3a93bfbe6d](https://github.com/niklasvh/html2canvas/commit/49f87fb680dbfe1898b3aeb60f2f5c3a93bfbe6d))
|
||||
|
||||
|
||||
|
||||
# [1.0.0-rc.0](https://github.com/niklasvh/html2canvas/compare/v1.0.0-alpha.12...v1.0.0-rc.0) (2019-04-07)
|
||||
|
||||
|
||||
### build
|
||||
|
||||
* update webpack and babel (#1793) ([44f3d79f68836624c2673a86f9ad47c17ef843c3](https://github.com/niklasvh/html2canvas/commit/44f3d79f68836624c2673a86f9ad47c17ef843c3)), closes [#1793](https://github.com/niklasvh/html2canvas/issues/1793)
|
||||
|
||||
### ci
|
||||
|
||||
* automate changelog generation (#1792) ([7ebef72e927eaafd34a1792ece431d2a73109230](https://github.com/niklasvh/html2canvas/commit/7ebef72e927eaafd34a1792ece431d2a73109230)), closes [#1792](https://github.com/niklasvh/html2canvas/issues/1792)
|
||||
* Improve CI pipeline (#1790) ([c45ef099fe8f7142e174f4fce39448a370a987d5](https://github.com/niklasvh/html2canvas/commit/c45ef099fe8f7142e174f4fce39448a370a987d5)), closes [#1790](https://github.com/niklasvh/html2canvas/issues/1790)
|
||||
|
||||
### docs
|
||||
|
||||
* improve canvas size limit documentation (#1576) ([3212184146b33c3564c2f416e1bfda911737c38b](https://github.com/niklasvh/html2canvas/commit/3212184146b33c3564c2f416e1bfda911737c38b)), closes [#1576](https://github.com/niklasvh/html2canvas/issues/1576)
|
||||
|
||||
### fix
|
||||
|
||||
* enforce colorstop min 0 (#1743) ([349bbf137abd83464e074db3948fc79a541c2ef3](https://github.com/niklasvh/html2canvas/commit/349bbf137abd83464e074db3948fc79a541c2ef3)), closes [#1743](https://github.com/niklasvh/html2canvas/issues/1743)
|
||||
* prevent unhandled promise rejections for hidden frames (#1762) ([5cbe5db35155e3a9790a30de09feb17843053b7a](https://github.com/niklasvh/html2canvas/commit/5cbe5db35155e3a9790a30de09feb17843053b7a)), closes [#1762](https://github.com/niklasvh/html2canvas/issues/1762)
|
||||
* wrap .sheet.cssRules access in try...catch. (#1693) ([2c018d19875ced30caafdc40f84ca531de6e6f91](https://github.com/niklasvh/html2canvas/commit/2c018d19875ced30caafdc40f84ca531de6e6f91)), closes [#1693](https://github.com/niklasvh/html2canvas/issues/1693)
|
||||
|
||||
|
||||
|
||||
# [1.0.0-alpha.12](https://github.com/niklasvh/html2canvas/compare/v1.0.0-alpha.12...v1.0.0-alpha.13) (2018-04-05)
|
||||
* Fix white space appearing on element rendering (Fix #1438)
|
||||
* Reset canvas transform on finish (Fix #1494)
|
||||
|
||||
# v1.0.0-alpha.11 - 1.4.2018
|
||||
* Fix IE11 member not found error
|
||||
* Support blob image resources in non-foreignObjectRendering mode
|
||||
|
||||
# v1.0.0-alpha.10 - 15.2.2018
|
||||
* Re-introduce `onclone` option (Fix #1434)
|
||||
* Add `ignoreElements` predicate function option
|
||||
* Fix version console logging
|
||||
|
||||
# v1.0.0-alpha.9 - 7.1.2018
|
||||
* Fix dynamic style sheets
|
||||
* Fix > 50% border-radius values
|
||||
|
||||
# v1.0.0-alpha.8 - 2.1.2018
|
||||
* Use correct doctype in cloned Document (Fix #1298)
|
||||
* Fix individual border rendering (Fix #1349)
|
||||
|
||||
# v1.0.0-alpha.7 - 31.12.2017
|
||||
* Fix form input rendering (#1338)
|
||||
* Improve word line breaking algorithm
|
||||
|
||||
# v1.0.0-alpha.6 - 28.12.2017
|
||||
* Fix list-style: none (#1340)
|
||||
* Extend supported values for pseudo element content
|
||||
|
||||
# v1.0.0-alpha.5 - 21.12.2017
|
||||
* Fix underline positioning
|
||||
* Fix canvas rendering on Chrome
|
||||
* Fix overflow: auto
|
||||
* Added support for rendering list-style
|
||||
|
||||
v1.0.0-alpha.4 - 12.12.2017
|
||||
* Fix rendering with multiple fonts defined (Fix #796)
|
||||
* Add support for radial-gradients
|
||||
* Fix logging option (#1302)
|
||||
* Add support for rendering webgl canvas content (#646)
|
||||
* Fix external SVG loading with proxies (#802)
|
||||
|
||||
#### v1.0.0-alpha3 - 9.12.2017 ####
|
||||
# v1.0.0-alpha.3 - 9.12.2017
|
||||
* Disable `foreignObjectRendering` by default (#1295)
|
||||
* Fix background-size when using background-origin and background-size: cover/contain (#1299)
|
||||
* Added support for background-origin: content-box (#1299)
|
||||
|
||||
#### v1.0.0-alpha2 - 7.12.2017 ####
|
||||
# v1.0.0-alpha.2 - 7.12.2017
|
||||
* Fix scroll positions for CanvasRenderer (#1259)
|
||||
* Fix `data-html2canvas-ignore` attribute (#1253)
|
||||
* Fix decimal `letter-spacing` values (#1293)
|
||||
|
||||
#### v1.0.0-alpha1 - 5.12.2017 ####
|
||||
# v1.0.0-alpha.1 - 5.12.2017
|
||||
* Complete rewrite of library
|
||||
##### Breaking Changes #####
|
||||
* Remove deprecated onrendered callback, calling `html2canvas` returns a `Promise<HTMLCanvasElement>`
|
||||
* Removed option `type`, same results can be achieved by assigning `x`, `y`, `scrollX`, `scrollY`, `width` and `height` properties.
|
||||
|
||||
##### New featues / fixes #####
|
||||
## New featues / fixes
|
||||
* Add support for scaling canvas (defaults to device pixel ratio)
|
||||
* Add support for multiple text-shadows
|
||||
* Add support for multiple text-decorations
|
||||
@ -33,18 +118,18 @@
|
||||
* Correctly support all angle types for linear-gradients
|
||||
* Add support for multiple values for background-repeat, background-position and background-size
|
||||
|
||||
#### v0.5.0-beta4 - 23.1.2016 ####
|
||||
# v0.5.0-beta4 - 23.1.2016
|
||||
* Fix logger requiring access to window object
|
||||
* Derequire browserify build
|
||||
* Fix rendering of specific elements when window is scrolled and `type` isn't set to `view`
|
||||
|
||||
#### v0.5.0-beta3 - 6.12.2015 ####
|
||||
# v0.5.0-beta3 - 6.12.2015
|
||||
* Handle color names in linear gradients
|
||||
|
||||
#### v0.5.0-beta2 - 20.10.2015 ####
|
||||
# v0.5.0-beta2 - 20.10.2015
|
||||
* Remove Promise polyfill (use native or provide it yourself)
|
||||
|
||||
#### v0.5.0-beta1 - 19.10.2015 ####
|
||||
# v0.5.0-beta1 - 19.10.2015
|
||||
* Fix bug with unmatched color stops in gradients
|
||||
* Fix scrolling issues with iOS
|
||||
* Correctly handle named colors in gradients
|
||||
@ -52,11 +137,11 @@
|
||||
* Fix transparent colors breaking gradients
|
||||
* Preserve scrolling positions on render
|
||||
|
||||
#### v0.5.0-alpha2 - 3.2.2015 ####
|
||||
# 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####
|
||||
# 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.
|
||||
@ -67,14 +152,14 @@
|
||||
* 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 ####
|
||||
# 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 ####
|
||||
# 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
|
||||
@ -84,7 +169,7 @@
|
||||
* Support for placeholder rendering
|
||||
* Reformatted all tests to small units to test specific features
|
||||
|
||||
#### v0.3.4 - 26.6.2012 ####
|
||||
# v0.3.4 - 26.6.2012
|
||||
|
||||
* Removed (last?) jQuery dependencies (<a href="https://github.com/niklasvh/html2canvas/commit/343b86705fe163766fcf735eb0217130e4bd5b17">niklasvh</a>)
|
||||
* SVG-powered rendering (<a href="https://github.com/niklasvh/html2canvas/commit/67d3e0d0f59a5a654caf71a2e3be6494ff146c75">niklasvh</a>)
|
||||
@ -92,7 +177,7 @@
|
||||
* 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 ####
|
||||
# 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>)
|
||||
@ -100,7 +185,7 @@
|
||||
* 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 ####
|
||||
# v0.3.2 - 20.2.2012
|
||||
|
||||
* Added changelog!
|
||||
* Added bookmarklet (<a href="https://github.com/niklasvh/html2canvas/commit/b320dd306e1a2d32a3bc5a71b6ebf6d8c060cde5">cobexer</a>)
|
||||
|
@ -3,7 +3,10 @@ html2canvas
|
||||
|
||||
[Homepage](https://html2canvas.hertzen.com) | [Downloads](https://github.com/niklasvh/html2canvas/releases) | [Questions](http://stackoverflow.com/questions/tagged/html2canvas?sort=newest) | [Donate](https://www.gittip.com/niklasvh/)
|
||||
|
||||
[](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://travis-ci.org/niklasvh/html2canvas)
|
||||
[](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://dev.azure.com/niklasvh/html2canvas/_build/latest?definitionId=1&branchName=master)
|
||||
[](https://www.npmjs.org/package/html2canvas)
|
||||
[](https://www.npmjs.org/package/html2canvas)
|
||||
|
||||
#### JavaScript HTML renderer ####
|
||||
|
||||
|
430
azure-pipelines.yml
Normal file
430
azure-pipelines.yml
Normal file
@ -0,0 +1,430 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
- job: Build
|
||||
displayName: Build
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- script: npm run build
|
||||
displayName: Build
|
||||
- script: |
|
||||
npm pack
|
||||
mv html2canvas-*.tgz html2canvas.tgz
|
||||
tar --list --verbose --file=html2canvas.tgz
|
||||
displayName: Pack
|
||||
name: pack
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: html2canvas.tgz
|
||||
artifactName: npm
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: 'dist'
|
||||
artifactName: dist
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: 'build'
|
||||
artifactName: build
|
||||
- job: Test
|
||||
displayName: Tests
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- script: npm run build
|
||||
displayName: Build
|
||||
- script: npm run lint
|
||||
displayName: Lint
|
||||
- script: npm run flow
|
||||
displayName: Flow
|
||||
- script: npm run test:node
|
||||
displayName: Unit tests
|
||||
- job: Build_docs
|
||||
displayName: Build docs
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- script: npm run build && cd www && npm install && npm run build && cd ..
|
||||
displayName: Build docs
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload docs website artifact
|
||||
inputs:
|
||||
PathtoPublish: 'www/public'
|
||||
artifactName: docs
|
||||
- job: Browser_Tests_Linux_Firefox_Stable
|
||||
displayName: Linux Firefox Stable
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
variables:
|
||||
TARGET_BROWSER: Firefox_Stable
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: Xvfb :99 &
|
||||
displayName: 'Start Xvfb'
|
||||
- script: DISPLAY=:99 npm run karma
|
||||
displayName: 'Run Firefox tests - Firefox Stable'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
||||
- job: Browser_Tests_Linux_Chrome_Stable
|
||||
displayName: Linux Chrome Stable
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
variables:
|
||||
TARGET_BROWSER: Chrome_Stable
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: Xvfb :99 &
|
||||
displayName: 'Start Xvfb'
|
||||
- script: DISPLAY=:99 npm run karma
|
||||
displayName: 'Run Chrome tests - Chrome Stable'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
||||
- job: Browser_Tests_OSX_Safari_IOS_9
|
||||
displayName: iOS Simulator Safari 9
|
||||
pool:
|
||||
vmImage: 'macOS-10.13'
|
||||
variables:
|
||||
TARGET_BROWSER: Safari_IOS_9
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: npm run karma
|
||||
displayName: 'Run Safari tests - Safari IOS'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
||||
- job: Browser_Tests_OSX_Safari_IOS_10
|
||||
displayName: iOS Simulator Safari 10
|
||||
pool:
|
||||
vmImage: 'macOS-10.13'
|
||||
variables:
|
||||
TARGET_BROWSER: Safari_IOS_10
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: npm run karma
|
||||
displayName: 'Run Safari tests - Safari IOS'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
||||
- job: Browser_Tests_OSX_Safari_IOS_11
|
||||
displayName: iOS Simulator Safari 11
|
||||
pool:
|
||||
vmImage: 'macOS-10.13'
|
||||
variables:
|
||||
TARGET_BROWSER: Safari_IOS_11
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: npm run karma
|
||||
displayName: 'Run Safari tests - Safari IOS'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
||||
- job: Browser_Tests_OSX_Safari_Stable
|
||||
displayName: OSX Safari Stable
|
||||
pool:
|
||||
vmImage: 'macOS-10.13'
|
||||
variables:
|
||||
TARGET_BROWSER: Safari_Stable
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: npm run karma
|
||||
displayName: 'Run Safari tests - Safari Stable'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
||||
- job: Browser_Tests_Windows_IE9
|
||||
displayName: Windows Internet Explorer 9 (Emulated)
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
variables:
|
||||
TARGET_BROWSER: IE_9
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: npm run karma
|
||||
displayName: 'Run Internet Explorer tests - IE 9'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
||||
- job: Browser_Tests_Windows_IE10
|
||||
displayName: Windows Internet Explorer 10 (Emulated)
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
variables:
|
||||
TARGET_BROWSER: IE_10
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: npm run karma
|
||||
displayName: 'Run Internet Explorer tests - IE 10'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
||||
- job: Browser_Tests_Windows_IE11
|
||||
displayName: Windows Internet Explorer 11
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
variables:
|
||||
TARGET_BROWSER: IE_11
|
||||
dependsOn: Build
|
||||
condition: succeeded()
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: Npm@0
|
||||
inputs:
|
||||
command: install
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download library'
|
||||
inputs:
|
||||
artifactName: dist
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- task: DownloadBuildArtifacts@0
|
||||
displayName: 'Download testrunner'
|
||||
inputs:
|
||||
artifactName: build
|
||||
downloadPath: $(System.DefaultWorkingDirectory)
|
||||
- script: npm run karma
|
||||
displayName: 'Run Internet Explorer tests - IE 11'
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testRunner: JUnit
|
||||
testResultsFiles: 'tmp/junit/*.xml'
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: Upload Screenshots
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
PathtoPublish: 'tmp/reftests'
|
||||
artifactName: ReftestResults
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "Configuration"
|
||||
title: "Options"
|
||||
description: "Explore the different configuration options available for html2canvas"
|
||||
previousUrl: "/getting-started"
|
||||
previousTitle: "Getting Started"
|
||||
@ -11,16 +11,18 @@ These are all of the available configuration options.
|
||||
|
||||
| Name | Default | Description |
|
||||
| ------------- | :------: | ----------- |
|
||||
| async | `true` | Whether to parse and render the element asynchronously
|
||||
| allowTaint | `false` | Whether to allow cross-origin images to taint the canvas
|
||||
| backgroundColor | `#ffffff` | Canvas background color, if none is specified in DOM. Set `null` for transparent
|
||||
| canvas | `null` | Existing `canvas` element to use as a base for drawing on
|
||||
| foreignObjectRendering | `false` | Whether to use ForeignObject rendering if the browser supports it
|
||||
| imageTimeout | `15000` | Timeout for loading an image (in milliseconds). Set to `0` to disable timeout.
|
||||
| ignoreElements | `(element) => false` | Predicate function which removes the matching elements from the render.
|
||||
| logging | `true` | Enable logging for debug purposes
|
||||
| onclone | `null` | Callback function which is called when the Document has been cloned for rendering, can be used to modify the contents that will be rendered without affecting the original source document.
|
||||
| proxy | `null` | Url to the [proxy](/proxy/) which is to be used for loading cross-origin images. If left empty, cross-origin images won't be loaded.
|
||||
| removeContainer | `true` | Whether to cleanup the cloned DOM elements html2canvas creates temporarily
|
||||
| scale | `window.devicePixelRatio` | The scale to use for rendering. Defaults to the browsers device pixel ratio.
|
||||
| useCORS | `false` | Whether to attempt to load images from a server using CORS
|
||||
| width | `Element` width | The width of the `canvas`
|
||||
| height | `Element` height | The height of the `canvas`
|
||||
| x | `Element` x-offset | Crop canvas x-coordinate
|
||||
@ -29,3 +31,5 @@ These are all of the available configuration options.
|
||||
| scrollY | `Element` scrollY | The y-scroll position to used when rendering element, (for example if the Element uses `position: fixed`)
|
||||
| windowWidth | `Window.innerWidth` | Window width to use when rendering `Element`, which may affect things like Media queries
|
||||
| windowHeight | `Window.innerHeight` | Window height to use when rendering `Element`, which may affect things like Media queries
|
||||
|
||||
If you wish to exclude certain `Element`s from getting rendered, you can add a `data-html2canvas-ignore` attribute to those elements and html2canvas will exclude them from the rendering.
|
||||
|
10
docs/faq.md
10
docs/faq.md
@ -13,8 +13,14 @@ methods to check whether an image would taint the canvas before applying it. If
|
||||
If you wish to load images that reside outside of your pages origin, you can use a [proxy](/proxy) to load the images.
|
||||
|
||||
## Why is the produced canvas empty or cuts off half way through?
|
||||
Make sure that `canvas` element doesn't hit [browser limitations](https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element) for the `canvas` size.
|
||||
The limitations vary by browser, operating system and system hardware.
|
||||
Make sure that `canvas` element doesn't hit [browser limitations](https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element) for the `canvas` size or use the window configuration options to set a custom window size based on the `canvas` element:
|
||||
```
|
||||
await html2canvas(element, {
|
||||
windowWidth: element.scrollWidth,
|
||||
windowHeight: element.scrollHeight
|
||||
});
|
||||
```
|
||||
The window limitations vary by browser, operating system and system hardware.
|
||||
|
||||
### Chrome
|
||||
> Maximum height/width: 32,767 pixels
|
||||
|
@ -22,7 +22,7 @@ Below is a list of all the supported CSS properties and values.
|
||||
- border-width
|
||||
- bottom
|
||||
- box-sizing
|
||||
- content (**Does not support `attr()`**)
|
||||
- content
|
||||
- color
|
||||
- display
|
||||
- flex
|
||||
@ -36,6 +36,11 @@ Below is a list of all the supported CSS properties and values.
|
||||
- height
|
||||
- left
|
||||
- letter-spacing
|
||||
- line-break
|
||||
- list-style
|
||||
- list-style-image
|
||||
- list-style-position
|
||||
- list-style-type
|
||||
- margin
|
||||
- max-height
|
||||
- max-width
|
||||
@ -43,6 +48,7 @@ Below is a list of all the supported CSS properties and values.
|
||||
- min-width
|
||||
- opacity
|
||||
- overflow
|
||||
- overflow-wrap
|
||||
- padding
|
||||
- position
|
||||
- right
|
||||
@ -58,7 +64,9 @@ Below is a list of all the supported CSS properties and values.
|
||||
- visibility
|
||||
- white-space
|
||||
- width
|
||||
- word-break
|
||||
- word-spacing
|
||||
- word-wrap
|
||||
- z-index
|
||||
|
||||
## Unsupported CSS properties
|
||||
@ -71,10 +79,7 @@ These CSS properties are **NOT** currently supported
|
||||
- [font-variant-ligatures](https://github.com/niklasvh/html2canvas/pull/1085)
|
||||
- [mix-blend-mode](https://github.com/niklasvh/html2canvas/issues/580)
|
||||
- [object-fit](https://github.com/niklasvh/html2canvas/issues/1064)
|
||||
- [list-style](https://github.com/niklasvh/html2canvas/issues/177)
|
||||
- [repeating-linear-gradient()](https://github.com/niklasvh/html2canvas/issues/1162)
|
||||
- word-break
|
||||
- [word-wrap](https://github.com/niklasvh/html2canvas/issues/664)
|
||||
- [writing-mode](https://github.com/niklasvh/html2canvas/issues/1258)
|
||||
- [zoom](https://github.com/niklasvh/html2canvas/issues/732)
|
||||
|
||||
|
163
karma.conf.js
163
karma.conf.js
@ -1,89 +1,163 @@
|
||||
// Karma configuration
|
||||
// Generated on Sat Aug 05 2017 23:42:26 GMT+0800 (Malay Peninsula Standard Time)
|
||||
|
||||
const path = require('path');
|
||||
const simctl = require('node-simctl');
|
||||
const iosSimulator = require('appium-ios-simulator');
|
||||
const port = 9876;
|
||||
|
||||
const log = require('karma/lib/logger').create('launcher:MobileSafari');
|
||||
|
||||
module.exports = function(config) {
|
||||
const slLaunchers = (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) ? {} : {
|
||||
sl_beta_chrome: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
platform: 'Windows 10',
|
||||
version: 'beta'
|
||||
const launchers = {
|
||||
Safari_IOS_9: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
sdk: '9.0'
|
||||
},
|
||||
sl_ie9: {
|
||||
Safari_IOS_10: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
sdk: '10.0'
|
||||
},
|
||||
Safari_IOS_11: {
|
||||
base: 'MobileSafari',
|
||||
name: 'iPhone 5s',
|
||||
sdk: '11.4'
|
||||
},
|
||||
SauceLabs_IE9: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '9.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
sl_ie10: {
|
||||
SauceLabs_IE10: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '10.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
sl_ie11: {
|
||||
SauceLabs_IE11: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
version: '11.0',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
sl_edge_15: {
|
||||
SauceLabs_Edge18: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'MicrosoftEdge',
|
||||
version: '15.15063',
|
||||
version: '18.17763',
|
||||
platform: 'Windows 10'
|
||||
},
|
||||
sl_edge_14: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'MicrosoftEdge',
|
||||
version: '14.14393',
|
||||
platform: 'Windows 10'
|
||||
},
|
||||
sl_safari: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
version: '10.1',
|
||||
platform: 'macOS 10.12'
|
||||
},
|
||||
'sl_android_4.4': {
|
||||
SauceLabs_Android4: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Browser',
|
||||
platform: 'Android',
|
||||
version: '4.4',
|
||||
device: 'Android Emulator',
|
||||
},
|
||||
'sl_ios_10.3_safari': {
|
||||
SauceLabs_iOS10_3: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Safari',
|
||||
platform: 'iOS',
|
||||
version: '10.3',
|
||||
device: 'iPhone 7 Plus Simulator'
|
||||
},
|
||||
'sl_ios_9.3_safari': {
|
||||
SauceLabs_iOS9_3: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Safari',
|
||||
platform: 'iOS',
|
||||
version: '9.3',
|
||||
device: 'iPhone 6 Plus Simulator'
|
||||
},
|
||||
'sl_ios_8.4_safari': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Safari',
|
||||
platform: 'iOS',
|
||||
version: '8.4',
|
||||
device: 'iPhone 5s Simulator'
|
||||
IE_9: {
|
||||
base: 'IE',
|
||||
'x-ua-compatible': 'IE=EmulateIE9',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
IE_10: {
|
||||
base: 'IE',
|
||||
'x-ua-compatible': 'IE=EmulateIE10',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
IE_11: {
|
||||
base: 'IE',
|
||||
flags: ['-extoff']
|
||||
},
|
||||
Safari_Stable: {
|
||||
base: 'Safari'
|
||||
},
|
||||
Chrome_Stable: {
|
||||
base: 'Chrome'
|
||||
},
|
||||
Firefox_Stable: {
|
||||
base: 'Firefox'
|
||||
}
|
||||
};
|
||||
|
||||
const customLaunchers = Object.assign({}, slLaunchers, {
|
||||
const ciLauncher = launchers[process.env.TARGET_BROWSER];
|
||||
|
||||
const customLaunchers = ciLauncher ? {target_browser: ciLauncher} : {
|
||||
stable_chrome: {
|
||||
base: 'Chrome'
|
||||
},
|
||||
stable_firefox: {
|
||||
base: 'Firefox'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const injectTypedArrayPolyfills = function(files) {
|
||||
files.unshift({
|
||||
pattern: path.resolve(__dirname, './node_modules/js-polyfills/typedarray.js'),
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false
|
||||
});
|
||||
};
|
||||
|
||||
injectTypedArrayPolyfills.$inject = ['config.files'];
|
||||
|
||||
const MobileSafari = function(baseBrowserDecorator, args) {
|
||||
if(process.platform !== "darwin"){
|
||||
log.error("This launcher only works in MacOS.");
|
||||
this._process.kill();
|
||||
return;
|
||||
}
|
||||
baseBrowserDecorator(this);
|
||||
this.on('start', url => {
|
||||
simctl.getDevices().then(devices => {
|
||||
const d = devices[args.sdk].find(d => {
|
||||
return d.name === args.name;
|
||||
});
|
||||
|
||||
if (!d) {
|
||||
log.error(`No device found for sdk ${args.sdk} with name ${args.name}`);
|
||||
this._process.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
return iosSimulator.getSimulator(d.udid).then(device => {
|
||||
return simctl.bootDevice(d.udid).then(() => device);
|
||||
}).then(device => {
|
||||
return device.waitForBoot(60 * 5 * 1000).then(() => {
|
||||
return device.openUrl(url);
|
||||
});
|
||||
});
|
||||
}).catch(e => {
|
||||
console.log('err,', e);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
MobileSafari.prototype = {
|
||||
name: 'MobileSafari',
|
||||
DEFAULT_CMD: {
|
||||
darwin: '/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator',
|
||||
},
|
||||
ENV_CMD: null,
|
||||
};
|
||||
|
||||
MobileSafari.$inject = ['baseBrowserDecorator', 'args'];
|
||||
|
||||
config.set({
|
||||
|
||||
@ -93,17 +167,26 @@ module.exports = function(config) {
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['mocha'],
|
||||
|
||||
frameworks: ['mocha', 'inline-mocha-fix'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'build/testrunner.js',
|
||||
'build/RefTestRenderer.js',
|
||||
{ pattern: './tests/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
{ pattern: './dist/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
{ pattern: './node_modules/**/*', 'watched': true, 'included': false, 'served': true}
|
||||
{ pattern: './node_modules/**/*', 'watched': true, 'included': false, 'served': true},
|
||||
],
|
||||
|
||||
plugins: [
|
||||
'karma-*',
|
||||
{
|
||||
'framework:inline-mocha-fix': ['factory', injectTypedArrayPolyfills]
|
||||
},
|
||||
{
|
||||
'launcher:MobileSafari': ['type', MobileSafari]
|
||||
}
|
||||
],
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
@ -119,7 +202,11 @@ module.exports = function(config) {
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['progress', 'saucelabs'],
|
||||
reporters: ['dots', 'junit'],
|
||||
|
||||
junitReporter: {
|
||||
outputDir: 'tmp/junit/'
|
||||
},
|
||||
|
||||
// web server port
|
||||
port,
|
||||
|
10
karma.js
10
karma.js
@ -14,6 +14,12 @@ const bodyParser = require('body-parser');
|
||||
const cors = require('cors');
|
||||
const filenamifyUrl = require('filenamify-url');
|
||||
|
||||
const mkdirp = require('mkdirp');
|
||||
const screenshotFolder = './tmp/reftests';
|
||||
|
||||
mkdirp.sync(path.resolve(__dirname, screenshotFolder));
|
||||
|
||||
|
||||
const CORS_PORT = 8081;
|
||||
const corsApp = express();
|
||||
corsApp.use('/proxy', proxy());
|
||||
@ -59,9 +65,9 @@ const writeScreenshot = (buffer, body) => {
|
||||
const filename = `${filenamifyUrl(
|
||||
body.test.replace(/^\/tests\/reftests\//, '').replace(/\.html$/, ''),
|
||||
{replacement: '-'}
|
||||
)}!${body.platform.name}-${body.platform.version}.png`;
|
||||
)}!${[process.env.TARGET_BROWSER, body.platform.name, body.platform.version].join('-')}.png`;
|
||||
|
||||
fs.writeFileSync(path.resolve(__dirname, './tests/results/', filename), buffer);
|
||||
fs.writeFileSync(path.resolve(__dirname, screenshotFolder, filename), buffer);
|
||||
};
|
||||
|
||||
app.post('/screenshot', (req, res) => {
|
||||
|
15037
package-lock.json
generated
15037
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
@ -3,7 +3,9 @@
|
||||
"name": "html2canvas",
|
||||
"description": "Screenshots with JavaScript",
|
||||
"main": "dist/npm/index.js",
|
||||
"version": "1.0.0-alpha.4",
|
||||
"module": "dist/html2canvas.js",
|
||||
"browser": "dist/html2canvas.js",
|
||||
"version": "1.0.0-rc.1",
|
||||
"author": {
|
||||
"name": "Niklas von Hertzen",
|
||||
"email": "niklasvh@gmail.com",
|
||||
@ -20,52 +22,60 @@
|
||||
"url": "https://github.com/niklasvh/html2canvas/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "6.24.1",
|
||||
"babel-core": "6.25.0",
|
||||
"babel-eslint": "7.2.3",
|
||||
"babel-loader": "7.1.1",
|
||||
"babel-plugin-dev-expression": "0.2.1",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "6.26.0",
|
||||
"babel-plugin-transform-object-rest-spread": "6.23.0",
|
||||
"babel-preset-es2015": "6.24.1",
|
||||
"babel-preset-flow": "6.23.0",
|
||||
"@babel/cli": "^7.4.3",
|
||||
"@babel/core": "^7.4.3",
|
||||
"@babel/preset-env": "^7.4.3",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"appium-ios-simulator": "^3.10.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.5",
|
||||
"babel-plugin-add-module-exports": "^1.0.0",
|
||||
"babel-plugin-dev-expression": "^0.2.1",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"body-parser": "1.17.2",
|
||||
"body-parser": "^1.18.3",
|
||||
"chai": "4.1.1",
|
||||
"chromeless": "1.2.0",
|
||||
"chromeless": "^1.5.2",
|
||||
"cors": "2.8.4",
|
||||
"eslint": "4.2.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-flowtype": "2.35.0",
|
||||
"eslint-plugin-prettier": "2.1.2",
|
||||
"express": "4.15.4",
|
||||
"express": "^4.16.4",
|
||||
"filenamify-url": "1.0.0",
|
||||
"flow-bin": "0.56.0",
|
||||
"glob": "7.1.2",
|
||||
"html2canvas-proxy": "1.0.0",
|
||||
"html2canvas-proxy": "1.0.1",
|
||||
"jquery": "3.2.1",
|
||||
"karma": "1.7.0",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-edge-launcher": "0.4.1",
|
||||
"karma-firefox-launcher": "1.0.1",
|
||||
"karma-ie-launcher": "1.0.0",
|
||||
"karma-mocha": "1.3.0",
|
||||
"karma-sauce-launcher": "1.1.0",
|
||||
"mocha": "3.5.0",
|
||||
"js-polyfills": "^0.1.42",
|
||||
"karma": "^4.0.1",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-edge-launcher": "^0.4.2",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-junit-reporter": "^1.2.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-safari-launcher": "^1.0.0",
|
||||
"karma-sauce-launcher": "^2.0.2",
|
||||
"mocha": "^6.1.0",
|
||||
"node-simctl": "^5.0.0",
|
||||
"platform": "1.3.4",
|
||||
"prettier": "1.5.3",
|
||||
"promise-polyfill": "6.0.2",
|
||||
"replace-in-file": "^3.0.0",
|
||||
"rimraf": "2.6.1",
|
||||
"serve-index": "1.9.0",
|
||||
"serve-index": "^1.9.1",
|
||||
"slash": "1.0.0",
|
||||
"standard-version": "^5.0.2",
|
||||
"uglifyjs-webpack-plugin": "^1.1.2",
|
||||
"webpack": "3.4.1"
|
||||
"webpack": "^4.29.6",
|
||||
"webpack-cli": "^3.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rimraf dist/ && node scripts/create-reftest-list && npm run build:npm && npm run build:browser",
|
||||
"build:npm": "babel src/ -d dist/npm/ --plugins=dev-expression,transform-es2015-modules-commonjs && replace-in-file __VERSION__ '\"$npm_package_version\"' dist/npm/index.js",
|
||||
"build:npm": "babel src/ -d dist/npm/ --plugins=dev-expression && replace-in-file __VERSION__ '\"$npm_package_version\"' dist/npm/index.js",
|
||||
"build:browser": "webpack",
|
||||
"format": "prettier --single-quote --no-bracket-spacing --tab-width 4 --print-width 100 --write \"{src,www,tests,scripts}/**/*.js\"",
|
||||
"release": "standard-version",
|
||||
"rollup": "rollup -c",
|
||||
"format": "prettier --single-quote --no-bracket-spacing --tab-width 4 --print-width 100 --write \"{src,www/src,tests,scripts}/**/*.js\"",
|
||||
"flow": "flow",
|
||||
"lint": "eslint src/**",
|
||||
"test": "npm run flow && npm run lint && npm run test:node && npm run karma",
|
||||
@ -77,6 +87,6 @@
|
||||
"homepage": "https://html2canvas.hertzen.com",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "2.1.0"
|
||||
"css-line-break": "1.0.1"
|
||||
}
|
||||
}
|
||||
|
37
rollup.config.js
Normal file
37
rollup.config.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'package.json')));
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
${pkg.title} ${pkg.version} <${pkg.homepage}>
|
||||
Copyright (c) ${(new Date()).getFullYear()} ${pkg.author.name} <${pkg.author.url}>
|
||||
Released under ${pkg.license} License
|
||||
*/`;
|
||||
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
|
||||
export default {
|
||||
input: './src/index.js',
|
||||
plugins: [
|
||||
resolve(),
|
||||
babel({
|
||||
exclude: 'node_modules/**'
|
||||
}),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/css-line-break/dist/index.js': ['toCodePoints', 'fromCodePoint', 'LineBreaker']
|
||||
}
|
||||
})
|
||||
],
|
||||
output: {
|
||||
file: './dist/html2canvas.js',
|
||||
name: 'html2canvas',
|
||||
format: 'umd',
|
||||
banner
|
||||
}
|
||||
};
|
@ -10,7 +10,7 @@ const outputPath = 'tests/reftests.js';
|
||||
const ignoredTests = fs
|
||||
.readFileSync(path.resolve(__dirname, `../tests/reftests/ignore.txt`))
|
||||
.toString()
|
||||
.split('\n')
|
||||
.split(/\r\n|\r|\n/)
|
||||
.filter(l => l.length)
|
||||
.reduce((acc, l) => {
|
||||
const m = l.match(/^(\[(.+)\])?(.+)$/i);
|
||||
|
@ -202,40 +202,32 @@ export const parseBoundCurves = (
|
||||
borders: Array<Border>,
|
||||
borderRadius: Array<BorderRadius>
|
||||
): BoundCurves => {
|
||||
const HALF_WIDTH = bounds.width / 2;
|
||||
const HALF_HEIGHT = bounds.height / 2;
|
||||
const tlh =
|
||||
borderRadius[CORNER.TOP_LEFT][H].getAbsoluteValue(bounds.width) < HALF_WIDTH
|
||||
? borderRadius[CORNER.TOP_LEFT][H].getAbsoluteValue(bounds.width)
|
||||
: HALF_WIDTH;
|
||||
const tlv =
|
||||
borderRadius[CORNER.TOP_LEFT][V].getAbsoluteValue(bounds.height) < HALF_HEIGHT
|
||||
? borderRadius[CORNER.TOP_LEFT][V].getAbsoluteValue(bounds.height)
|
||||
: HALF_HEIGHT;
|
||||
const trh =
|
||||
borderRadius[CORNER.TOP_RIGHT][H].getAbsoluteValue(bounds.width) < HALF_WIDTH
|
||||
? borderRadius[CORNER.TOP_RIGHT][H].getAbsoluteValue(bounds.width)
|
||||
: HALF_WIDTH;
|
||||
const trv =
|
||||
borderRadius[CORNER.TOP_RIGHT][V].getAbsoluteValue(bounds.height) < HALF_HEIGHT
|
||||
? borderRadius[CORNER.TOP_RIGHT][V].getAbsoluteValue(bounds.height)
|
||||
: HALF_HEIGHT;
|
||||
const brh =
|
||||
borderRadius[CORNER.BOTTOM_RIGHT][H].getAbsoluteValue(bounds.width) < HALF_WIDTH
|
||||
? borderRadius[CORNER.BOTTOM_RIGHT][H].getAbsoluteValue(bounds.width)
|
||||
: HALF_WIDTH;
|
||||
const brv =
|
||||
borderRadius[CORNER.BOTTOM_RIGHT][V].getAbsoluteValue(bounds.height) < HALF_HEIGHT
|
||||
? borderRadius[CORNER.BOTTOM_RIGHT][V].getAbsoluteValue(bounds.height)
|
||||
: HALF_HEIGHT;
|
||||
const blh =
|
||||
borderRadius[CORNER.BOTTOM_LEFT][H].getAbsoluteValue(bounds.width) < HALF_WIDTH
|
||||
? borderRadius[CORNER.BOTTOM_LEFT][H].getAbsoluteValue(bounds.width)
|
||||
: HALF_WIDTH;
|
||||
const blv =
|
||||
borderRadius[CORNER.BOTTOM_LEFT][V].getAbsoluteValue(bounds.height) < HALF_HEIGHT
|
||||
? borderRadius[CORNER.BOTTOM_LEFT][V].getAbsoluteValue(bounds.height)
|
||||
: HALF_HEIGHT;
|
||||
let tlh = borderRadius[CORNER.TOP_LEFT][H].getAbsoluteValue(bounds.width);
|
||||
let tlv = borderRadius[CORNER.TOP_LEFT][V].getAbsoluteValue(bounds.height);
|
||||
let trh = borderRadius[CORNER.TOP_RIGHT][H].getAbsoluteValue(bounds.width);
|
||||
let trv = borderRadius[CORNER.TOP_RIGHT][V].getAbsoluteValue(bounds.height);
|
||||
let brh = borderRadius[CORNER.BOTTOM_RIGHT][H].getAbsoluteValue(bounds.width);
|
||||
let brv = borderRadius[CORNER.BOTTOM_RIGHT][V].getAbsoluteValue(bounds.height);
|
||||
let blh = borderRadius[CORNER.BOTTOM_LEFT][H].getAbsoluteValue(bounds.width);
|
||||
let blv = borderRadius[CORNER.BOTTOM_LEFT][V].getAbsoluteValue(bounds.height);
|
||||
|
||||
const factors = [];
|
||||
factors.push((tlh + trh) / bounds.width);
|
||||
factors.push((blh + brh) / bounds.width);
|
||||
factors.push((tlv + blv) / bounds.height);
|
||||
factors.push((trv + brv) / bounds.height);
|
||||
const maxFactor = Math.max(...factors);
|
||||
|
||||
if (maxFactor > 1) {
|
||||
tlh /= maxFactor;
|
||||
tlv /= maxFactor;
|
||||
trh /= maxFactor;
|
||||
trv /= maxFactor;
|
||||
brh /= maxFactor;
|
||||
brv /= maxFactor;
|
||||
blh /= maxFactor;
|
||||
blv /= maxFactor;
|
||||
}
|
||||
|
||||
const topWidth = bounds.width - trh;
|
||||
const rightHeight = bounds.height - brv;
|
||||
|
166
src/Clone.js
166
src/Clone.js
@ -2,6 +2,7 @@
|
||||
'use strict';
|
||||
import type {Bounds} from './Bounds';
|
||||
import type {Options} from './index';
|
||||
import type {PseudoContentData, PseudoContentItem} from './PseudoNodeContent';
|
||||
import type Logger from './Logger';
|
||||
|
||||
import {parseBounds} from './Bounds';
|
||||
@ -10,6 +11,12 @@ import ResourceLoader from './ResourceLoader';
|
||||
import {copyCSSStyles} from './Util';
|
||||
import {parseBackgroundImage} from './parsing/background';
|
||||
import CanvasRenderer from './renderer/CanvasRenderer';
|
||||
import {
|
||||
parseCounterReset,
|
||||
popCounters,
|
||||
resolvePseudoContent,
|
||||
PSEUDO_CONTENT_ITEM_TYPE
|
||||
} from './PseudoNodeContent';
|
||||
|
||||
const IGNORE_ATTRIBUTE = 'data-html2canvas-ignore';
|
||||
|
||||
@ -24,6 +31,7 @@ export class DocumentCloner {
|
||||
inlineImages: boolean;
|
||||
copyStyles: boolean;
|
||||
renderer: (element: HTMLElement, options: Options, logger: Logger) => Promise<*>;
|
||||
pseudoContentData: PseudoContentData;
|
||||
|
||||
constructor(
|
||||
element: HTMLElement,
|
||||
@ -40,6 +48,10 @@ export class DocumentCloner {
|
||||
this.options = options;
|
||||
this.renderer = renderer;
|
||||
this.resourceLoader = new ResourceLoader(options, logger, window);
|
||||
this.pseudoContentData = {
|
||||
counters: {},
|
||||
quoteDepth: 0
|
||||
};
|
||||
// $FlowFixMe
|
||||
this.documentElement = this.cloneNode(element.ownerDocument.documentElement);
|
||||
}
|
||||
@ -172,7 +184,6 @@ export class DocumentCloner {
|
||||
return this.renderer(
|
||||
documentElement,
|
||||
{
|
||||
async: this.options.async,
|
||||
allowTaint: this.options.allowTaint,
|
||||
backgroundColor: '#ffffff',
|
||||
canvas: null,
|
||||
@ -182,6 +193,7 @@ export class DocumentCloner {
|
||||
removeContainer: this.options.removeContainer,
|
||||
scale: this.options.scale,
|
||||
foreignObjectRendering: this.options.foreignObjectRendering,
|
||||
useCORS: this.options.useCORS,
|
||||
target: new CanvasRenderer(),
|
||||
width,
|
||||
height,
|
||||
@ -200,7 +212,12 @@ export class DocumentCloner {
|
||||
new Promise((resolve, reject) => {
|
||||
const iframeCanvas = document.createElement('img');
|
||||
iframeCanvas.onload = () => resolve(canvas);
|
||||
iframeCanvas.onerror = reject;
|
||||
iframeCanvas.onerror = function(event) {
|
||||
// Empty iframes may result in empty "data:," URLs, which are invalid from the <img>'s point of view
|
||||
// and instead of `onload` cause `onerror` and unhandled rejection warnings
|
||||
// https://github.com/niklasvh/html2canvas/issues/1502
|
||||
iframeCanvas.src == 'data:,' ? resolve(canvas) : reject(event);
|
||||
};
|
||||
iframeCanvas.src = canvas.toDataURL();
|
||||
if (tempIframe.parentNode) {
|
||||
tempIframe.parentNode.replaceChild(
|
||||
@ -216,6 +233,27 @@ export class DocumentCloner {
|
||||
return tempIframe;
|
||||
}
|
||||
|
||||
try {
|
||||
if (node instanceof HTMLStyleElement && node.sheet && node.sheet.cssRules) {
|
||||
const css = [].slice.call(node.sheet.cssRules, 0).reduce((css, rule) => {
|
||||
if (rule && rule.cssText) {
|
||||
return css + rule.cssText;
|
||||
}
|
||||
return css;
|
||||
}, '');
|
||||
const style = node.cloneNode(false);
|
||||
style.textContent = css;
|
||||
return style;
|
||||
}
|
||||
} catch (e) {
|
||||
// accessing node.sheet.cssRules throws a DOMException
|
||||
this.logger.log('Unable to access cssRules property');
|
||||
if (e.name !== 'SecurityError') {
|
||||
this.logger.log(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return node.cloneNode(false);
|
||||
}
|
||||
|
||||
@ -226,6 +264,11 @@ export class DocumentCloner {
|
||||
: this.createElementClone(node);
|
||||
|
||||
const window = node.ownerDocument.defaultView;
|
||||
const style = node instanceof window.HTMLElement ? window.getComputedStyle(node) : null;
|
||||
const styleBefore =
|
||||
node instanceof window.HTMLElement ? window.getComputedStyle(node, ':before') : null;
|
||||
const styleAfter =
|
||||
node instanceof window.HTMLElement ? window.getComputedStyle(node, ':after') : null;
|
||||
|
||||
if (this.referenceElement === node && clone instanceof window.HTMLElement) {
|
||||
this.clonedReferenceElement = clone;
|
||||
@ -235,22 +278,41 @@ export class DocumentCloner {
|
||||
createPseudoHideStyles(clone);
|
||||
}
|
||||
|
||||
const counters = parseCounterReset(style, this.pseudoContentData);
|
||||
const contentBefore = resolvePseudoContent(node, styleBefore, this.pseudoContentData);
|
||||
|
||||
for (let child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (
|
||||
child.nodeType !== Node.ELEMENT_NODE ||
|
||||
// $FlowFixMe
|
||||
(child.nodeName !== 'SCRIPT' && !child.hasAttribute(IGNORE_ATTRIBUTE))
|
||||
(child.nodeName !== 'SCRIPT' &&
|
||||
// $FlowFixMe
|
||||
!child.hasAttribute(IGNORE_ATTRIBUTE) &&
|
||||
(typeof this.options.ignoreElements !== 'function' ||
|
||||
// $FlowFixMe
|
||||
!this.options.ignoreElements(child)))
|
||||
) {
|
||||
if (!this.copyStyles || child.nodeName !== 'STYLE') {
|
||||
clone.appendChild(this.cloneNode(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const contentAfter = resolvePseudoContent(node, styleAfter, this.pseudoContentData);
|
||||
popCounters(counters, this.pseudoContentData);
|
||||
|
||||
if (node instanceof window.HTMLElement && clone instanceof window.HTMLElement) {
|
||||
this.inlineAllImages(inlinePseudoElement(node, clone, PSEUDO_BEFORE));
|
||||
this.inlineAllImages(inlinePseudoElement(node, clone, PSEUDO_AFTER));
|
||||
if (this.copyStyles && !(node instanceof HTMLIFrameElement)) {
|
||||
copyCSSStyles(node.ownerDocument.defaultView.getComputedStyle(node), clone);
|
||||
if (styleBefore) {
|
||||
this.inlineAllImages(
|
||||
inlinePseudoElement(node, clone, styleBefore, contentBefore, PSEUDO_BEFORE)
|
||||
);
|
||||
}
|
||||
if (styleAfter) {
|
||||
this.inlineAllImages(
|
||||
inlinePseudoElement(node, clone, styleAfter, contentAfter, PSEUDO_AFTER)
|
||||
);
|
||||
}
|
||||
if (style && this.copyStyles && !(node instanceof HTMLIFrameElement)) {
|
||||
copyCSSStyles(style, clone);
|
||||
}
|
||||
this.inlineAllImages(clone);
|
||||
if (node.scrollTop !== 0 || node.scrollLeft !== 0) {
|
||||
@ -361,9 +423,10 @@ const cloneCanvasContents = (canvas: HTMLCanvasElement, clonedCanvas: HTMLCanvas
|
||||
const inlinePseudoElement = (
|
||||
node: HTMLElement,
|
||||
clone: HTMLElement,
|
||||
style: CSSStyleDeclaration,
|
||||
contentItems: ?Array<PseudoContentItem>,
|
||||
pseudoElt: ':before' | ':after'
|
||||
): ?HTMLElement => {
|
||||
const style = node.ownerDocument.defaultView.getComputedStyle(node, pseudoElt);
|
||||
if (
|
||||
!style ||
|
||||
!style.content ||
|
||||
@ -374,20 +437,29 @@ const inlinePseudoElement = (
|
||||
return;
|
||||
}
|
||||
|
||||
const content = stripQuotes(style.content);
|
||||
const image = content.match(URL_REGEXP);
|
||||
const anonymousReplacedElement = clone.ownerDocument.createElement(
|
||||
image ? 'img' : 'html2canvaspseudoelement'
|
||||
);
|
||||
if (image) {
|
||||
// $FlowFixMe
|
||||
anonymousReplacedElement.src = stripQuotes(image[1]);
|
||||
} else {
|
||||
anonymousReplacedElement.textContent = content;
|
||||
}
|
||||
|
||||
const anonymousReplacedElement = clone.ownerDocument.createElement('html2canvaspseudoelement');
|
||||
copyCSSStyles(style, anonymousReplacedElement);
|
||||
|
||||
if (contentItems) {
|
||||
const len = contentItems.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
const item = contentItems[i];
|
||||
switch (item.type) {
|
||||
case PSEUDO_CONTENT_ITEM_TYPE.IMAGE:
|
||||
const img = clone.ownerDocument.createElement('img');
|
||||
img.src = parseBackgroundImage(`url(${item.value})`)[0].args[0];
|
||||
img.style.opacity = '1';
|
||||
anonymousReplacedElement.appendChild(img);
|
||||
break;
|
||||
case PSEUDO_CONTENT_ITEM_TYPE.TEXT:
|
||||
anonymousReplacedElement.appendChild(
|
||||
clone.ownerDocument.createTextNode(item.value)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anonymousReplacedElement.className = `${PSEUDO_HIDE_ELEMENT_CLASS_BEFORE} ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`;
|
||||
clone.className +=
|
||||
pseudoElt === PSEUDO_BEFORE
|
||||
@ -402,13 +474,6 @@ const inlinePseudoElement = (
|
||||
return anonymousReplacedElement;
|
||||
};
|
||||
|
||||
const stripQuotes = (content: string): string => {
|
||||
const first = content.substr(0, 1);
|
||||
return first === content.substr(content.length - 1) && first.match(/['"]/)
|
||||
? content.substr(1, content.length - 2)
|
||||
: content;
|
||||
};
|
||||
|
||||
const URL_REGEXP = /^url\((.+)\)$/i;
|
||||
const PSEUDO_BEFORE = ':before';
|
||||
const PSEUDO_AFTER = ':after';
|
||||
@ -562,14 +627,21 @@ export const cloneWindow = (
|
||||
documentClone.documentElement.style.left = -bounds.left + 'px';
|
||||
documentClone.documentElement.style.position = 'absolute';
|
||||
}
|
||||
|
||||
const result = Promise.resolve([
|
||||
cloneIframeContainer,
|
||||
cloner.clonedReferenceElement,
|
||||
cloner.resourceLoader
|
||||
]);
|
||||
|
||||
const onclone = options.onclone;
|
||||
|
||||
return cloner.clonedReferenceElement instanceof cloneWindow.HTMLElement ||
|
||||
cloner.clonedReferenceElement instanceof ownerDocument.defaultView.HTMLElement ||
|
||||
cloner.clonedReferenceElement instanceof HTMLElement
|
||||
? Promise.resolve([
|
||||
cloneIframeContainer,
|
||||
cloner.clonedReferenceElement,
|
||||
cloner.resourceLoader
|
||||
])
|
||||
? typeof onclone === 'function'
|
||||
? Promise.resolve().then(() => onclone(documentClone)).then(() => result)
|
||||
: result
|
||||
: Promise.reject(
|
||||
__DEV__
|
||||
? `Error finding the ${referenceElement.nodeName} in the cloned document`
|
||||
@ -578,7 +650,7 @@ export const cloneWindow = (
|
||||
});
|
||||
|
||||
documentClone.open();
|
||||
documentClone.write('<!DOCTYPE html><html></html>');
|
||||
documentClone.write(`${serializeDoctype(document.doctype)}<html></html>`);
|
||||
// Chrome scrolls the parent document for some reason after the write to the cloned window???
|
||||
restoreOwnerScroll(referenceElement.ownerDocument, scrollX, scrollY);
|
||||
documentClone.replaceChild(
|
||||
@ -590,3 +662,29 @@ export const cloneWindow = (
|
||||
return iframeLoad;
|
||||
});
|
||||
};
|
||||
|
||||
const serializeDoctype = (doctype: ?DocumentType): string => {
|
||||
let str = '';
|
||||
if (doctype) {
|
||||
str += '<!DOCTYPE ';
|
||||
if (doctype.name) {
|
||||
str += doctype.name;
|
||||
}
|
||||
|
||||
if (doctype.internalSubset) {
|
||||
str += doctype.internalSubset;
|
||||
}
|
||||
|
||||
if (doctype.publicId) {
|
||||
str += `"${doctype.publicId}"`;
|
||||
}
|
||||
|
||||
if (doctype.systemId) {
|
||||
str += `"${doctype.systemId}"`;
|
||||
}
|
||||
|
||||
str += '>';
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
@ -27,38 +27,6 @@ const testRangeBounds = document => {
|
||||
return false;
|
||||
};
|
||||
|
||||
// iOS 10.3 taints canvas with base64 images unless crossOrigin = 'anonymous'
|
||||
const testBase64 = (document: Document, src: string): Promise<boolean> => {
|
||||
const img = new Image();
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
return new Promise(resolve => {
|
||||
// Single pixel base64 image renders fine on iOS 10.3???
|
||||
img.src = src;
|
||||
|
||||
const onload = () => {
|
||||
try {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
canvas.toDataURL();
|
||||
} catch (e) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
return resolve(true);
|
||||
};
|
||||
|
||||
img.onload = onload;
|
||||
img.onerror = () => resolve(false);
|
||||
|
||||
if (img.complete === true) {
|
||||
setTimeout(() => {
|
||||
onload();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const testCORS = () => typeof new Image().crossOrigin !== 'undefined';
|
||||
|
||||
const testResponseType = () => typeof new XMLHttpRequest().responseType === 'string';
|
||||
@ -135,15 +103,6 @@ const FEATURES = {
|
||||
return value;
|
||||
},
|
||||
// $FlowFixMe - get/set properties not yet supported
|
||||
get SUPPORT_BASE64_DRAWING() {
|
||||
'use strict';
|
||||
return (src: string) => {
|
||||
const value = testBase64(document, src);
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_BASE64_DRAWING', {value: () => value});
|
||||
return value;
|
||||
};
|
||||
},
|
||||
// $FlowFixMe - get/set properties not yet supported
|
||||
get SUPPORT_FOREIGNOBJECT_DRAWING() {
|
||||
'use strict';
|
||||
const value =
|
||||
|
@ -133,7 +133,7 @@ const inlineFormElement = (
|
||||
if (value.length > 0 && body) {
|
||||
const wrapper = node.ownerDocument.createElement('html2canvaswrapper');
|
||||
copyCSSStyles(node.ownerDocument.defaultView.getComputedStyle(node, null), wrapper);
|
||||
wrapper.style.position = 'fixed';
|
||||
wrapper.style.position = 'absolute';
|
||||
wrapper.style.left = `${container.bounds.left}px`;
|
||||
wrapper.style.top = `${container.bounds.top}px`;
|
||||
if (!allowLinebreak) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import NodeContainer from './NodeContainer';
|
||||
import type NodeContainer from './NodeContainer';
|
||||
|
||||
const LENGTH_WITH_UNIT = /([\d.]+)(px|r?em|%)/i;
|
||||
|
||||
|
711
src/ListItem.js
Normal file
711
src/ListItem.js
Normal file
@ -0,0 +1,711 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import type ResourceLoader from './ResourceLoader';
|
||||
import type {ListStyleType} from './parsing/listStyle';
|
||||
|
||||
import {copyCSSStyles, contains} from './Util';
|
||||
import NodeContainer from './NodeContainer';
|
||||
import TextContainer from './TextContainer';
|
||||
import {LIST_STYLE_POSITION, LIST_STYLE_TYPE} from './parsing/listStyle';
|
||||
import {fromCodePoint} from './Unicode';
|
||||
|
||||
// Margin between the enumeration and the list item content
|
||||
const MARGIN_RIGHT = 7;
|
||||
|
||||
const ancestorTypes = ['OL', 'UL', 'MENU'];
|
||||
|
||||
export const getListOwner = (container: NodeContainer): ?NodeContainer => {
|
||||
let parent = container.parent;
|
||||
if (!parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
do {
|
||||
let isAncestor = ancestorTypes.indexOf(parent.tagName) !== -1;
|
||||
if (isAncestor) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parent;
|
||||
} while (parent);
|
||||
|
||||
return container.parent;
|
||||
};
|
||||
|
||||
export const inlineListItemElement = (
|
||||
node: HTMLElement,
|
||||
container: NodeContainer,
|
||||
resourceLoader: ResourceLoader
|
||||
): void => {
|
||||
const listStyle = container.style.listStyle;
|
||||
|
||||
if (!listStyle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const style = node.ownerDocument.defaultView.getComputedStyle(node, null);
|
||||
const wrapper = node.ownerDocument.createElement('html2canvaswrapper');
|
||||
copyCSSStyles(style, wrapper);
|
||||
|
||||
wrapper.style.position = 'absolute';
|
||||
wrapper.style.bottom = 'auto';
|
||||
wrapper.style.display = 'block';
|
||||
wrapper.style.letterSpacing = 'normal';
|
||||
|
||||
switch (listStyle.listStylePosition) {
|
||||
case LIST_STYLE_POSITION.OUTSIDE:
|
||||
wrapper.style.left = 'auto';
|
||||
wrapper.style.right = `${node.ownerDocument.defaultView.innerWidth -
|
||||
container.bounds.left -
|
||||
container.style.margin[1].getAbsoluteValue(container.bounds.width) +
|
||||
MARGIN_RIGHT}px`;
|
||||
wrapper.style.textAlign = 'right';
|
||||
break;
|
||||
case LIST_STYLE_POSITION.INSIDE:
|
||||
wrapper.style.left = `${container.bounds.left -
|
||||
container.style.margin[3].getAbsoluteValue(container.bounds.width)}px`;
|
||||
wrapper.style.right = 'auto';
|
||||
wrapper.style.textAlign = 'left';
|
||||
break;
|
||||
}
|
||||
|
||||
let text;
|
||||
const MARGIN_TOP = container.style.margin[0].getAbsoluteValue(container.bounds.width);
|
||||
const styleImage = listStyle.listStyleImage;
|
||||
if (styleImage) {
|
||||
if (styleImage.method === 'url') {
|
||||
const image = node.ownerDocument.createElement('img');
|
||||
image.src = styleImage.args[0];
|
||||
wrapper.style.top = `${container.bounds.top - MARGIN_TOP}px`;
|
||||
wrapper.style.width = 'auto';
|
||||
wrapper.style.height = 'auto';
|
||||
wrapper.appendChild(image);
|
||||
} else {
|
||||
const size = parseFloat(container.style.font.fontSize) * 0.5;
|
||||
wrapper.style.top = `${container.bounds.top -
|
||||
MARGIN_TOP +
|
||||
container.bounds.height -
|
||||
1.5 * size}px`;
|
||||
wrapper.style.width = `${size}px`;
|
||||
wrapper.style.height = `${size}px`;
|
||||
wrapper.style.backgroundImage = style.listStyleImage;
|
||||
}
|
||||
} else if (typeof container.listIndex === 'number') {
|
||||
text = node.ownerDocument.createTextNode(
|
||||
createCounterText(container.listIndex, listStyle.listStyleType, true)
|
||||
);
|
||||
wrapper.appendChild(text);
|
||||
wrapper.style.top = `${container.bounds.top - MARGIN_TOP}px`;
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
const body: HTMLBodyElement = node.ownerDocument.body;
|
||||
body.appendChild(wrapper);
|
||||
|
||||
if (text) {
|
||||
container.childNodes.push(TextContainer.fromTextNode(text, container));
|
||||
body.removeChild(wrapper);
|
||||
} else {
|
||||
// $FlowFixMe
|
||||
container.childNodes.push(new NodeContainer(wrapper, container, resourceLoader, 0));
|
||||
}
|
||||
};
|
||||
|
||||
const ROMAN_UPPER = {
|
||||
integers: [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
|
||||
values: ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']
|
||||
};
|
||||
|
||||
const ARMENIAN = {
|
||||
integers: [
|
||||
9000,
|
||||
8000,
|
||||
7000,
|
||||
6000,
|
||||
5000,
|
||||
4000,
|
||||
3000,
|
||||
2000,
|
||||
1000,
|
||||
900,
|
||||
800,
|
||||
700,
|
||||
600,
|
||||
500,
|
||||
400,
|
||||
300,
|
||||
200,
|
||||
100,
|
||||
90,
|
||||
80,
|
||||
70,
|
||||
60,
|
||||
50,
|
||||
40,
|
||||
30,
|
||||
20,
|
||||
10,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1
|
||||
],
|
||||
values: [
|
||||
'Ք',
|
||||
'Փ',
|
||||
'Ւ',
|
||||
'Ց',
|
||||
'Ր',
|
||||
'Տ',
|
||||
'Վ',
|
||||
'Ս',
|
||||
'Ռ',
|
||||
'Ջ',
|
||||
'Պ',
|
||||
'Չ',
|
||||
'Ո',
|
||||
'Շ',
|
||||
'Ն',
|
||||
'Յ',
|
||||
'Մ',
|
||||
'Ճ',
|
||||
'Ղ',
|
||||
'Ձ',
|
||||
'Հ',
|
||||
'Կ',
|
||||
'Ծ',
|
||||
'Խ',
|
||||
'Լ',
|
||||
'Ի',
|
||||
'Ժ',
|
||||
'Թ',
|
||||
'Ը',
|
||||
'Է',
|
||||
'Զ',
|
||||
'Ե',
|
||||
'Դ',
|
||||
'Գ',
|
||||
'Բ',
|
||||
'Ա'
|
||||
]
|
||||
};
|
||||
|
||||
const HEBREW = {
|
||||
integers: [
|
||||
10000,
|
||||
9000,
|
||||
8000,
|
||||
7000,
|
||||
6000,
|
||||
5000,
|
||||
4000,
|
||||
3000,
|
||||
2000,
|
||||
1000,
|
||||
400,
|
||||
300,
|
||||
200,
|
||||
100,
|
||||
90,
|
||||
80,
|
||||
70,
|
||||
60,
|
||||
50,
|
||||
40,
|
||||
30,
|
||||
20,
|
||||
19,
|
||||
18,
|
||||
17,
|
||||
16,
|
||||
15,
|
||||
10,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1
|
||||
],
|
||||
values: [
|
||||
'י׳',
|
||||
'ט׳',
|
||||
'ח׳',
|
||||
'ז׳',
|
||||
'ו׳',
|
||||
'ה׳',
|
||||
'ד׳',
|
||||
'ג׳',
|
||||
'ב׳',
|
||||
'א׳',
|
||||
'ת',
|
||||
'ש',
|
||||
'ר',
|
||||
'ק',
|
||||
'צ',
|
||||
'פ',
|
||||
'ע',
|
||||
'ס',
|
||||
'נ',
|
||||
'מ',
|
||||
'ל',
|
||||
'כ',
|
||||
'יט',
|
||||
'יח',
|
||||
'יז',
|
||||
'טז',
|
||||
'טו',
|
||||
'י',
|
||||
'ט',
|
||||
'ח',
|
||||
'ז',
|
||||
'ו',
|
||||
'ה',
|
||||
'ד',
|
||||
'ג',
|
||||
'ב',
|
||||
'א'
|
||||
]
|
||||
};
|
||||
|
||||
const GEORGIAN = {
|
||||
integers: [
|
||||
10000,
|
||||
9000,
|
||||
8000,
|
||||
7000,
|
||||
6000,
|
||||
5000,
|
||||
4000,
|
||||
3000,
|
||||
2000,
|
||||
1000,
|
||||
900,
|
||||
800,
|
||||
700,
|
||||
600,
|
||||
500,
|
||||
400,
|
||||
300,
|
||||
200,
|
||||
100,
|
||||
90,
|
||||
80,
|
||||
70,
|
||||
60,
|
||||
50,
|
||||
40,
|
||||
30,
|
||||
20,
|
||||
10,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1
|
||||
],
|
||||
values: [
|
||||
'ჵ',
|
||||
'ჰ',
|
||||
'ჯ',
|
||||
'ჴ',
|
||||
'ხ',
|
||||
'ჭ',
|
||||
'წ',
|
||||
'ძ',
|
||||
'ც',
|
||||
'ჩ',
|
||||
'შ',
|
||||
'ყ',
|
||||
'ღ',
|
||||
'ქ',
|
||||
'ფ',
|
||||
'ჳ',
|
||||
'ტ',
|
||||
'ს',
|
||||
'რ',
|
||||
'ჟ',
|
||||
'პ',
|
||||
'ო',
|
||||
'ჲ',
|
||||
'ნ',
|
||||
'მ',
|
||||
'ლ',
|
||||
'კ',
|
||||
'ი',
|
||||
'თ',
|
||||
'ჱ',
|
||||
'ზ',
|
||||
'ვ',
|
||||
'ე',
|
||||
'დ',
|
||||
'გ',
|
||||
'ბ',
|
||||
'ა'
|
||||
]
|
||||
};
|
||||
|
||||
const createAdditiveCounter = (
|
||||
value: number,
|
||||
min: number,
|
||||
max: number,
|
||||
symbols,
|
||||
fallback: ListStyleType,
|
||||
suffix: string
|
||||
) => {
|
||||
if (value < min || value > max) {
|
||||
return createCounterText(value, fallback, suffix.length > 0);
|
||||
}
|
||||
|
||||
return (
|
||||
symbols.integers.reduce((string, integer, index) => {
|
||||
while (value >= integer) {
|
||||
value -= integer;
|
||||
string += symbols.values[index];
|
||||
}
|
||||
return string;
|
||||
}, '') + suffix
|
||||
);
|
||||
};
|
||||
|
||||
const createCounterStyleWithSymbolResolver = (
|
||||
value: number,
|
||||
codePointRangeLength: number,
|
||||
isNumeric: boolean,
|
||||
resolver
|
||||
): string => {
|
||||
let string = '';
|
||||
|
||||
do {
|
||||
if (!isNumeric) {
|
||||
value--;
|
||||
}
|
||||
string = resolver(value) + string;
|
||||
value /= codePointRangeLength;
|
||||
} while (value * codePointRangeLength >= codePointRangeLength);
|
||||
|
||||
return string;
|
||||
};
|
||||
|
||||
const createCounterStyleFromRange = (
|
||||
value: number,
|
||||
codePointRangeStart: number,
|
||||
codePointRangeEnd: number,
|
||||
isNumeric: boolean,
|
||||
suffix: string
|
||||
): string => {
|
||||
const codePointRangeLength = codePointRangeEnd - codePointRangeStart + 1;
|
||||
|
||||
return (
|
||||
(value < 0 ? '-' : '') +
|
||||
(createCounterStyleWithSymbolResolver(
|
||||
Math.abs(value),
|
||||
codePointRangeLength,
|
||||
isNumeric,
|
||||
codePoint =>
|
||||
fromCodePoint(Math.floor(codePoint % codePointRangeLength) + codePointRangeStart)
|
||||
) +
|
||||
suffix)
|
||||
);
|
||||
};
|
||||
|
||||
const createCounterStyleFromSymbols = (
|
||||
value: number,
|
||||
symbols: string,
|
||||
suffix: string = '. '
|
||||
): string => {
|
||||
const codePointRangeLength = symbols.length;
|
||||
return (
|
||||
createCounterStyleWithSymbolResolver(
|
||||
Math.abs(value),
|
||||
codePointRangeLength,
|
||||
false,
|
||||
codePoint => symbols[Math.floor(codePoint % codePointRangeLength)]
|
||||
) + suffix
|
||||
);
|
||||
};
|
||||
|
||||
const CJK_ZEROS = 1 << 0;
|
||||
const CJK_TEN_COEFFICIENTS = 1 << 1;
|
||||
const CJK_TEN_HIGH_COEFFICIENTS = 1 << 2;
|
||||
const CJK_HUNDRED_COEFFICIENTS = 1 << 3;
|
||||
|
||||
const createCJKCounter = (
|
||||
value: number,
|
||||
numbers: string,
|
||||
multipliers: string,
|
||||
negativeSign: string,
|
||||
suffix: string,
|
||||
flags: number
|
||||
): string => {
|
||||
if (value < -9999 || value > 9999) {
|
||||
return createCounterText(value, LIST_STYLE_TYPE.CJK_DECIMAL, suffix.length > 0);
|
||||
}
|
||||
let tmp = Math.abs(value);
|
||||
let string = suffix;
|
||||
|
||||
if (tmp === 0) {
|
||||
return numbers[0] + string;
|
||||
}
|
||||
|
||||
for (let digit = 0; tmp > 0 && digit <= 4; digit++) {
|
||||
let coefficient = tmp % 10;
|
||||
|
||||
if (coefficient === 0 && contains(flags, CJK_ZEROS) && string !== '') {
|
||||
string = numbers[coefficient] + string;
|
||||
} else if (
|
||||
coefficient > 1 ||
|
||||
(coefficient === 1 && digit === 0) ||
|
||||
(coefficient === 1 && digit === 1 && contains(flags, CJK_TEN_COEFFICIENTS)) ||
|
||||
(coefficient === 1 &&
|
||||
digit === 1 &&
|
||||
contains(flags, CJK_TEN_HIGH_COEFFICIENTS) &&
|
||||
value > 100) ||
|
||||
(coefficient === 1 && digit > 1 && contains(flags, CJK_HUNDRED_COEFFICIENTS))
|
||||
) {
|
||||
string = numbers[coefficient] + (digit > 0 ? multipliers[digit - 1] : '') + string;
|
||||
} else if (coefficient === 1 && digit > 0) {
|
||||
string = multipliers[digit - 1] + string;
|
||||
}
|
||||
tmp = Math.floor(tmp / 10);
|
||||
}
|
||||
|
||||
return (value < 0 ? negativeSign : '') + string;
|
||||
};
|
||||
|
||||
const CHINESE_INFORMAL_MULTIPLIERS = '十百千萬';
|
||||
const CHINESE_FORMAL_MULTIPLIERS = '拾佰仟萬';
|
||||
const JAPANESE_NEGATIVE = 'マイナス';
|
||||
const KOREAN_NEGATIVE = '마이너스';
|
||||
|
||||
export const createCounterText = (
|
||||
value: number,
|
||||
type: ListStyleType,
|
||||
appendSuffix: boolean
|
||||
): string => {
|
||||
const defaultSuffix = appendSuffix ? '. ' : '';
|
||||
const cjkSuffix = appendSuffix ? '、' : '';
|
||||
const koreanSuffix = appendSuffix ? ', ' : '';
|
||||
switch (type) {
|
||||
case LIST_STYLE_TYPE.DISC:
|
||||
return '•';
|
||||
case LIST_STYLE_TYPE.CIRCLE:
|
||||
return '◦';
|
||||
case LIST_STYLE_TYPE.SQUARE:
|
||||
return '◾';
|
||||
case LIST_STYLE_TYPE.DECIMAL_LEADING_ZERO:
|
||||
const string = createCounterStyleFromRange(value, 48, 57, true, defaultSuffix);
|
||||
return string.length < 4 ? `0${string}` : string;
|
||||
case LIST_STYLE_TYPE.CJK_DECIMAL:
|
||||
return createCounterStyleFromSymbols(value, '〇一二三四五六七八九', cjkSuffix);
|
||||
case LIST_STYLE_TYPE.LOWER_ROMAN:
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
3999,
|
||||
ROMAN_UPPER,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
).toLowerCase();
|
||||
case LIST_STYLE_TYPE.UPPER_ROMAN:
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
3999,
|
||||
ROMAN_UPPER,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.LOWER_GREEK:
|
||||
return createCounterStyleFromRange(value, 945, 969, false, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.LOWER_ALPHA:
|
||||
return createCounterStyleFromRange(value, 97, 122, false, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.UPPER_ALPHA:
|
||||
return createCounterStyleFromRange(value, 65, 90, false, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.ARABIC_INDIC:
|
||||
return createCounterStyleFromRange(value, 1632, 1641, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.ARMENIAN:
|
||||
case LIST_STYLE_TYPE.UPPER_ARMENIAN:
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
9999,
|
||||
ARMENIAN,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.LOWER_ARMENIAN:
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
9999,
|
||||
ARMENIAN,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
).toLowerCase();
|
||||
case LIST_STYLE_TYPE.BENGALI:
|
||||
return createCounterStyleFromRange(value, 2534, 2543, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.CAMBODIAN:
|
||||
case LIST_STYLE_TYPE.KHMER:
|
||||
return createCounterStyleFromRange(value, 6112, 6121, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.CJK_EARTHLY_BRANCH:
|
||||
return createCounterStyleFromSymbols(value, '子丑寅卯辰巳午未申酉戌亥', cjkSuffix);
|
||||
case LIST_STYLE_TYPE.CJK_HEAVENLY_STEM:
|
||||
return createCounterStyleFromSymbols(value, '甲乙丙丁戊己庚辛壬癸', cjkSuffix);
|
||||
case LIST_STYLE_TYPE.CJK_IDEOGRAPHIC:
|
||||
case LIST_STYLE_TYPE.TRAD_CHINESE_INFORMAL:
|
||||
return createCJKCounter(
|
||||
value,
|
||||
'零一二三四五六七八九',
|
||||
CHINESE_INFORMAL_MULTIPLIERS,
|
||||
'負',
|
||||
cjkSuffix,
|
||||
CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.TRAD_CHINESE_FORMAL:
|
||||
return createCJKCounter(
|
||||
value,
|
||||
'零壹貳參肆伍陸柒捌玖',
|
||||
CHINESE_FORMAL_MULTIPLIERS,
|
||||
'負',
|
||||
cjkSuffix,
|
||||
CJK_ZEROS |
|
||||
CJK_TEN_COEFFICIENTS |
|
||||
CJK_TEN_HIGH_COEFFICIENTS |
|
||||
CJK_HUNDRED_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.SIMP_CHINESE_INFORMAL:
|
||||
return createCJKCounter(
|
||||
value,
|
||||
'零一二三四五六七八九',
|
||||
CHINESE_INFORMAL_MULTIPLIERS,
|
||||
'负',
|
||||
cjkSuffix,
|
||||
CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.SIMP_CHINESE_FORMAL:
|
||||
return createCJKCounter(
|
||||
value,
|
||||
'零壹贰叁肆伍陆柒捌玖',
|
||||
CHINESE_FORMAL_MULTIPLIERS,
|
||||
'负',
|
||||
cjkSuffix,
|
||||
CJK_ZEROS |
|
||||
CJK_TEN_COEFFICIENTS |
|
||||
CJK_TEN_HIGH_COEFFICIENTS |
|
||||
CJK_HUNDRED_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.JAPANESE_INFORMAL:
|
||||
return createCJKCounter(value, '〇一二三四五六七八九', '十百千万', JAPANESE_NEGATIVE, cjkSuffix, 0);
|
||||
case LIST_STYLE_TYPE.JAPANESE_FORMAL:
|
||||
return createCJKCounter(
|
||||
value,
|
||||
'零壱弐参四伍六七八九',
|
||||
'拾百千万',
|
||||
JAPANESE_NEGATIVE,
|
||||
cjkSuffix,
|
||||
CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.KOREAN_HANGUL_FORMAL:
|
||||
return createCJKCounter(
|
||||
value,
|
||||
'영일이삼사오육칠팔구',
|
||||
'십백천만',
|
||||
KOREAN_NEGATIVE,
|
||||
koreanSuffix,
|
||||
CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.KOREAN_HANJA_INFORMAL:
|
||||
return createCJKCounter(value, '零一二三四五六七八九', '十百千萬', KOREAN_NEGATIVE, koreanSuffix, 0);
|
||||
case LIST_STYLE_TYPE.KOREAN_HANJA_FORMAL:
|
||||
return createCJKCounter(
|
||||
value,
|
||||
'零壹貳參四五六七八九',
|
||||
'拾百千',
|
||||
KOREAN_NEGATIVE,
|
||||
koreanSuffix,
|
||||
CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.DEVANAGARI:
|
||||
return createCounterStyleFromRange(value, 0x966, 0x96f, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.GEORGIAN:
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
19999,
|
||||
GEORGIAN,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.GUJARATI:
|
||||
return createCounterStyleFromRange(value, 0xae6, 0xaef, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.GURMUKHI:
|
||||
return createCounterStyleFromRange(value, 0xa66, 0xa6f, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.HEBREW:
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
10999,
|
||||
HEBREW,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.HIRAGANA:
|
||||
return createCounterStyleFromSymbols(
|
||||
value,
|
||||
'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわゐゑをん'
|
||||
);
|
||||
case LIST_STYLE_TYPE.HIRAGANA_IROHA:
|
||||
return createCounterStyleFromSymbols(
|
||||
value,
|
||||
'いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす'
|
||||
);
|
||||
case LIST_STYLE_TYPE.KANNADA:
|
||||
return createCounterStyleFromRange(value, 0xce6, 0xcef, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.KATAKANA:
|
||||
return createCounterStyleFromSymbols(
|
||||
value,
|
||||
'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヰヱヲン',
|
||||
cjkSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.KATAKANA_IROHA:
|
||||
return createCounterStyleFromSymbols(
|
||||
value,
|
||||
'イロハニホヘトチリヌルヲワカヨタレソツネナラムウヰノオクヤマケフコエテアサキユメミシヱヒモセス',
|
||||
cjkSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.LAO:
|
||||
return createCounterStyleFromRange(value, 0xed0, 0xed9, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.MONGOLIAN:
|
||||
return createCounterStyleFromRange(value, 0x1810, 0x1819, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.MYANMAR:
|
||||
return createCounterStyleFromRange(value, 0x1040, 0x1049, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.ORIYA:
|
||||
return createCounterStyleFromRange(value, 0xb66, 0xb6f, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.PERSIAN:
|
||||
return createCounterStyleFromRange(value, 0x6f0, 0x6f9, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.TAMIL:
|
||||
return createCounterStyleFromRange(value, 0xbe6, 0xbef, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.TELUGU:
|
||||
return createCounterStyleFromRange(value, 0xc66, 0xc6f, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.THAI:
|
||||
return createCounterStyleFromRange(value, 0xe50, 0xe59, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.TIBETAN:
|
||||
return createCounterStyleFromRange(value, 0xf20, 0xf29, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.DECIMAL:
|
||||
default:
|
||||
return createCounterStyleFromRange(value, 48, 57, true, defaultSuffix);
|
||||
}
|
||||
};
|
@ -7,7 +7,7 @@ export default class Logger {
|
||||
id: ?string;
|
||||
|
||||
constructor(enabled: boolean, id: ?string, start: ?number) {
|
||||
this.enabled = enabled;
|
||||
this.enabled = typeof window !== 'undefined' && enabled;
|
||||
this.start = start ? start : Date.now();
|
||||
this.id = id;
|
||||
}
|
||||
|
@ -7,7 +7,11 @@ import type {BorderRadius} from './parsing/borderRadius';
|
||||
import type {DisplayBit} from './parsing/display';
|
||||
import type {Float} from './parsing/float';
|
||||
import type {Font} from './parsing/font';
|
||||
import type {LineBreak} from './parsing/lineBreak';
|
||||
import type {ListStyle} from './parsing/listStyle';
|
||||
import type {Margin} from './parsing/margin';
|
||||
import type {Overflow} from './parsing/overflow';
|
||||
import type {OverflowWrap} from './parsing/overflowWrap';
|
||||
import type {Padding} from './parsing/padding';
|
||||
import type {Position} from './parsing/position';
|
||||
import type {TextShadow} from './parsing/textShadow';
|
||||
@ -15,6 +19,7 @@ import type {TextTransform} from './parsing/textTransform';
|
||||
import type {TextDecoration} from './parsing/textDecoration';
|
||||
import type {Transform} from './parsing/transform';
|
||||
import type {Visibility} from './parsing/visibility';
|
||||
import type {WordBreak} from './parsing/word-break';
|
||||
import type {zIndex} from './parsing/zIndex';
|
||||
|
||||
import type {Bounds, BoundCurves} from './Bounds';
|
||||
@ -32,7 +37,11 @@ import {parseDisplay, DISPLAY} from './parsing/display';
|
||||
import {parseCSSFloat, FLOAT} from './parsing/float';
|
||||
import {parseFont} from './parsing/font';
|
||||
import {parseLetterSpacing} from './parsing/letterSpacing';
|
||||
import {parseLineBreak} from './parsing/lineBreak';
|
||||
import {parseListStyle} from './parsing/listStyle';
|
||||
import {parseMargin} from './parsing/margin';
|
||||
import {parseOverflow, OVERFLOW} from './parsing/overflow';
|
||||
import {parseOverflowWrap} from './parsing/overflowWrap';
|
||||
import {parsePadding} from './parsing/padding';
|
||||
import {parsePosition, POSITION} from './parsing/position';
|
||||
import {parseTextDecoration} from './parsing/textDecoration';
|
||||
@ -40,6 +49,7 @@ import {parseTextShadow} from './parsing/textShadow';
|
||||
import {parseTextTransform} from './parsing/textTransform';
|
||||
import {parseTransform} from './parsing/transform';
|
||||
import {parseVisibility, VISIBILITY} from './parsing/visibility';
|
||||
import {parseWordBreak} from './parsing/word-break';
|
||||
import {parseZIndex} from './parsing/zIndex';
|
||||
|
||||
import {parseBounds, parseBoundCurves, calculatePaddingBoxPath} from './Bounds';
|
||||
@ -50,6 +60,7 @@ import {
|
||||
getInputBorderRadius,
|
||||
reformatInputBounds
|
||||
} from './Input';
|
||||
import {getListOwner} from './ListItem';
|
||||
|
||||
type StyleDeclaration = {
|
||||
background: Background,
|
||||
@ -60,8 +71,12 @@ type StyleDeclaration = {
|
||||
float: Float,
|
||||
font: Font,
|
||||
letterSpacing: number,
|
||||
lineBreak: LineBreak,
|
||||
listStyle: ListStyle | null,
|
||||
margin: Margin,
|
||||
opacity: number,
|
||||
overflow: Overflow,
|
||||
overflowWrap: OverflowWrap,
|
||||
padding: Padding,
|
||||
position: Position,
|
||||
textDecoration: TextDecoration | null,
|
||||
@ -69,6 +84,7 @@ type StyleDeclaration = {
|
||||
textTransform: TextTransform,
|
||||
transform: Transform,
|
||||
visibility: Visibility,
|
||||
wordBreak: WordBreak,
|
||||
zIndex: zIndex
|
||||
};
|
||||
|
||||
@ -79,10 +95,14 @@ export default class NodeContainer {
|
||||
parent: ?NodeContainer;
|
||||
style: StyleDeclaration;
|
||||
childNodes: Array<TextContainer | Path>;
|
||||
listItems: Array<NodeContainer>;
|
||||
listIndex: ?number;
|
||||
listStart: ?number;
|
||||
bounds: Bounds;
|
||||
curvedBounds: BoundCurves;
|
||||
image: ?string;
|
||||
index: number;
|
||||
tagName: string;
|
||||
|
||||
constructor(
|
||||
node: HTMLElement | SVGSVGElement,
|
||||
@ -91,8 +111,13 @@ export default class NodeContainer {
|
||||
index: number
|
||||
) {
|
||||
this.parent = parent;
|
||||
this.tagName = node.tagName;
|
||||
this.index = index;
|
||||
this.childNodes = [];
|
||||
this.listItems = [];
|
||||
if (typeof node.start === 'number') {
|
||||
this.listStart = node.start;
|
||||
}
|
||||
const defaultView = node.ownerDocument.defaultView;
|
||||
const scrollX = defaultView.pageXOffset;
|
||||
const scrollY = defaultView.pageYOffset;
|
||||
@ -117,11 +142,17 @@ export default class NodeContainer {
|
||||
float: parseCSSFloat(style.float),
|
||||
font: parseFont(style),
|
||||
letterSpacing: parseLetterSpacing(style.letterSpacing),
|
||||
listStyle: display === DISPLAY.LIST_ITEM ? parseListStyle(style) : null,
|
||||
lineBreak: parseLineBreak(style.lineBreak),
|
||||
margin: parseMargin(style),
|
||||
opacity: parseFloat(style.opacity),
|
||||
overflow:
|
||||
INPUT_TAGS.indexOf(node.tagName) === -1
|
||||
? parseOverflow(style.overflow)
|
||||
: OVERFLOW.HIDDEN,
|
||||
overflowWrap: parseOverflowWrap(
|
||||
style.overflowWrap ? style.overflowWrap : style.wordWrap
|
||||
),
|
||||
padding: parsePadding(style),
|
||||
position: position,
|
||||
textDecoration: parseTextDecoration(style),
|
||||
@ -129,6 +160,7 @@ export default class NodeContainer {
|
||||
textTransform: parseTextTransform(style.textTransform),
|
||||
transform: parseTransform(style),
|
||||
visibility: parseVisibility(style.visibility),
|
||||
wordBreak: parseWordBreak(style.wordBreak),
|
||||
zIndex: parseZIndex(position !== POSITION.STATIC ? style.zIndex : 'auto')
|
||||
};
|
||||
|
||||
@ -137,6 +169,20 @@ export default class NodeContainer {
|
||||
node.style.transform = 'matrix(1,0,0,1,0,0)';
|
||||
}
|
||||
|
||||
if (display === DISPLAY.LIST_ITEM) {
|
||||
const listOwner = getListOwner(this);
|
||||
if (listOwner) {
|
||||
const listIndex = listOwner.listItems.length;
|
||||
listOwner.listItems.push(this);
|
||||
this.listIndex =
|
||||
node.hasAttribute('value') && typeof node.value === 'number'
|
||||
? node.value
|
||||
: listIndex === 0
|
||||
? typeof listOwner.listStart === 'number' ? listOwner.listStart : 1
|
||||
: listOwner.listItems[listIndex - 1].listIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO move bound retrieval for all nodes to a later stage?
|
||||
if (node.tagName === 'IMG') {
|
||||
node.addEventListener('load', () => {
|
||||
@ -170,8 +216,7 @@ export default class NodeContainer {
|
||||
}
|
||||
getClipPaths(): Array<Path> {
|
||||
const parentClips = this.parent ? this.parent.getClipPaths() : [];
|
||||
const isClipped =
|
||||
this.style.overflow === OVERFLOW.HIDDEN || this.style.overflow === OVERFLOW.SCROLL;
|
||||
const isClipped = this.style.overflow !== OVERFLOW.VISIBLE;
|
||||
|
||||
return isClipped
|
||||
? parentClips.concat([calculatePaddingBoxPath(this.curvedBounds)])
|
||||
|
@ -6,6 +6,8 @@ import StackingContext from './StackingContext';
|
||||
import NodeContainer from './NodeContainer';
|
||||
import TextContainer from './TextContainer';
|
||||
import {inlineInputElement, inlineTextAreaElement, inlineSelectElement} from './Input';
|
||||
import {inlineListItemElement} from './ListItem';
|
||||
import {LIST_STYLE_TYPE} from './parsing/listStyle';
|
||||
|
||||
export const NodeParser = (
|
||||
node: HTMLElement,
|
||||
@ -71,6 +73,11 @@ const parseNodeTree = (
|
||||
} else if (childNode.tagName === 'SELECT') {
|
||||
// $FlowFixMe
|
||||
inlineSelectElement(childNode, container);
|
||||
} else if (
|
||||
container.style.listStyle &&
|
||||
container.style.listStyle.listStyleType !== LIST_STYLE_TYPE.NONE
|
||||
) {
|
||||
inlineListItemElement(childNode, container, resourceLoader);
|
||||
}
|
||||
|
||||
const SHOULD_TRAVERSE_CHILDREN = childNode.tagName !== 'TEXTAREA';
|
||||
|
341
src/PseudoNodeContent.js
Normal file
341
src/PseudoNodeContent.js
Normal file
@ -0,0 +1,341 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import {createCounterText} from './ListItem';
|
||||
import {parseListStyleType} from './parsing/listStyle';
|
||||
|
||||
export const PSEUDO_CONTENT_ITEM_TYPE = {
|
||||
TEXT: 0,
|
||||
IMAGE: 1
|
||||
};
|
||||
|
||||
export const TOKEN_TYPE = {
|
||||
STRING: 0,
|
||||
ATTRIBUTE: 1,
|
||||
URL: 2,
|
||||
COUNTER: 3,
|
||||
COUNTERS: 4,
|
||||
OPENQUOTE: 5,
|
||||
CLOSEQUOTE: 6
|
||||
};
|
||||
|
||||
export type PseudoContentData = {
|
||||
counters: {[string]: Array<number>},
|
||||
quoteDepth: number
|
||||
};
|
||||
|
||||
export type PseudoContentItem = {
|
||||
type: $Values<typeof PSEUDO_CONTENT_ITEM_TYPE>,
|
||||
value: string
|
||||
};
|
||||
|
||||
export type Token = {
|
||||
type: $Values<typeof TOKEN_TYPE>,
|
||||
value?: string,
|
||||
name?: string,
|
||||
format?: string,
|
||||
glue?: string
|
||||
};
|
||||
|
||||
export const parseCounterReset = (
|
||||
style: ?CSSStyleDeclaration,
|
||||
data: PseudoContentData
|
||||
): Array<string> => {
|
||||
if (!style || !style.counterReset || style.counterReset === 'none') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const counterNames: Array<string> = [];
|
||||
const counterResets = style.counterReset.split(/\s*,\s*/);
|
||||
const lenCounterResets = counterResets.length;
|
||||
|
||||
for (let i = 0; i < lenCounterResets; i++) {
|
||||
const [counterName, initialValue] = counterResets[i].split(/\s+/);
|
||||
counterNames.push(counterName);
|
||||
let counter = data.counters[counterName];
|
||||
if (!counter) {
|
||||
counter = data.counters[counterName] = [];
|
||||
}
|
||||
counter.push(parseInt(initialValue || 0, 10));
|
||||
}
|
||||
|
||||
return counterNames;
|
||||
};
|
||||
|
||||
export const popCounters = (counterNames: Array<string>, data: PseudoContentData): void => {
|
||||
const lenCounters = counterNames.length;
|
||||
for (let i = 0; i < lenCounters; i++) {
|
||||
data.counters[counterNames[i]].pop();
|
||||
}
|
||||
};
|
||||
|
||||
export const resolvePseudoContent = (
|
||||
node: Node,
|
||||
style: ?CSSStyleDeclaration,
|
||||
data: PseudoContentData
|
||||
): ?Array<PseudoContentItem> => {
|
||||
if (
|
||||
!style ||
|
||||
!style.content ||
|
||||
style.content === 'none' ||
|
||||
style.content === '-moz-alt-content' ||
|
||||
style.display === 'none'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokens = parseContent(style.content);
|
||||
|
||||
const len = tokens.length;
|
||||
const contentItems: Array<PseudoContentItem> = [];
|
||||
let s = '';
|
||||
|
||||
// increment the counter (if there is a "counter-increment" declaration)
|
||||
const counterIncrement = style.counterIncrement;
|
||||
if (counterIncrement && counterIncrement !== 'none') {
|
||||
const [counterName, incrementValue] = counterIncrement.split(/\s+/);
|
||||
const counter = data.counters[counterName];
|
||||
if (counter) {
|
||||
counter[counter.length - 1] +=
|
||||
incrementValue === undefined ? 1 : parseInt(incrementValue, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// build the content string
|
||||
for (let i = 0; i < len; i++) {
|
||||
const token = tokens[i];
|
||||
switch (token.type) {
|
||||
case TOKEN_TYPE.STRING:
|
||||
s += token.value || '';
|
||||
break;
|
||||
|
||||
case TOKEN_TYPE.ATTRIBUTE:
|
||||
if (node instanceof HTMLElement && token.value) {
|
||||
s += node.getAttribute(token.value) || '';
|
||||
}
|
||||
break;
|
||||
|
||||
case TOKEN_TYPE.COUNTER:
|
||||
const counter = data.counters[token.name || ''];
|
||||
if (counter) {
|
||||
s += formatCounterValue([counter[counter.length - 1]], '', token.format);
|
||||
}
|
||||
break;
|
||||
|
||||
case TOKEN_TYPE.COUNTERS:
|
||||
const counters = data.counters[token.name || ''];
|
||||
if (counters) {
|
||||
s += formatCounterValue(counters, token.glue, token.format);
|
||||
}
|
||||
break;
|
||||
|
||||
case TOKEN_TYPE.OPENQUOTE:
|
||||
s += getQuote(style, true, data.quoteDepth);
|
||||
data.quoteDepth++;
|
||||
break;
|
||||
|
||||
case TOKEN_TYPE.CLOSEQUOTE:
|
||||
data.quoteDepth--;
|
||||
s += getQuote(style, false, data.quoteDepth);
|
||||
break;
|
||||
|
||||
case TOKEN_TYPE.URL:
|
||||
if (s) {
|
||||
contentItems.push({type: PSEUDO_CONTENT_ITEM_TYPE.TEXT, value: s});
|
||||
s = '';
|
||||
}
|
||||
contentItems.push({type: PSEUDO_CONTENT_ITEM_TYPE.IMAGE, value: token.value || ''});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s) {
|
||||
contentItems.push({type: PSEUDO_CONTENT_ITEM_TYPE.TEXT, value: s});
|
||||
}
|
||||
|
||||
return contentItems;
|
||||
};
|
||||
|
||||
export const parseContent = (content: string, cache?: {[string]: Array<Token>}): Array<Token> => {
|
||||
if (cache && cache[content]) {
|
||||
return cache[content];
|
||||
}
|
||||
|
||||
const tokens: Array<Token> = [];
|
||||
const len = content.length;
|
||||
|
||||
let isString = false;
|
||||
let isEscaped = false;
|
||||
let isFunction = false;
|
||||
let str = '';
|
||||
let functionName = '';
|
||||
let args = [];
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const c = content.charAt(i);
|
||||
|
||||
switch (c) {
|
||||
case "'":
|
||||
case '"':
|
||||
if (isEscaped) {
|
||||
str += c;
|
||||
} else {
|
||||
isString = !isString;
|
||||
if (!isFunction && !isString) {
|
||||
tokens.push({type: TOKEN_TYPE.STRING, value: str});
|
||||
str = '';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
if (isEscaped) {
|
||||
str += c;
|
||||
isEscaped = false;
|
||||
} else {
|
||||
isEscaped = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case '(':
|
||||
if (isString) {
|
||||
str += c;
|
||||
} else {
|
||||
isFunction = true;
|
||||
functionName = str;
|
||||
str = '';
|
||||
args = [];
|
||||
}
|
||||
break;
|
||||
|
||||
case ')':
|
||||
if (isString) {
|
||||
str += c;
|
||||
} else if (isFunction) {
|
||||
if (str) {
|
||||
args.push(str);
|
||||
}
|
||||
|
||||
switch (functionName) {
|
||||
case 'attr':
|
||||
if (args.length > 0) {
|
||||
tokens.push({type: TOKEN_TYPE.ATTRIBUTE, value: args[0]});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'counter':
|
||||
if (args.length > 0) {
|
||||
const counter: Token = {
|
||||
type: TOKEN_TYPE.COUNTER,
|
||||
name: args[0]
|
||||
};
|
||||
if (args.length > 1) {
|
||||
counter.format = args[1];
|
||||
}
|
||||
tokens.push(counter);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'counters':
|
||||
if (args.length > 0) {
|
||||
const counters: Token = {
|
||||
type: TOKEN_TYPE.COUNTERS,
|
||||
name: args[0]
|
||||
};
|
||||
if (args.length > 1) {
|
||||
counters.glue = args[1];
|
||||
}
|
||||
if (args.length > 2) {
|
||||
counters.format = args[2];
|
||||
}
|
||||
tokens.push(counters);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
if (args.length > 0) {
|
||||
tokens.push({type: TOKEN_TYPE.URL, value: args[0]});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
isFunction = false;
|
||||
str = '';
|
||||
}
|
||||
break;
|
||||
|
||||
case ',':
|
||||
if (isString) {
|
||||
str += c;
|
||||
} else if (isFunction) {
|
||||
args.push(str);
|
||||
str = '';
|
||||
}
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
case '\t':
|
||||
if (isString) {
|
||||
str += c;
|
||||
} else if (str) {
|
||||
addOtherToken(tokens, str);
|
||||
str = '';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
str += c;
|
||||
}
|
||||
|
||||
if (c !== '\\') {
|
||||
isEscaped = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (str) {
|
||||
addOtherToken(tokens, str);
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
cache[content] = tokens;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
};
|
||||
|
||||
const addOtherToken = (tokens: Array<Token>, identifier: string): void => {
|
||||
switch (identifier) {
|
||||
case 'open-quote':
|
||||
tokens.push({type: TOKEN_TYPE.OPENQUOTE});
|
||||
break;
|
||||
case 'close-quote':
|
||||
tokens.push({type: TOKEN_TYPE.CLOSEQUOTE});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const getQuote = (style: CSSStyleDeclaration, isOpening: boolean, quoteDepth: number): string => {
|
||||
const quotes = style.quotes ? style.quotes.split(/\s+/) : ["'\"'", "'\"'"];
|
||||
let idx = quoteDepth * 2;
|
||||
if (idx >= quotes.length) {
|
||||
idx = quotes.length - 2;
|
||||
}
|
||||
if (!isOpening) {
|
||||
++idx;
|
||||
}
|
||||
return quotes[idx].replace(/^["']|["']$/g, '');
|
||||
};
|
||||
|
||||
const formatCounterValue = (counter, glue: ?string, format: ?string): string => {
|
||||
const len = counter.length;
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (i > 0) {
|
||||
result += glue || '';
|
||||
}
|
||||
result += createCounterText(counter[i], parseListStyleType(format || 'decimal'), false);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
@ -165,7 +165,7 @@ export default class Renderer {
|
||||
!container.style.background.backgroundColor.isTransparent() ||
|
||||
container.style.background.backgroundImage.length;
|
||||
|
||||
const renderableBorders = container.style.border.filter(
|
||||
const hasRenderableBorders = container.style.border.some(
|
||||
border =>
|
||||
border.borderStyle !== BORDER_STYLE.NONE && !border.borderColor.isTransparent()
|
||||
);
|
||||
@ -186,12 +186,17 @@ export default class Renderer {
|
||||
});
|
||||
}
|
||||
|
||||
renderableBorders.forEach((border, side) => {
|
||||
this.renderBorder(border, side, container.curvedBounds);
|
||||
container.style.border.forEach((border, side) => {
|
||||
if (
|
||||
border.borderStyle !== BORDER_STYLE.NONE &&
|
||||
!border.borderColor.isTransparent()
|
||||
) {
|
||||
this.renderBorder(border, side, container.curvedBounds);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (HAS_BACKGROUND || renderableBorders.length) {
|
||||
if (HAS_BACKGROUND || hasRenderableBorders) {
|
||||
const paths = container.parent ? container.parent.getClipPaths() : [];
|
||||
if (paths.length) {
|
||||
this.target.clip(paths, callback);
|
||||
|
@ -33,6 +33,10 @@ export default class ResourceLoader {
|
||||
if (this.hasResourceInCache(src)) {
|
||||
return src;
|
||||
}
|
||||
if (isBlobImage(src)) {
|
||||
this.cache[src] = loadImage(src, this.options.imageTimeout || 0);
|
||||
return src;
|
||||
}
|
||||
|
||||
if (!isSVG(src) || FEATURES.SUPPORT_SVG_DRAWING) {
|
||||
if (this.options.allowTaint === true || isInlineImage(src) || this.isSameOrigin(src)) {
|
||||
@ -125,42 +129,36 @@ export default class ResourceLoader {
|
||||
this.logger.log(`Added image ${key.substring(0, 256)}`);
|
||||
}
|
||||
|
||||
const imageLoadHandler = (supportsDataImages: boolean): Promise<Image> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
//ios safari 10.3 taints canvas with data urls unless crossOrigin is set to anonymous
|
||||
if (!supportsDataImages || useCORS) {
|
||||
img.crossOrigin = 'anonymous';
|
||||
}
|
||||
this.cache[key] = new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
//ios safari 10.3 taints canvas with data urls unless crossOrigin is set to anonymous
|
||||
if (isInlineBase64Image(src) || useCORS) {
|
||||
img.crossOrigin = 'anonymous';
|
||||
}
|
||||
|
||||
img.onerror = reject;
|
||||
img.src = src;
|
||||
if (img.complete === true) {
|
||||
// Inline XML images may fail to parse, throwing an Error later on
|
||||
setTimeout(() => {
|
||||
resolve(img);
|
||||
}, 500);
|
||||
}
|
||||
if (this.options.imageTimeout) {
|
||||
const timeout = this.options.imageTimeout;
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
__DEV__
|
||||
? `Timed out (${timeout}ms) fetching ${src.substring(0, 256)}`
|
||||
: ''
|
||||
),
|
||||
timeout
|
||||
);
|
||||
}
|
||||
});
|
||||
img.onerror = reject;
|
||||
img.src = src;
|
||||
if (img.complete === true) {
|
||||
// Inline XML images may fail to parse, throwing an Error later on
|
||||
setTimeout(() => {
|
||||
resolve(img);
|
||||
}, 500);
|
||||
}
|
||||
if (this.options.imageTimeout) {
|
||||
const timeout = this.options.imageTimeout;
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
__DEV__
|
||||
? `Timed out (${timeout}ms) fetching ${src.substring(0, 256)}`
|
||||
: ''
|
||||
),
|
||||
timeout
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.cache[key] =
|
||||
isInlineBase64Image(src) && !isSVG(src)
|
||||
? // $FlowFixMe
|
||||
FEATURES.SUPPORT_BASE64_DRAWING(src).then(imageLoadHandler)
|
||||
: imageLoadHandler(true);
|
||||
return key;
|
||||
}
|
||||
|
||||
@ -215,6 +213,7 @@ const INLINE_IMG = /^data:image\/.*/i;
|
||||
|
||||
const isInlineImage = (src: string): boolean => INLINE_IMG.test(src);
|
||||
const isInlineBase64Image = (src: string): boolean => INLINE_BASE64.test(src);
|
||||
const isBlobImage = (src: string): boolean => src.substr(0, 4) === 'blob';
|
||||
|
||||
const isSVG = (src: string): boolean =>
|
||||
src.substr(-3).toLowerCase() === 'svg' || INLINE_SVG.test(src);
|
||||
|
@ -1,18 +1,12 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import {ucs2} from 'punycode';
|
||||
import type NodeContainer from './NodeContainer';
|
||||
import {Bounds, parseBounds} from './Bounds';
|
||||
import {TEXT_DECORATION} from './parsing/textDecoration';
|
||||
|
||||
import FEATURES from './Feature';
|
||||
|
||||
const UNICODE = /[^\u0000-\u00ff]/;
|
||||
|
||||
const hasUnicodeCharacters = (text: string): boolean => UNICODE.test(text);
|
||||
|
||||
const encodeCodePoint = (codePoint: number): string => ucs2.encode([codePoint]);
|
||||
import {breakWords, toCodePoints, fromCodePoint} from './Unicode';
|
||||
|
||||
export class TextBounds {
|
||||
text: string;
|
||||
@ -29,9 +23,10 @@ export const parseTextBounds = (
|
||||
parent: NodeContainer,
|
||||
node: Text
|
||||
): Array<TextBounds> => {
|
||||
const codePoints = ucs2.decode(value);
|
||||
const letterRendering = parent.style.letterSpacing !== 0 || hasUnicodeCharacters(value);
|
||||
const textList = letterRendering ? codePoints.map(encodeCodePoint) : splitWords(codePoints);
|
||||
const letterRendering = parent.style.letterSpacing !== 0;
|
||||
const textList = letterRendering
|
||||
? toCodePoints(value).map(i => fromCodePoint(i))
|
||||
: breakWords(value, parent);
|
||||
const length = textList.length;
|
||||
const defaultView = node.parentNode ? node.parentNode.ownerDocument.defaultView : null;
|
||||
const scrollX = defaultView ? defaultView.pageXOffset : 0;
|
||||
@ -88,42 +83,3 @@ const getRangeBounds = (
|
||||
range.setEnd(node, offset + length);
|
||||
return Bounds.fromClientRect(range.getBoundingClientRect(), scrollX, scrollY);
|
||||
};
|
||||
|
||||
const splitWords = (codePoints: Array<number>): Array<string> => {
|
||||
const words = [];
|
||||
let i = 0;
|
||||
let onWordBoundary = false;
|
||||
let word;
|
||||
while (codePoints.length) {
|
||||
if (isWordBoundary(codePoints[i]) === onWordBoundary) {
|
||||
word = codePoints.splice(0, i);
|
||||
if (word.length) {
|
||||
words.push(ucs2.encode(word));
|
||||
}
|
||||
onWordBoundary = !onWordBoundary;
|
||||
i = 0;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= codePoints.length) {
|
||||
word = codePoints.splice(0, i);
|
||||
if (word.length) {
|
||||
words.push(ucs2.encode(word));
|
||||
}
|
||||
}
|
||||
}
|
||||
return words;
|
||||
};
|
||||
|
||||
const isWordBoundary = (characterCode: number): boolean => {
|
||||
return (
|
||||
[
|
||||
32, // <space>
|
||||
13, // \r
|
||||
10, // \n
|
||||
9, // \t
|
||||
45 // -
|
||||
].indexOf(characterCode) !== -1
|
||||
);
|
||||
};
|
||||
|
27
src/Unicode.js
Normal file
27
src/Unicode.js
Normal file
@ -0,0 +1,27 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import type NodeContainer from './NodeContainer';
|
||||
import {LineBreaker, fromCodePoint, toCodePoints} from 'css-line-break';
|
||||
import {OVERFLOW_WRAP} from './parsing/overflowWrap';
|
||||
|
||||
export {toCodePoints, fromCodePoint} from 'css-line-break';
|
||||
|
||||
export const breakWords = (str: string, parent: NodeContainer): Array<string> => {
|
||||
const breaker = LineBreaker(str, {
|
||||
lineBreak: parent.style.lineBreak,
|
||||
wordBreak:
|
||||
parent.style.overflowWrap === OVERFLOW_WRAP.BREAK_WORD
|
||||
? 'break-word'
|
||||
: parent.style.wordBreak
|
||||
});
|
||||
|
||||
const words = [];
|
||||
let bk;
|
||||
|
||||
while (!(bk = breaker.next()).done) {
|
||||
words.push(bk.value.slice());
|
||||
}
|
||||
|
||||
return words;
|
||||
};
|
@ -14,6 +14,7 @@ import {Bounds} from './Bounds';
|
||||
import {cloneWindow, DocumentCloner} from './Clone';
|
||||
import {FontMetrics} from './Font';
|
||||
import Color, {TRANSPARENT} from './Color';
|
||||
import {parseBounds, parseDocumentSize} from './Bounds';
|
||||
|
||||
export const renderElement = (
|
||||
element: HTMLElement,
|
||||
@ -62,14 +63,32 @@ export const renderElement = (
|
||||
.then(() => cloner.resourceLoader.ready())
|
||||
.then(() => {
|
||||
const renderer = new ForeignObjectRenderer(cloner.documentElement);
|
||||
|
||||
const defaultView = ownerDocument.defaultView;
|
||||
const scrollX = defaultView.pageXOffset;
|
||||
const scrollY = defaultView.pageYOffset;
|
||||
|
||||
const isDocument =
|
||||
element.tagName === 'HTML' || element.tagName === 'BODY';
|
||||
|
||||
const {width, height, left, top} = isDocument
|
||||
? parseDocumentSize(ownerDocument)
|
||||
: parseBounds(element, scrollX, scrollY);
|
||||
|
||||
return renderer.render({
|
||||
backgroundColor,
|
||||
logger,
|
||||
scale: options.scale,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
x: typeof options.x === 'number' ? options.x : left,
|
||||
y: typeof options.y === 'number' ? options.y : top,
|
||||
width:
|
||||
typeof options.width === 'number'
|
||||
? options.width
|
||||
: Math.ceil(width),
|
||||
height:
|
||||
typeof options.height === 'number'
|
||||
? options.height
|
||||
: Math.ceil(height),
|
||||
windowWidth: options.windowWidth,
|
||||
windowHeight: options.windowHeight,
|
||||
scrollX: options.scrollX,
|
||||
@ -97,31 +116,38 @@ export const renderElement = (
|
||||
}
|
||||
|
||||
return resourceLoader.ready().then(imageStore => {
|
||||
if (options.removeContainer === true) {
|
||||
if (container.parentNode) {
|
||||
container.parentNode.removeChild(container);
|
||||
} else if (__DEV__) {
|
||||
logger.log(
|
||||
`Cannot detach cloned iframe as it is not in the DOM anymore`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fontMetrics = new FontMetrics(clonedDocument);
|
||||
if (__DEV__) {
|
||||
logger.log(`Starting renderer`);
|
||||
}
|
||||
|
||||
const defaultView = clonedDocument.defaultView;
|
||||
const scrollX = defaultView.pageXOffset;
|
||||
const scrollY = defaultView.pageYOffset;
|
||||
|
||||
const isDocument =
|
||||
clonedElement.tagName === 'HTML' || clonedElement.tagName === 'BODY';
|
||||
|
||||
const {width, height, left, top} = isDocument
|
||||
? parseDocumentSize(ownerDocument)
|
||||
: parseBounds(clonedElement, scrollX, scrollY);
|
||||
|
||||
const renderOptions = {
|
||||
backgroundColor,
|
||||
fontMetrics,
|
||||
imageStore,
|
||||
logger,
|
||||
scale: options.scale,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height
|
||||
x: typeof options.x === 'number' ? options.x : left,
|
||||
y: typeof options.y === 'number' ? options.y : top,
|
||||
width:
|
||||
typeof options.width === 'number'
|
||||
? options.width
|
||||
: Math.ceil(width),
|
||||
height:
|
||||
typeof options.height === 'number'
|
||||
? options.height
|
||||
: Math.ceil(height)
|
||||
};
|
||||
|
||||
if (Array.isArray(options.target)) {
|
||||
@ -133,7 +159,18 @@ export const renderElement = (
|
||||
);
|
||||
} else {
|
||||
const renderer = new Renderer(options.target, renderOptions);
|
||||
return renderer.render(stack);
|
||||
const canvas = renderer.render(stack);
|
||||
if (options.removeContainer === true) {
|
||||
if (container.parentNode) {
|
||||
container.parentNode.removeChild(container);
|
||||
} else if (__DEV__) {
|
||||
logger.log(
|
||||
`Cannot detach cloned iframe as it is not in the DOM anymore`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
});
|
||||
})
|
||||
|
30
src/index.js
30
src/index.js
@ -6,20 +6,21 @@ import type {RenderTarget} from './Renderer';
|
||||
import CanvasRenderer from './renderer/CanvasRenderer';
|
||||
import Logger from './Logger';
|
||||
import {renderElement} from './Window';
|
||||
import {parseBounds, parseDocumentSize} from './Bounds';
|
||||
|
||||
export type Options = {
|
||||
async: ?boolean,
|
||||
allowTaint: ?boolean,
|
||||
backgroundColor: string,
|
||||
canvas: ?HTMLCanvasElement,
|
||||
foreignObjectRendering: boolean,
|
||||
ignoreElements?: HTMLElement => boolean,
|
||||
imageTimeout: number,
|
||||
logging: boolean,
|
||||
onclone?: Document => void,
|
||||
proxy: ?string,
|
||||
removeContainer: ?boolean,
|
||||
scale: number,
|
||||
target: RenderTarget<*>,
|
||||
useCORS: boolean,
|
||||
width: number,
|
||||
height: number,
|
||||
x: number,
|
||||
@ -31,14 +32,9 @@ export type Options = {
|
||||
};
|
||||
|
||||
const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
|
||||
// eslint-disable-next-line no-console
|
||||
if (typeof console === 'object' && typeof console.log === 'function') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`html2canvas ${__VERSION__}`);
|
||||
}
|
||||
|
||||
const config = conf || {};
|
||||
const logger = new Logger(typeof config.logging === 'boolean' ? config.logging : true);
|
||||
logger.log(`html2canvas ${__VERSION__}`);
|
||||
|
||||
if (__DEV__ && typeof config.onrendered === 'function') {
|
||||
logger.error(
|
||||
@ -52,17 +48,7 @@ const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
|
||||
}
|
||||
const defaultView = ownerDocument.defaultView;
|
||||
|
||||
const scrollX = defaultView.pageXOffset;
|
||||
const scrollY = defaultView.pageYOffset;
|
||||
|
||||
const isDocument = element.tagName === 'HTML' || element.tagName === 'BODY';
|
||||
|
||||
const {width, height, left, top} = isDocument
|
||||
? parseDocumentSize(ownerDocument)
|
||||
: parseBounds(element, scrollX, scrollY);
|
||||
|
||||
const defaultOptions = {
|
||||
async: true,
|
||||
allowTaint: false,
|
||||
backgroundColor: '#ffffff',
|
||||
imageTimeout: 15000,
|
||||
@ -72,10 +58,7 @@ const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
|
||||
foreignObjectRendering: false,
|
||||
scale: defaultView.devicePixelRatio || 1,
|
||||
target: new CanvasRenderer(config.canvas),
|
||||
x: left,
|
||||
y: top,
|
||||
width: Math.ceil(width),
|
||||
height: Math.ceil(height),
|
||||
useCORS: false,
|
||||
windowWidth: defaultView.innerWidth,
|
||||
windowHeight: defaultView.innerHeight,
|
||||
scrollX: defaultView.pageXOffset,
|
||||
@ -94,5 +77,4 @@ const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
|
||||
};
|
||||
|
||||
html2canvas.CanvasRenderer = CanvasRenderer;
|
||||
|
||||
module.exports = html2canvas;
|
||||
export default html2canvas;
|
||||
|
19
src/parsing/lineBreak.js
Normal file
19
src/parsing/lineBreak.js
Normal file
@ -0,0 +1,19 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const LINE_BREAK = {
|
||||
NORMAL: 'normal',
|
||||
STRICT: 'strict'
|
||||
};
|
||||
|
||||
export type LineBreak = $Values<typeof LINE_BREAK>;
|
||||
|
||||
export const parseLineBreak = (wordBreak: string): LineBreak => {
|
||||
switch (wordBreak) {
|
||||
case 'strict':
|
||||
return LINE_BREAK.STRICT;
|
||||
case 'normal':
|
||||
default:
|
||||
return LINE_BREAK.NORMAL;
|
||||
}
|
||||
};
|
209
src/parsing/listStyle.js
Normal file
209
src/parsing/listStyle.js
Normal file
@ -0,0 +1,209 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import type {BackgroundSource} from './background';
|
||||
import {parseBackgroundImage} from './background';
|
||||
|
||||
export type ListStyle = {
|
||||
listStyleType: ListStyleType,
|
||||
listStyleImage: ?BackgroundSource,
|
||||
listStylePosition: ListStylePosition
|
||||
};
|
||||
|
||||
export const LIST_STYLE_POSITION = {
|
||||
INSIDE: 0,
|
||||
OUTSIDE: 1
|
||||
};
|
||||
|
||||
export const LIST_STYLE_TYPE = {
|
||||
NONE: -1,
|
||||
DISC: 0,
|
||||
CIRCLE: 1,
|
||||
SQUARE: 2,
|
||||
DECIMAL: 3,
|
||||
CJK_DECIMAL: 4,
|
||||
DECIMAL_LEADING_ZERO: 5,
|
||||
LOWER_ROMAN: 6,
|
||||
UPPER_ROMAN: 7,
|
||||
LOWER_GREEK: 8,
|
||||
LOWER_ALPHA: 9,
|
||||
UPPER_ALPHA: 10,
|
||||
ARABIC_INDIC: 11,
|
||||
ARMENIAN: 12,
|
||||
BENGALI: 13,
|
||||
CAMBODIAN: 14,
|
||||
CJK_EARTHLY_BRANCH: 15,
|
||||
CJK_HEAVENLY_STEM: 16,
|
||||
CJK_IDEOGRAPHIC: 17,
|
||||
DEVANAGARI: 18,
|
||||
ETHIOPIC_NUMERIC: 19,
|
||||
GEORGIAN: 20,
|
||||
GUJARATI: 21,
|
||||
GURMUKHI: 22,
|
||||
HEBREW: 22,
|
||||
HIRAGANA: 23,
|
||||
HIRAGANA_IROHA: 24,
|
||||
JAPANESE_FORMAL: 25,
|
||||
JAPANESE_INFORMAL: 26,
|
||||
KANNADA: 27,
|
||||
KATAKANA: 28,
|
||||
KATAKANA_IROHA: 29,
|
||||
KHMER: 30,
|
||||
KOREAN_HANGUL_FORMAL: 31,
|
||||
KOREAN_HANJA_FORMAL: 32,
|
||||
KOREAN_HANJA_INFORMAL: 33,
|
||||
LAO: 34,
|
||||
LOWER_ARMENIAN: 35,
|
||||
MALAYALAM: 36,
|
||||
MONGOLIAN: 37,
|
||||
MYANMAR: 38,
|
||||
ORIYA: 39,
|
||||
PERSIAN: 40,
|
||||
SIMP_CHINESE_FORMAL: 41,
|
||||
SIMP_CHINESE_INFORMAL: 42,
|
||||
TAMIL: 43,
|
||||
TELUGU: 44,
|
||||
THAI: 45,
|
||||
TIBETAN: 46,
|
||||
TRAD_CHINESE_FORMAL: 47,
|
||||
TRAD_CHINESE_INFORMAL: 48,
|
||||
UPPER_ARMENIAN: 49,
|
||||
DISCLOSURE_OPEN: 50,
|
||||
DISCLOSURE_CLOSED: 51
|
||||
};
|
||||
|
||||
export type ListStylePosition = $Values<typeof LIST_STYLE_POSITION>;
|
||||
export type ListStyleType = $Values<typeof LIST_STYLE_TYPE>;
|
||||
|
||||
export const parseListStyleType = (type: string): ListStyleType => {
|
||||
switch (type) {
|
||||
case 'disc':
|
||||
return LIST_STYLE_TYPE.DISC;
|
||||
case 'circle':
|
||||
return LIST_STYLE_TYPE.CIRCLE;
|
||||
case 'square':
|
||||
return LIST_STYLE_TYPE.SQUARE;
|
||||
case 'decimal':
|
||||
return LIST_STYLE_TYPE.DECIMAL;
|
||||
case 'cjk-decimal':
|
||||
return LIST_STYLE_TYPE.CJK_DECIMAL;
|
||||
case 'decimal-leading-zero':
|
||||
return LIST_STYLE_TYPE.DECIMAL_LEADING_ZERO;
|
||||
case 'lower-roman':
|
||||
return LIST_STYLE_TYPE.LOWER_ROMAN;
|
||||
case 'upper-roman':
|
||||
return LIST_STYLE_TYPE.UPPER_ROMAN;
|
||||
case 'lower-greek':
|
||||
return LIST_STYLE_TYPE.LOWER_GREEK;
|
||||
case 'lower-alpha':
|
||||
return LIST_STYLE_TYPE.LOWER_ALPHA;
|
||||
case 'upper-alpha':
|
||||
return LIST_STYLE_TYPE.UPPER_ALPHA;
|
||||
case 'arabic-indic':
|
||||
return LIST_STYLE_TYPE.ARABIC_INDIC;
|
||||
case 'armenian':
|
||||
return LIST_STYLE_TYPE.ARMENIAN;
|
||||
case 'bengali':
|
||||
return LIST_STYLE_TYPE.BENGALI;
|
||||
case 'cambodian':
|
||||
return LIST_STYLE_TYPE.CAMBODIAN;
|
||||
case 'cjk-earthly-branch':
|
||||
return LIST_STYLE_TYPE.CJK_EARTHLY_BRANCH;
|
||||
case 'cjk-heavenly-stem':
|
||||
return LIST_STYLE_TYPE.CJK_HEAVENLY_STEM;
|
||||
case 'cjk-ideographic':
|
||||
return LIST_STYLE_TYPE.CJK_IDEOGRAPHIC;
|
||||
case 'devanagari':
|
||||
return LIST_STYLE_TYPE.DEVANAGARI;
|
||||
case 'ethiopic-numeric':
|
||||
return LIST_STYLE_TYPE.ETHIOPIC_NUMERIC;
|
||||
case 'georgian':
|
||||
return LIST_STYLE_TYPE.GEORGIAN;
|
||||
case 'gujarati':
|
||||
return LIST_STYLE_TYPE.GUJARATI;
|
||||
case 'gurmukhi':
|
||||
return LIST_STYLE_TYPE.GURMUKHI;
|
||||
case 'hebrew':
|
||||
return LIST_STYLE_TYPE.HEBREW;
|
||||
case 'hiragana':
|
||||
return LIST_STYLE_TYPE.HIRAGANA;
|
||||
case 'hiragana-iroha':
|
||||
return LIST_STYLE_TYPE.HIRAGANA_IROHA;
|
||||
case 'japanese-formal':
|
||||
return LIST_STYLE_TYPE.JAPANESE_FORMAL;
|
||||
case 'japanese-informal':
|
||||
return LIST_STYLE_TYPE.JAPANESE_INFORMAL;
|
||||
case 'kannada':
|
||||
return LIST_STYLE_TYPE.KANNADA;
|
||||
case 'katakana':
|
||||
return LIST_STYLE_TYPE.KATAKANA;
|
||||
case 'katakana-iroha':
|
||||
return LIST_STYLE_TYPE.KATAKANA_IROHA;
|
||||
case 'khmer':
|
||||
return LIST_STYLE_TYPE.KHMER;
|
||||
case 'korean-hangul-formal':
|
||||
return LIST_STYLE_TYPE.KOREAN_HANGUL_FORMAL;
|
||||
case 'korean-hanja-formal':
|
||||
return LIST_STYLE_TYPE.KOREAN_HANJA_FORMAL;
|
||||
case 'korean-hanja-informal':
|
||||
return LIST_STYLE_TYPE.KOREAN_HANJA_INFORMAL;
|
||||
case 'lao':
|
||||
return LIST_STYLE_TYPE.LAO;
|
||||
case 'lower-armenian':
|
||||
return LIST_STYLE_TYPE.LOWER_ARMENIAN;
|
||||
case 'malayalam':
|
||||
return LIST_STYLE_TYPE.MALAYALAM;
|
||||
case 'mongolian':
|
||||
return LIST_STYLE_TYPE.MONGOLIAN;
|
||||
case 'myanmar':
|
||||
return LIST_STYLE_TYPE.MYANMAR;
|
||||
case 'oriya':
|
||||
return LIST_STYLE_TYPE.ORIYA;
|
||||
case 'persian':
|
||||
return LIST_STYLE_TYPE.PERSIAN;
|
||||
case 'simp-chinese-formal':
|
||||
return LIST_STYLE_TYPE.SIMP_CHINESE_FORMAL;
|
||||
case 'simp-chinese-informal':
|
||||
return LIST_STYLE_TYPE.SIMP_CHINESE_INFORMAL;
|
||||
case 'tamil':
|
||||
return LIST_STYLE_TYPE.TAMIL;
|
||||
case 'telugu':
|
||||
return LIST_STYLE_TYPE.TELUGU;
|
||||
case 'thai':
|
||||
return LIST_STYLE_TYPE.THAI;
|
||||
case 'tibetan':
|
||||
return LIST_STYLE_TYPE.TIBETAN;
|
||||
case 'trad-chinese-formal':
|
||||
return LIST_STYLE_TYPE.TRAD_CHINESE_FORMAL;
|
||||
case 'trad-chinese-informal':
|
||||
return LIST_STYLE_TYPE.TRAD_CHINESE_INFORMAL;
|
||||
case 'upper-armenian':
|
||||
return LIST_STYLE_TYPE.UPPER_ARMENIAN;
|
||||
case 'disclosure-open':
|
||||
return LIST_STYLE_TYPE.DISCLOSURE_OPEN;
|
||||
case 'disclosure-closed':
|
||||
return LIST_STYLE_TYPE.DISCLOSURE_CLOSED;
|
||||
case 'none':
|
||||
default:
|
||||
return LIST_STYLE_TYPE.NONE;
|
||||
}
|
||||
};
|
||||
|
||||
export const parseListStyle = (style: CSSStyleDeclaration): ListStyle => {
|
||||
const listStyleImage = parseBackgroundImage(style.getPropertyValue('list-style-image'));
|
||||
return {
|
||||
listStyleType: parseListStyleType(style.getPropertyValue('list-style-type')),
|
||||
listStyleImage: listStyleImage.length ? listStyleImage[0] : null,
|
||||
listStylePosition: parseListStylePosition(style.getPropertyValue('list-style-position'))
|
||||
};
|
||||
};
|
||||
|
||||
const parseListStylePosition = (position: string): ListStylePosition => {
|
||||
switch (position) {
|
||||
case 'inside':
|
||||
return LIST_STYLE_POSITION.INSIDE;
|
||||
case 'outside':
|
||||
default:
|
||||
return LIST_STYLE_POSITION.OUTSIDE;
|
||||
}
|
||||
};
|
11
src/parsing/margin.js
Normal file
11
src/parsing/margin.js
Normal file
@ -0,0 +1,11 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
import Length from '../Length';
|
||||
|
||||
const SIDES = ['top', 'right', 'bottom', 'left'];
|
||||
|
||||
export type Margin = Array<Length>;
|
||||
|
||||
export const parseMargin = (style: CSSStyleDeclaration): Margin => {
|
||||
return SIDES.map(side => new Length(style.getPropertyValue(`margin-${side}`)));
|
||||
};
|
19
src/parsing/overflowWrap.js
Normal file
19
src/parsing/overflowWrap.js
Normal file
@ -0,0 +1,19 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const OVERFLOW_WRAP = {
|
||||
NORMAL: 0,
|
||||
BREAK_WORD: 1
|
||||
};
|
||||
|
||||
export type OverflowWrap = $Values<typeof OVERFLOW_WRAP>;
|
||||
|
||||
export const parseOverflowWrap = (overflow: string): OverflowWrap => {
|
||||
switch (overflow) {
|
||||
case 'break-word':
|
||||
return OVERFLOW_WRAP.BREAK_WORD;
|
||||
case 'normal':
|
||||
default:
|
||||
return OVERFLOW_WRAP.NORMAL;
|
||||
}
|
||||
};
|
22
src/parsing/word-break.js
Normal file
22
src/parsing/word-break.js
Normal file
@ -0,0 +1,22 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const WORD_BREAK = {
|
||||
NORMAL: 'normal',
|
||||
BREAK_ALL: 'break-all',
|
||||
KEEP_ALL: 'keep-all'
|
||||
};
|
||||
|
||||
export type WordBreak = $Values<typeof WORD_BREAK>;
|
||||
|
||||
export const parseWordBreak = (wordBreak: string): WordBreak => {
|
||||
switch (wordBreak) {
|
||||
case 'break-all':
|
||||
return WORD_BREAK.BREAK_ALL;
|
||||
case 'keep-all':
|
||||
return WORD_BREAK.KEEP_ALL;
|
||||
case 'normal':
|
||||
default:
|
||||
return WORD_BREAK.NORMAL;
|
||||
}
|
||||
};
|
@ -26,7 +26,10 @@ const addColorStops = (
|
||||
const maxStop = Math.max.apply(null, gradient.colorStops.map(colorStop => colorStop.stop));
|
||||
const f = 1 / Math.max(1, maxStop);
|
||||
gradient.colorStops.forEach(colorStop => {
|
||||
canvasGradient.addColorStop(f * colorStop.stop, colorStop.color.toString());
|
||||
canvasGradient.addColorStop(
|
||||
Math.floor(Math.max(0, f * colorStop.stop)),
|
||||
colorStop.color.toString()
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -98,6 +101,7 @@ export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
||||
}
|
||||
|
||||
getTarget(): Promise<HTMLCanvasElement> {
|
||||
this.canvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0);
|
||||
return Promise.resolve(this.canvas);
|
||||
}
|
||||
|
||||
@ -231,6 +235,11 @@ export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
||||
text.bounds.top + text.bounds.height
|
||||
);
|
||||
});
|
||||
|
||||
this.ctx.shadowColor = '';
|
||||
this.ctx.shadowOffsetX = 0;
|
||||
this.ctx.shadowOffsetY = 0;
|
||||
this.ctx.shadowBlur = 0;
|
||||
} else {
|
||||
this.ctx.fillText(
|
||||
text.text,
|
||||
|
@ -16,6 +16,7 @@ export default class ForeignObjectRenderer {
|
||||
this.canvas.height = Math.floor(options.height) * options.scale;
|
||||
this.canvas.style.width = `${options.width}px`;
|
||||
this.canvas.style.height = `${options.height}px`;
|
||||
this.ctx.scale(options.scale, options.scale);
|
||||
|
||||
options.logger.log(
|
||||
`ForeignObject renderer initialized (${options.width}x${options.height} at ${options.x},${options.y}) with scale ${options.scale}`
|
||||
|
@ -24,7 +24,7 @@ import type {TextBounds} from '../TextBounds';
|
||||
import {TEXT_DECORATION_STYLE, TEXT_DECORATION_LINE} from '../parsing/textDecoration';
|
||||
import {PATH} from '../drawing/Path';
|
||||
|
||||
class RefTestRenderer implements RenderTarget<string> {
|
||||
export default class RefTestRenderer implements RenderTarget<string> {
|
||||
options: RenderOptions;
|
||||
indent: number;
|
||||
lines: Array<string>;
|
||||
@ -268,5 +268,3 @@ class RefTestRenderer implements RenderTarget<string> {
|
||||
this.lines.push(`${new Array(this.indent + 1).join(' ')}${text}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RefTestRenderer;
|
||||
|
111
tests/node/pseudonodecontent.js
Normal file
111
tests/node/pseudonodecontent.js
Normal file
@ -0,0 +1,111 @@
|
||||
const PseudoNodeContent = require('../../dist/npm/PseudoNodeContent');
|
||||
const assert = require('assert');
|
||||
|
||||
describe('PseudoNodeContent', function() {
|
||||
it('should parse string', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('"hello"'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: 'hello'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse string with (,)', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('"a,b (c) d"'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: 'a,b (c) d'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse string with escaped quotes', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('"3.14\\""'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: '3.14"'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse string with escape', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('"a\\) \\\\ b"'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: 'a) \\ b'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse two strings', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('"hello" \'world\''), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: 'hello'},
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: 'world'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse counter', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('counter(x)'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.COUNTER, name: 'x'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse counters', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('counters(x, "-")'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.COUNTERS, name: 'x', glue: '-'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse strings and counters', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('"["counters(c2, " < ") \']\''), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: '['},
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.COUNTERS, name: 'c2', glue: ' < '},
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: ']'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse counter with format', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('counter(x, lower-greek)'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.COUNTER, name: 'x', format: 'lower-greek'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse counters with format', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('counters(x, "-", upper-roman)'), [
|
||||
{
|
||||
type: PseudoNodeContent.TOKEN_TYPE.COUNTERS,
|
||||
name: 'x',
|
||||
glue: '-',
|
||||
format: 'upper-roman'
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse strings and counters with format', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent("\"[\"counters(c2, ' < ', disc) ']'"), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: '['},
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.COUNTERS, name: 'c2', glue: ' < ', format: 'disc'},
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: ']'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse attr', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('attr(id)'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.ATTRIBUTE, value: 'id'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse url', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('url(http://www.abc.de/f/g.png)'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.URL, value: 'http://www.abc.de/f/g.png'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse open-quote', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('open-quote'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.OPENQUOTE}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse close-quote', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('close-quote'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.CLOSEQUOTE}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse open-quote and string', function() {
|
||||
assert.deepEqual(PseudoNodeContent.parseContent('open-quote "!"'), [
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.OPENQUOTE},
|
||||
{type: PseudoNodeContent.TOKEN_TYPE.STRING, value: '!'}
|
||||
]);
|
||||
});
|
||||
});
|
@ -123,7 +123,7 @@
|
||||
<p><table><tr><td></table><p class="bad"> <!-- <table> closes <p> per the HTML4 DTD -->
|
||||
<blockquote class="first one"><address class="second two"></address></blockquote>
|
||||
<div class="forehead"><div> </div></div>
|
||||
<div class="eyes"><div id="eyes-a"><object data="data:application/x-unknown,ERROR"><object data="http://www.damowmow.com/404/" type="text/html"><object data="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAFy7sgCAAAGsUlEQVRo3u2ZbWwcZxHHf3s%2B7LNbO3ZjXBtowprGODRX0qpNQCjmJKuVKhMl1P2AkCwhFOIKkCBSm9IXavGFKAixIAECwkmWo5MrhRI3Ub40IEwQgp6aIDg3Cd6eEqyIHEteah%2B1E69vhw%2BZtTaX8704ZzkKjHS6271nZ56ZZ%2BY%2F%2F%2BdZKF%2FCwYshx3EkkggLsD1v4FQkEZZYLCbAKyG9%2Ba9EIsG6hnUAf8x74K3aUC3j4%2BM54HcsR2oAIomwZOezkv%2FnSHpYNh%2BNCmAE7xv94zvFdd1bHsjMZmQkPSxAJP%2B%2FfuBLwK54PC7JZFKAVJmzXLBt2w%2FMvcDLwIb8QS8CeJ4nkURYIomw7J%2FYJ8BvSiiXptGGxWds2%2Fa9%2Bnaxh%2BYAD%2Bgt04NDgABTpQY2cvvSFLzw86gWeBVwC8SzlOSv2YeBPfmDBoBHgKmR9LBEEmHZfDTqGykqfkUE0nA78BzQGfSgUeP3wNeTXwXg7MwZDhw4UHL6ra2ti79%2FOvljgG8AZ4H64Lhm4MvAocxsRppGG%2FxcXihlwLIs6R%2FfKV2HO%2F26uA94pdDYUKUZUU7W1RQYXA98Gnhaf5%2FXWX0HeAHYoQonqa4sZSOsSWMCWeC9Yko%2BCQwBe4E6oNc0Tc91XTl1%2BaTsn9gnI%2Blhyc5nZWxsrBIkKSbl2tiic3tW53YDEwOKaoFBrcOfqKee53lG9xsPMjV784r%2F4lO%2FpPvyJ9iyZcuvFSaXK5XYeAZ4CDgGvB3MS4B54LQuWYPeuy4iRFsevsXqpuYoqVQKIH2bK1CuDQNo11o4XUzh%2FcDWYIe1LEtyuZx4niee54njOGKapgfsqlL%2Bl2OjEXg8nxrc1dJ0h3hbtL%2BGCtz7KPBF4CuBe9uB15VafE8hr9qylI3HgG8C2%2FK7VyHZoJj7MrBRm30qFotJMpkU27YlHo%2F7Ha5a%2BV%2FKRkSJ4KuKRLVLKapTjB1SzAVIjY2NSXY%2BKyPpYdk%2FsU9OXT4pruv6BdZbBQfKsVGnvWlIe1VB6VQO8JxC1vZYLCbZ%2BaxsPhpdZDyRRFhG0sPiOE6ldKBg2lRg4xF1YCDIIIKN7DGgD3gH%2BBXwejKZfPrs2tPs%2FvPN2bKuYR1nd7xLKBSSJeqoXKnERjPwNWAG%2BLn2rZuM%2B4Tpml6vaWlp4eLcxVusZq5lCgVgOVKJjRqdX86ffL4D5wIoZACnTpw4wRMdT96i%2FImOJxERAs4uVyqxUacF%2FPdiCj%2BjdRBRGFtwXVdG0sPSdbhTmkYbpH98p2RmM2JZlig1vl0GWo4NQ%2Fn%2Bs5pKRXfwjweaxy7TND3HcRZbfC6X8xVPVQlGy7WxVWlO5XRXFXm6EZmrQuSXYyPE3SiVoEhE6Wyr0u2rumO6zv%2B21AFdQAswC1wCMuUCXCmyWQus103Qg8qlDO0lxwOb%2Fl4FiK3AB3VS%2FuKKLtK%2FgbeAnwG%2FvUODuRw%2FFrR0H1UC75fwu8oJ%2FhFsW5VIG%2FBUgEIN6Y65O4AHu4Ap0zQ9y7LEcZyb9lRBUHQcRyzL8unZVBW5bFWAvAp%2BhDQ2g4F47dUYtlU6obXA54DnVdFLekjUGGifh4AFy7LEdV3xj3X9I66m0QZpGm2QrsOd0j%2B%2BU0bSw5KZzYjrun6HWlAd961i4FfCj0aN1Usau%2Bc1lmuXPFwvAEumUut7tQQvAb%2FXb%2FT0bCAej9cODg7yt%2Bm%2F8q2%2F7OUHZ76PnZ1k2p0mJzlykmPancbOTnL0whHs7CQfb%2B5mx2d3sH79%2BtCRI0c6FeaOr9ICrIQfLvA%2B8BGNXxi4R6HrisJVUWrxAVW2oMFf0Aczim8o3kV6enowDIPjF9%2Fk%2BMU3S3rrjzMMg56eHr%2BxP7qKFbASfojG6kpeDGs1tiW53RxwWT%2Bin5q8w4xpQK5evQpAR30H7ZH2khNvj7TTUd8BgD4rqmu1ZKX8qNeY%2BfHz4zlXDgT5E8tpCTUq7XSBC4Euv8227TV9fX1E73%2BYtvo27BmbS9cvFVTY3bSRFza9yOcf6Gfmygy7d%2B%2Fm%2FPnzF4DvrsBLhnJlJfwIKXxv1PheAE4qK6p4H9AGbNKTuhngBPBPXYRe4IemaT5kWZbR19fHNbmGnZ1k4r3U4glDR30Hm5qjbGjsImJEOHbsGHv27JFz5869o0eFq01Jq%2BmHAXwI6FFKagMTgHM7GzFDS%2BoeLSMv7zjzC9x4Y7gxFovVDAwMEI1GaWlpWSzRVCrFwYMH%2FXfxZ4AfAa8B%2F7lDaGg1%2FQgp43lfK0yqtRMuJa3ceKe5DfgYsCYAZ2ngD8CfAkzqTpW7xY%2F%2FSznyX%2FVeUb2kVmX4AAAAAElFTkSuQmCC">ERROR</object></object></object></div><div id="eyes-b"></div><div id="eyes-c"></div></div> <!-- that's a PNG with 8bit alpha containing two eyes -->
|
||||
<div class="eyes"><div id="eyes-a"><object data="data:application/x-unknown,ERROR"><object data="http://localhost/404/" type="text/html"><object data="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAFy7sgCAAAGsUlEQVRo3u2ZbWwcZxHHf3s%2B7LNbO3ZjXBtowprGODRX0qpNQCjmJKuVKhMl1P2AkCwhFOIKkCBSm9IXavGFKAixIAECwkmWo5MrhRI3Ub40IEwQgp6aIDg3Cd6eEqyIHEteah%2B1E69vhw%2BZtTaX8704ZzkKjHS6271nZ56ZZ%2BY%2F%2F%2BdZKF%2FCwYshx3EkkggLsD1v4FQkEZZYLCbAKyG9%2Ba9EIsG6hnUAf8x74K3aUC3j4%2BM54HcsR2oAIomwZOezkv%2FnSHpYNh%2BNCmAE7xv94zvFdd1bHsjMZmQkPSxAJP%2B%2FfuBLwK54PC7JZFKAVJmzXLBt2w%2FMvcDLwIb8QS8CeJ4nkURYIomw7J%2FYJ8BvSiiXptGGxWds2%2Fa9%2Bnaxh%2BYAD%2Bgt04NDgABTpQY2cvvSFLzw86gWeBVwC8SzlOSv2YeBPfmDBoBHgKmR9LBEEmHZfDTqGykqfkUE0nA78BzQGfSgUeP3wNeTXwXg7MwZDhw4UHL6ra2ti79%2FOvljgG8AZ4H64Lhm4MvAocxsRppGG%2FxcXihlwLIs6R%2FfKV2HO%2F26uA94pdDYUKUZUU7W1RQYXA98Gnhaf5%2FXWX0HeAHYoQonqa4sZSOsSWMCWeC9Yko%2BCQwBe4E6oNc0Tc91XTl1%2BaTsn9gnI%2Blhyc5nZWxsrBIkKSbl2tiic3tW53YDEwOKaoFBrcOfqKee53lG9xsPMjV784r%2F4lO%2FpPvyJ9iyZcuvFSaXK5XYeAZ4CDgGvB3MS4B54LQuWYPeuy4iRFsevsXqpuYoqVQKIH2bK1CuDQNo11o4XUzh%2FcDWYIe1LEtyuZx4niee54njOGKapgfsqlL%2Bl2OjEXg8nxrc1dJ0h3hbtL%2BGCtz7KPBF4CuBe9uB15VafE8hr9qylI3HgG8C2%2FK7VyHZoJj7MrBRm30qFotJMpkU27YlHo%2F7Ha5a%2BV%2FKRkSJ4KuKRLVLKapTjB1SzAVIjY2NSXY%2BKyPpYdk%2FsU9OXT4pruv6BdZbBQfKsVGnvWlIe1VB6VQO8JxC1vZYLCbZ%2BaxsPhpdZDyRRFhG0sPiOE6ldKBg2lRg4xF1YCDIIIKN7DGgD3gH%2BBXwejKZfPrs2tPs%2FvPN2bKuYR1nd7xLKBSSJeqoXKnERjPwNWAG%2BLn2rZuM%2B4Tpml6vaWlp4eLcxVusZq5lCgVgOVKJjRqdX86ffL4D5wIoZACnTpw4wRMdT96i%2FImOJxERAs4uVyqxUacF%2FPdiCj%2BjdRBRGFtwXVdG0sPSdbhTmkYbpH98p2RmM2JZlig1vl0GWo4NQ%2Fn%2Bs5pKRXfwjweaxy7TND3HcRZbfC6X8xVPVQlGy7WxVWlO5XRXFXm6EZmrQuSXYyPE3SiVoEhE6Wyr0u2rumO6zv%2B21AFdQAswC1wCMuUCXCmyWQus103Qg8qlDO0lxwOb%2Fl4FiK3AB3VS%2FuKKLtK%2FgbeAnwG%2FvUODuRw%2FFrR0H1UC75fwu8oJ%2FhFsW5VIG%2FBUgEIN6Y65O4AHu4Ap0zQ9y7LEcZyb9lRBUHQcRyzL8unZVBW5bFWAvAp%2BhDQ2g4F47dUYtlU6obXA54DnVdFLekjUGGifh4AFy7LEdV3xj3X9I66m0QZpGm2QrsOd0j%2B%2BU0bSw5KZzYjrun6HWlAd961i4FfCj0aN1Usau%2Bc1lmuXPFwvAEumUut7tQQvAb%2FXb%2FT0bCAej9cODg7yt%2Bm%2F8q2%2F7OUHZ76PnZ1k2p0mJzlykmPancbOTnL0whHs7CQfb%2B5mx2d3sH79%2BtCRI0c6FeaOr9ICrIQfLvA%2B8BGNXxi4R6HrisJVUWrxAVW2oMFf0Aczim8o3kV6enowDIPjF9%2Fk%2BMU3S3rrjzMMg56eHr%2BxP7qKFbASfojG6kpeDGs1tiW53RxwWT%2Bin5q8w4xpQK5evQpAR30H7ZH2khNvj7TTUd8BgD4rqmu1ZKX8qNeY%2BfHz4zlXDgT5E8tpCTUq7XSBC4Euv8227TV9fX1E73%2BYtvo27BmbS9cvFVTY3bSRFza9yOcf6Gfmygy7d%2B%2Fm%2FPnzF4DvrsBLhnJlJfwIKXxv1PheAE4qK6p4H9AGbNKTuhngBPBPXYRe4IemaT5kWZbR19fHNbmGnZ1k4r3U4glDR30Hm5qjbGjsImJEOHbsGHv27JFz5869o0eFq01Jq%2BmHAXwI6FFKagMTgHM7GzFDS%2BoeLSMv7zjzC9x4Y7gxFovVDAwMEI1GaWlpWSzRVCrFwYMH%2FXfxZ4AfAa8B%2F7lDaGg1%2FQgp43lfK0yqtRMuJa3ceKe5DfgYsCYAZ2ngD8CfAkzqTpW7xY%2F%2FSznyX%2FVeUb2kVmX4AAAAAElFTkSuQmCC">ERROR</object></object></object></div><div id="eyes-b"></div><div id="eyes-c"></div></div> <!-- that's a PNG with 8bit alpha containing two eyes -->
|
||||
<div class="nose"><div><div></div></div></div>
|
||||
<div class="empty"><div></div></div>
|
||||
<div class="smile"><div><div><span><em><strong></strong></em></span></div></div></div>
|
||||
@ -138,4 +138,4 @@
|
||||
<div class="image-height-test"><table><tr><td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAFSDNYfAAAAaklEQVR42u3XQQrAIAwAQeP%2F%2F6wf8CJBJTK9lnQ7FpHGaOurt1I34nfH9pMMZAZ8BwMGEvvh%2BBsJCAgICLwIOA8EBAQEBAQEBAQEBK79H5RfIQAAAAAAAAAAAAAAAAAAAAAAAAAAAID%2FABMSqAfj%2FsLmvAAAAABJRU5ErkJggg%3D%3D" alt=""></td></tr></table></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style type="text/css">
|
||||
div {
|
||||
.box {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: inline-block;
|
||||
@ -63,6 +63,21 @@
|
||||
border-radius: 200px;
|
||||
}
|
||||
|
||||
.gauge{
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background: green;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.gauge1{ border-radius: 25px 25px 0 0 / 25px 25px 0 0; }
|
||||
.gauge2{ border-radius: 100px 100px 0 0 / 50px 50px 0 0; }
|
||||
.gauge3{ border-radius: 100px 100px 0 0 / 100px 100px 0 0; }
|
||||
.gauge4{ border-radius: 300px 100px 0 0 / 100px 100px 0 0; }
|
||||
.gauge5{ border-radius: 400px 400px 50px 50px / 50px 50px 50px 50px; }
|
||||
|
||||
|
||||
html {
|
||||
background: #3a84c3;
|
||||
}
|
||||
@ -70,11 +85,16 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="box1"> </div>
|
||||
<div class="box2"> </div>
|
||||
<div class="box3"> </div>
|
||||
<div class="box4"> </div>
|
||||
<div class="box5"> </div>
|
||||
<div class="box6"> </div>
|
||||
<div class="box box1"> </div>
|
||||
<div class="box box2"> </div>
|
||||
<div class="box box3"> </div>
|
||||
<div class="box box4"> </div>
|
||||
<div class="box box5"> </div>
|
||||
<div class="box box6"> </div>
|
||||
<div class="gauge gauge1"></div>
|
||||
<div class="gauge gauge2"></div>
|
||||
<div class="gauge gauge3"></div>
|
||||
<div class="gauge gauge4"></div>
|
||||
<div class="gauge gauge5"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -12,6 +12,7 @@
|
||||
margin: 10px;
|
||||
background:#6F428C;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.box1 {
|
||||
@ -33,6 +34,12 @@
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
.box5 {
|
||||
border-style: none;
|
||||
border-bottom: 50px solid #807d32;
|
||||
border-bottom-width: 50px;
|
||||
}
|
||||
|
||||
html {
|
||||
background: #3a84c3;
|
||||
}
|
||||
@ -43,5 +50,6 @@
|
||||
<div class="box2"> </div>
|
||||
<div class="box3"> </div>
|
||||
<div class="box4"> </div>
|
||||
<div class="box5"> </div>
|
||||
</body>
|
||||
</html>
|
||||
|
42
tests/reftests/dynamicstyle.html
Normal file
42
tests/reftests/dynamicstyle.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dynamic style</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../test.js"></script>
|
||||
<style id="style">
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/CWB0XYA8bzo0kSThX0UTuA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
|
||||
}
|
||||
body { font-family: "Roboto", serif }
|
||||
|
||||
div {
|
||||
padding: 20px;
|
||||
}
|
||||
.div1 {
|
||||
background: darkred;
|
||||
color: white;
|
||||
}
|
||||
@media (min-width: 10px) {
|
||||
.div1 {
|
||||
background: darkgreen;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.querySelector('#style').sheet.insertRule(
|
||||
'.div2 { background: darkgreen; color:white; }',
|
||||
document.querySelector('#style').sheet.cssRules.length
|
||||
);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="div1">Static styles</div>
|
||||
<div class="div2">Dynamic styles</div>
|
||||
</body>
|
||||
</html>
|
||||
|
11
tests/reftests/images/doctype.html
Normal file
11
tests/reftests/images/doctype.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<img src="../../assets/image.jpg" />
|
||||
<img src="../../assets/image2.jpg" style="display:block;" />
|
||||
<br>
|
||||
</body>
|
||||
</html>
|
102
tests/reftests/list/liststyle.html
Normal file
102
tests/reftests/list/liststyle.html
Normal file
@ -0,0 +1,102 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>List tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
|
||||
<style>
|
||||
li {
|
||||
margin: 10px 5%;
|
||||
}
|
||||
.list1 {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
.list2 {
|
||||
list-style-image: url(../../assets/image.jpg);
|
||||
}
|
||||
|
||||
.list3 {
|
||||
list-style-image: linear-gradient(60deg, deeppink, aquamarine);
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.list4 {
|
||||
}
|
||||
|
||||
.list5 {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
.list6 {
|
||||
list-style-type: hiragana;
|
||||
}
|
||||
|
||||
.list7 {
|
||||
list-style-type: simp-chinese-informal;
|
||||
}
|
||||
|
||||
.list8 {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
.list8 li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.list9 {
|
||||
display: list-item;
|
||||
list-style-type: lower-alpha;
|
||||
margin: 10px;
|
||||
position: relative;
|
||||
left: 200px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul class="list1">
|
||||
<li>Alpha</li>
|
||||
<li>Beta</li>
|
||||
<li>Gamma</li>
|
||||
</ul>
|
||||
<ul class="list2">
|
||||
<li>Alpha</li>
|
||||
<li>Beta</li>
|
||||
<li>Gamma</li>
|
||||
</ul>
|
||||
<ul class="list3">
|
||||
<li>Alpha</li>
|
||||
<li>Beta</li>
|
||||
<li>Gamma</li>
|
||||
</ul>
|
||||
<ol class="list4" start="-1">
|
||||
<li>Alpha</li>
|
||||
<li>Beta</li>
|
||||
<li>Gamma</li>
|
||||
</ol>
|
||||
<ol class="list5">
|
||||
<li>Alpha</li>
|
||||
<li value="5">Beta</li>
|
||||
<li>Gamma</li>
|
||||
</ol>
|
||||
<ol class="list6">
|
||||
<li>Alpha</li>
|
||||
<li>Beta</li>
|
||||
<li>Gamma</li>
|
||||
</ol>
|
||||
<ol class="list7">
|
||||
<li>Alpha</li>
|
||||
<li>Beta</li>
|
||||
<li>Gamma</li>
|
||||
</ol>
|
||||
<ol class="list8">
|
||||
<li>Alpha</li>
|
||||
<li>Beta</li>
|
||||
<li>Gamma</li>
|
||||
</ol>
|
||||
<div class="list9">Alpha</div>
|
||||
<div class="list9">Beta</div>
|
||||
<div class="list9">Gamma</div>
|
||||
</body>
|
||||
</html>
|
@ -4,7 +4,9 @@
|
||||
<title>element render test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script>
|
||||
|
||||
h2cOptions = {ignoreElements: function(element) {
|
||||
return element.className === 'ignored';
|
||||
}};
|
||||
</script>
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
@ -15,7 +17,7 @@
|
||||
background: green;
|
||||
}
|
||||
|
||||
#ignored {
|
||||
#ignored, .ignored {
|
||||
background: red;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
@ -32,10 +34,11 @@
|
||||
<div id="ignored" data-html2canvas-ignore>
|
||||
great failure
|
||||
</div>
|
||||
<div class="ignored">
|
||||
ignore predicate
|
||||
</div>
|
||||
<div id="div1">
|
||||
great success
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
42
tests/reftests/options/onclone.html
Normal file
42
tests/reftests/options/onclone.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>element render test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script>
|
||||
h2cOptions = {onclone: function(document) {
|
||||
const remove = document.querySelector('.ignored');
|
||||
remove.parentNode.removeChild(remove);
|
||||
}};
|
||||
</script>
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
#div1 {
|
||||
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: green;
|
||||
}
|
||||
|
||||
#ignored, .ignored {
|
||||
background: red;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="ignored">
|
||||
ignore during onclone
|
||||
</div>
|
||||
<div id="div1">
|
||||
great success
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,84 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Overflow tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
.small{
|
||||
font-size:14px;
|
||||
}
|
||||
<head>
|
||||
<title>Overflow tests</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
.small{
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
.medium{
|
||||
font-size:18px;
|
||||
}
|
||||
.large{
|
||||
font-size:24px;
|
||||
}
|
||||
div{
|
||||
background: #ccc;
|
||||
border:6px solid black;
|
||||
width:300px;
|
||||
height:200px;
|
||||
margin: 0 0 60px 0;
|
||||
}
|
||||
h1 {
|
||||
margin:0;
|
||||
line-height: 40px;
|
||||
}
|
||||
body {
|
||||
font-family: Arial;
|
||||
line-height: 17px;
|
||||
font-size: 1em;
|
||||
}
|
||||
</style>
|
||||
.medium{
|
||||
font-size:18px;
|
||||
}
|
||||
.large{
|
||||
font-size:24px;
|
||||
}
|
||||
div{
|
||||
background: #ccc;
|
||||
border:6px solid black;
|
||||
width:300px;
|
||||
height:200px;
|
||||
margin: 0 0 60px 0;
|
||||
}
|
||||
h1 {
|
||||
margin:0;
|
||||
line-height: 40px;
|
||||
}
|
||||
body {
|
||||
font-family: Arial;
|
||||
line-height: 17px;
|
||||
font-size: 1em;
|
||||
}
|
||||
.cell {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
float: right;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
.cell p {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
<h1>Overflow: visible</h1>
|
||||
<div> 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 Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
</div>
|
||||
.visible {
|
||||
overflow: visible;
|
||||
}
|
||||
.hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
.scroll {
|
||||
overflow: scroll;
|
||||
}
|
||||
.auto {
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Overflow: hidden</h1>
|
||||
<div style="overflow:hidden;float: right; height: 100px; border-radius: 100%; border-color: transparent;">
|
||||
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
|
||||
</head>
|
||||
<body>
|
||||
<div class="cell">
|
||||
visible
|
||||
<p class="visible">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
|
||||
</div>
|
||||
<div class="cell">
|
||||
hidden
|
||||
<p class="hidden">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
|
||||
</div>
|
||||
<div class="cell">
|
||||
scroll
|
||||
<p class="scroll">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
|
||||
</div>
|
||||
<div class="cell">
|
||||
auto
|
||||
<p class="auto">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
|
||||
</div>
|
||||
<h1>Overflow: visible</h1>
|
||||
<div> 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 Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
</div>
|
||||
|
||||
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.
|
||||
<h1>Overflow: hidden</h1>
|
||||
<div style="overflow:hidden;float: right; height: 100px; border-radius: 100%; border-color: transparent;">
|
||||
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="../../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 />
|
||||
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
|
||||
|
||||
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 Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
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 Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
</div>
|
||||
</div>
|
||||
<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="../../assets/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 />
|
||||
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
|
||||
|
||||
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 Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
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 Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow:scroll; height: 100px;" id="scroll">
|
||||
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
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow:scroll; height: 100px;" id="scroll">
|
||||
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="../../assets/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 />
|
||||
<div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
|
||||
|
||||
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 Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
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 Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
|
||||
|
||||
</div>
|
||||
<script>
|
||||
document.querySelector('#scroll').scrollTo(0, 200);
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</div>
|
||||
<script>
|
||||
document.querySelector('#scroll').scrollTo(0, 200);
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
104
tests/reftests/pseudo-content.html
Normal file
104
tests/reftests/pseudo-content.html
Normal file
@ -0,0 +1,104 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="../test.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
quotes: "<{" "}>" "->" "<-" "(" ")" "-:" ":-";
|
||||
}
|
||||
|
||||
.counter1,
|
||||
.counter2,
|
||||
.quotes1,
|
||||
.attr-url {
|
||||
border: 1px solid deepskyblue;
|
||||
padding: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.counter1 {
|
||||
counter-reset: c1 3;
|
||||
}
|
||||
|
||||
.counter1 > div::before {
|
||||
content: "«\"" counter(c1) "\»";
|
||||
counter-increment: c1 -1;
|
||||
}
|
||||
|
||||
.counter2 {
|
||||
counter-reset: c2;
|
||||
}
|
||||
|
||||
.counter2 > div::before {
|
||||
content: "["counters(c2, " < ", upper-roman) ']';
|
||||
counter-increment: c2 2;
|
||||
}
|
||||
|
||||
.quotes1::before {
|
||||
content: open-quote "!" open-quote close-quote open-quote;
|
||||
}
|
||||
|
||||
.quotes1::after {
|
||||
content: "!" close-quote close-quote;
|
||||
}
|
||||
|
||||
.quotes2 {
|
||||
quotes: "«" "»" "“" "”";
|
||||
}
|
||||
|
||||
.quotes2::before {
|
||||
content: open-quote;
|
||||
}
|
||||
|
||||
.quotes2::after {
|
||||
content: close-quote;
|
||||
}
|
||||
|
||||
.attr-url > div::after {
|
||||
content: url(../assets/image.jpg) "///" attr(data-text);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="counter1">
|
||||
<div>A</div>
|
||||
<div>B</div>
|
||||
<div>C</div>
|
||||
<div>D</div>
|
||||
</div>
|
||||
|
||||
<div class="counter2">
|
||||
<div>A</div>
|
||||
<div>B</div>
|
||||
<div>
|
||||
C
|
||||
<div class="counter2">
|
||||
<div>a</div>
|
||||
<div>b</div>
|
||||
<div>
|
||||
c
|
||||
<div class="counter2">
|
||||
<div>Aa</div>
|
||||
<div>Bb</div>
|
||||
<div>Cc</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>D</div>
|
||||
</div>
|
||||
|
||||
<div class="quotes1">
|
||||
Hello
|
||||
<div class="quotes2">
|
||||
Quoted
|
||||
<div class="quotes2">World</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="attr-url">
|
||||
<div data-text="Hello World"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
40
tests/reftests/text/line-break.html
Normal file
40
tests/reftests/text/line-break.html
Normal file
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>word-break</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial;
|
||||
}
|
||||
.test span {
|
||||
line-break: normal;
|
||||
}
|
||||
|
||||
.strict span {
|
||||
line-break: strict;
|
||||
}
|
||||
p.test{
|
||||
border: 1px solid gray;
|
||||
color: blue;
|
||||
width: 6em;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- iteration marks -->
|
||||
<p class="test" lang="ja">
|
||||
<span>サンプルぁルぁルぁルぁルぁルぁルぁぁぁぁ文ンプル–文々サンプル文</span>
|
||||
</p>
|
||||
|
||||
<p class="test strict" lang="ja">
|
||||
<span>サンプルぁルぁルぁルぁルぁルぁルぁぁぁぁ文文文文文‐–〜゠サンプル文々サンプル文</span>
|
||||
</p>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
</body>
|
||||
</html>
|
58
tests/reftests/text/overflow-wrap.html
Normal file
58
tests/reftests/text/overflow-wrap.html
Normal file
@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>word-break</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial;
|
||||
}
|
||||
.normal {
|
||||
width: 13em;
|
||||
background: gold;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.break-word {
|
||||
width: 13em;
|
||||
background: lime;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.word-normal {
|
||||
width: 13em;
|
||||
background: gold;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
.word-break-word {
|
||||
width: 13em;
|
||||
background: lime;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p>1. <code>overflow-wrap: normal</code></p>
|
||||
<p class="normal">FStrPrivFinÄndG (Gesetz zur Änderung des
|
||||
Fernstraßenbauprivatfinanzierungsgesetzes
|
||||
und straßenverkehrsrechtlicher Vorschriften)</p>
|
||||
<p>2. <code>overflow-wrap: break-word</code></p>
|
||||
<p class="break-word">FStrPrivFinÄndG (Gesetz zur Änderung des
|
||||
Fernstraßenbauprivatfinanzierungsgesetzes
|
||||
und straßenverkehrsrechtlicher Vorschriften)</p>
|
||||
<p>3. <code>word-wrap: normal</code></p>
|
||||
<p class="word-normal">FStrPrivFinÄndG (Gesetz zur Änderung des
|
||||
Fernstraßenbauprivatfinanzierungsgesetzes
|
||||
und straßenverkehrsrechtlicher Vorschriften)</p>
|
||||
<p>4. <code>word-wrap: break-word</code></p>
|
||||
<p class="word-break-word">FStrPrivFinÄndG (Gesetz zur Änderung des
|
||||
Fernstraßenbauprivatfinanzierungsgesetzes
|
||||
und straßenverkehrsrechtlicher Vorschriften)</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -50,6 +50,18 @@
|
||||
<span>testing with transparent</span>
|
||||
<strong>testing with low opacity</strong>
|
||||
</div>
|
||||
|
||||
<div id="capture" style="padding: 10px; background: #f5da55; border:2px solid blue;">
|
||||
<h4 style="color: #000;text-shadow:0px 0px 5px green;">Hello world!</h4>
|
||||
<div id="sampleDiv" style="border:2px solid red;padding:5px;margin:5px;">Sample Div with Border</div>
|
||||
<div style="border:2px solid red;background-color:pink; padding:5px;margin:5px;">Sample Div with Border</div>
|
||||
<div style="border:2px solid red;color:blue; padding:5px;margin:5px;">Sample Div with Border</div>
|
||||
<div style="border:2px solid red;padding:5px;margin:5px;">Sample Div with Border</div>
|
||||
<div style="border:2px solid red;padding:5px;text-shadow:0px 0px 5px red;margin:5px;">Sample Div with Border</div>
|
||||
<div style="border:2px solid red;padding:5px;margin:5px;">Sample Div with Border</div>
|
||||
<div style="border:2px solid red;padding:5px;margin:5px;">Sample Div with Border</div>
|
||||
</div>
|
||||
|
||||
<p class="white-text-with-blue-shadow">Sed ut perspiciatis unde omnis iste
|
||||
natus error sit voluptatem accusantium doloremque laudantium,
|
||||
totam rem aperiam, eaque ipsa quae ab illo inventore.</p>
|
||||
|
28
tests/reftests/text/thai.html
Normal file
28
tests/reftests/text/thai.html
Normal file
@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Thai text</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
.text-block {
|
||||
width: 500px;
|
||||
font-family: serif;
|
||||
float:left;
|
||||
text-align:justify;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="text" class="text-block">
|
||||
<p>.....</p>
|
||||
<p>ทดสอบ แบบกำหนดรูปแบบ ทำ ที นี่ นู่น นั่น นี้ มี หรือ ไม่ </p>
|
||||
<p>ภาษาไทย เป็นภาษาราชการของประเทศไทย และภาษาแม่ของชาวไทย และชนเชื้อสายอื่นในประเทศไทย ภาษาไทยเป็นภาษาในกลุ่มภาษาไท ซึ่งเป็นกลุ่มย่อยของตระกูลภาษาไท-กะได สันนิษฐานว่า ภาษาในตระกูลนี้มีถิ่นกำเนิดจากทางตอนใต้ของประเทศจีน และนักภาษาศาสตร์บางส่วนเสนอว่า ภาษาไทยน่าจะมีความเชื่อมโยงกับตระกูลภาษาออสโตร-เอเชียติก ตระกูลภาษาออสโตรนีเซียน และตระกูลภาษาจีน-ทิเบต</p>
|
||||
<p>ภาษาไทยเป็นภาษาที่มีระดับเสียงของคำแน่นอนหรือวรรณยุกต์เช่นเดียวกับภาษาจีน และออกเสียงแยกคำต่อคำ ทำให้เป็นที่ลำบากของชาวต่างชาติเนื่องจากการออกเสียงวรรณยุกต์ที่เป็นเอกลักษณ์ของแต่ละคำ และการสะกดคำที่ซับซ้อน</p>
|
||||
<p>คำว่า ไทย หมายความว่า อิสรภาพ เสรีภาพ หรืออีกความหมายหนึ่งคือ ใหญ่ ยิ่งใหญ่ เพราะการจะเป็นอิสระได้จะต้องมีกำลังที่มากกว่า แข็งแกร่งกว่า เพื่อป้องกันการรุกรานจากข้าศึก คำนี้เป็นคำไทยแท้ที่เกิดจากการสร้างคำที่เรียก "การลากคำเข้าวัด" ซึ่งเป็นการลากความวิธีหนึ่ง ตามหลักคติชนวิทยา คนไทยเป็นชนชาติที่นับถือกันว่า ภาษาบาลี ซึ่งเป็นภาษาที่บันทึกพระธรรมคำสอนของพระพุทธเจ้าเป็นภาษาอันศักดิ์สิทธิ์และเป็นมงคล เมื่อคนไทยต้องการตั้งชื่อประเทศว่า ไท ซึ่งเป็นคำไทยแท้ จึงเติมตัว ย เข้าไปข้างท้าย เพื่อให้มีลักษณะคล้ายคำในภาษาบาลี - สันสกฤตเพื่อความเป็นมงคลตามความเชื่อของตน ภาษาไทยจึงหมายถึงภาษาของชนชาติไทยผู้เป็นไทนั่นเอง</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
50
tests/reftests/text/word-break.html
Normal file
50
tests/reftests/text/word-break.html
Normal file
@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>word-break</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<script type="text/javascript" src="../../test.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial;
|
||||
}
|
||||
.narrow {
|
||||
padding: 5px;
|
||||
border: 1px solid;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.normal {
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.breakAll {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.keep {
|
||||
word-break: keep-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p>1. <code>word-break: normal</code></p>
|
||||
<p class="normal narrow">This is a long and
|
||||
Supercalifragilisticexpialidocious sentence.
|
||||
次の単語グレートブリテンおよび北アイルランド連合王国で本当に大きな言葉</p>
|
||||
|
||||
<p>2. <code>word-break: break-all</code></p>
|
||||
<p class="breakAll narrow">This is a long and
|
||||
Supercalifragilisticexpialidocious sentence.
|
||||
次の単語グレートブリテンおよび北アイルランド連合王国で本当に大きな言葉</p>
|
||||
|
||||
<p>3. <code>word-break: keep-all</code></p>
|
||||
<p class="keep narrow">This is a long and
|
||||
Supercalifragilisticexpialidocious sentence.
|
||||
次の単語グレートブリテンおよび北アイルランド連合王国で本当に大きな言葉</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -20,8 +20,7 @@ var REFTEST = window.location.search.indexOf('reftest') !== -1;
|
||||
(typeof Promise === 'undefined' ? ['/node_modules/promise-polyfill/promise.min'] : [])
|
||||
.concat([
|
||||
'/node_modules/jquery/dist/jquery.min',
|
||||
'/dist/html2canvas',
|
||||
'/dist/RefTestRenderer'
|
||||
'/dist/html2canvas'
|
||||
])
|
||||
.forEach(appendScript);
|
||||
|
||||
|
@ -75,7 +75,7 @@ const assertPath = (result, expected, desc) => {
|
||||
.filter(test => {
|
||||
return (
|
||||
!Array.isArray(reftests.ignoredTests[test]) ||
|
||||
reftests.ignoredTests[test].indexOf(query.browser) === -1
|
||||
reftests.ignoredTests[test].indexOf(platform.name) === -1
|
||||
);
|
||||
})
|
||||
.forEach(url => {
|
||||
@ -342,79 +342,37 @@ const assertPath = (result, expected, desc) => {
|
||||
}
|
||||
|
||||
if (window.__karma__) {
|
||||
const MAX_CHUNK_SIZE = 75000;
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr =
|
||||
'withCredentials' in new XMLHttpRequest()
|
||||
? new XMLHttpRequest()
|
||||
: new XDomainRequest();
|
||||
|
||||
const sendScreenshot = (tries, body, server) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr =
|
||||
'withCredentials' in new XMLHttpRequest()
|
||||
? new XMLHttpRequest()
|
||||
: new XDomainRequest();
|
||||
|
||||
xhr.onload = () => {
|
||||
if (
|
||||
typeof xhr.status !== 'number' ||
|
||||
xhr.status === 200
|
||||
) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
`Failed to send screenshot with status ${xhr.status}`
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.onerror = reject;
|
||||
|
||||
xhr.open('POST', server, true);
|
||||
xhr.send(body);
|
||||
}).catch(e => {
|
||||
if (tries > 0) {
|
||||
// Older edge browsers and some safari browsers have issues sending large xhr through saucetunnel
|
||||
const data = canvas.toDataURL();
|
||||
const totalCount = Math.ceil(
|
||||
data.length / MAX_CHUNK_SIZE
|
||||
);
|
||||
return Promise.all(
|
||||
Array.apply(
|
||||
null,
|
||||
Array(totalCount)
|
||||
).map((x, part) =>
|
||||
sendScreenshot(
|
||||
0,
|
||||
JSON.stringify({
|
||||
screenshot: data.substr(
|
||||
part * MAX_CHUNK_SIZE,
|
||||
MAX_CHUNK_SIZE
|
||||
),
|
||||
part,
|
||||
totalCount,
|
||||
test: url,
|
||||
platform: {
|
||||
name: platform.name,
|
||||
version: platform.version
|
||||
}
|
||||
}),
|
||||
'http://localhost:8000/screenshot/chunk'
|
||||
)
|
||||
)
|
||||
xhr.onload = () => {
|
||||
if (
|
||||
typeof xhr.status !== 'number' ||
|
||||
xhr.status === 200
|
||||
) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
`Failed to send screenshot with status ${xhr.status}`
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.onerror = reject;
|
||||
|
||||
return Promise.reject(e);
|
||||
});
|
||||
};
|
||||
return sendScreenshot(
|
||||
1,
|
||||
JSON.stringify({
|
||||
xhr.open('POST', 'http://localhost:8000/screenshot', true);
|
||||
xhr.send(JSON.stringify({
|
||||
screenshot: canvas.toDataURL(),
|
||||
test: url,
|
||||
platform: {
|
||||
name: platform.name,
|
||||
version: platform.version
|
||||
}
|
||||
}),
|
||||
'http://localhost:8000/screenshot'
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -18,7 +18,7 @@ const plugins = [
|
||||
];
|
||||
|
||||
const modules = {
|
||||
loaders: [{
|
||||
rules: [{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader'
|
||||
@ -27,20 +27,26 @@ const modules = {
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
mode: 'development',
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
filename: './dist/html2canvas.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'html2canvas.js',
|
||||
library: 'html2canvas',
|
||||
libraryExport: 'default',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
module: modules,
|
||||
plugins
|
||||
},
|
||||
{
|
||||
mode: 'production',
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
filename: './dist/html2canvas.min.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'html2canvas.min.js',
|
||||
library: 'html2canvas',
|
||||
libraryExport: 'default',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
module: modules,
|
||||
@ -54,19 +60,24 @@ module.exports = [
|
||||
]
|
||||
},
|
||||
{
|
||||
mode: 'production',
|
||||
entry: './src/renderer/RefTestRenderer.js',
|
||||
output: {
|
||||
filename: './dist/RefTestRenderer.js',
|
||||
path: path.resolve(__dirname, 'build'),
|
||||
filename: 'RefTestRenderer.js',
|
||||
library: 'RefTestRenderer',
|
||||
libraryExport: 'default',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
module: modules,
|
||||
plugins
|
||||
},
|
||||
{
|
||||
mode: 'production',
|
||||
entry: './tests/testrunner.js',
|
||||
output: {
|
||||
filename: './build/testrunner.js',
|
||||
path: path.resolve(__dirname, 'build'),
|
||||
filename: 'testrunner.js',
|
||||
library: 'testrunner',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
|
17361
www/package-lock.json
generated
17361
www/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,30 +5,36 @@
|
||||
"private": true,
|
||||
"author": "Niklas von Hertzen",
|
||||
"dependencies": {
|
||||
"cpy-cli": "^1.0.1",
|
||||
"gatsby": "^1.9.127",
|
||||
"gatsby-link": "^1.6.30",
|
||||
"gatsby-plugin-catch-links": "^1.0.13",
|
||||
"gatsby-plugin-glamor": "^1.6.9",
|
||||
"gatsby-plugin-google-analytics": "^1.0.14",
|
||||
"gatsby-plugin-react-helmet": "^1.0.8",
|
||||
"gatsby-plugin-twitter": "^1.0.14",
|
||||
"gatsby-plugin-typography": "^1.7.10",
|
||||
"gatsby-remark-prismjs": "^1.2.10",
|
||||
"gatsby-source-filesystem": "^1.5.9",
|
||||
"gatsby-transformer-remark": "^1.7.23",
|
||||
"gzip-size": "^4.1.0",
|
||||
"html2canvas": "1.0.0-alpha.3",
|
||||
"cpy-cli": "^2.0.0",
|
||||
"gatsby": "^2.3.14",
|
||||
"gatsby-link": "^2.0.16",
|
||||
"gatsby-plugin-catch-links": "^2.0.13",
|
||||
"gatsby-plugin-glamor": "^2.0.9",
|
||||
"gatsby-plugin-google-analytics": "^2.0.18",
|
||||
"gatsby-plugin-react-helmet": "^3.0.12",
|
||||
"gatsby-plugin-twitter": "^2.0.13",
|
||||
"gatsby-plugin-typography": "^2.2.10",
|
||||
"gatsby-remark-prismjs": "^3.2.7",
|
||||
"gatsby-source-filesystem": "^2.0.28",
|
||||
"gatsby-transformer-remark": "^2.3.8",
|
||||
"glamor": "^2.20.40",
|
||||
"gzip-size": "^5.0.0",
|
||||
"html2canvas": "^1.0.0-alpha.12",
|
||||
"mkdirp": "^0.5.1",
|
||||
"typography": "^0.16.6",
|
||||
"typography-theme-github": "^0.15.10"
|
||||
"prismjs": "^1.16.0",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-typography": "^0.16.19",
|
||||
"typography": "^0.16.19",
|
||||
"typography-theme-github": "^0.16.19"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "n/a",
|
||||
"scripts": {
|
||||
"copybuild": "mkdirp public/dist && cpy ../dist/*.js public/dist",
|
||||
"build": "npm run copybuild && gatsby build",
|
||||
"develop": "gatsby develop",
|
||||
"start": "gatsby develop",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ class CanvasContainer extends Component {
|
||||
>
|
||||
<img
|
||||
src={close}
|
||||
alt="Close"
|
||||
css={{position: 'absolute', right: '20px', top: '20px', cursor: 'pointer'}}
|
||||
/>
|
||||
</div>
|
||||
@ -66,7 +67,9 @@ export default class Example extends Component {
|
||||
position: 'fixed',
|
||||
zIndex: '1000',
|
||||
right: '-348.4px',
|
||||
bottom: '-327.2px'
|
||||
bottom: '-327.2px',
|
||||
visibility: this.state.open ? 'visible' : 'hidden',
|
||||
transition: 'visibility 0.3s cubic-bezier(0.42, 0, 0.58, 1)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@ -107,6 +110,7 @@ export default class Example extends Component {
|
||||
cursor: 'pointer',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
borderRadius: '50%',
|
||||
visibility: 'visible',
|
||||
boxShadow:
|
||||
'0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)'
|
||||
}}
|
||||
@ -120,6 +124,7 @@ export default class Example extends Component {
|
||||
flex: 1,
|
||||
margin: 0
|
||||
}}
|
||||
alt="Try html2canvas"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,14 +4,25 @@ export default () =>
|
||||
<footer
|
||||
css={{
|
||||
backgroundColor: '#558b2f',
|
||||
color: 'rgba(255,255,255,0.8)',
|
||||
color: 'rgba(255,255,255, 0.8)',
|
||||
fontWeight: 300,
|
||||
minHeight: '50px',
|
||||
lineHeight: '50px',
|
||||
padding: '10px 0px'
|
||||
}}
|
||||
>
|
||||
<div css={{margin: '0 auto', width: '85%'}}>
|
||||
<div
|
||||
css={{
|
||||
margin: '0 auto',
|
||||
fontSize: '10.5px',
|
||||
textAlign: 'center',
|
||||
'@media(min-width: 1000px)': {
|
||||
textAlign: 'left',
|
||||
width: '85%',
|
||||
fontSize: '14.5px'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Created by{' '}
|
||||
<a href="https://hertzen.com" css={{color: '#fff', fontWeight: 'bold'}}>
|
||||
Niklas von Hertzen
|
||||
|
@ -36,9 +36,22 @@ table {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
table tr:nth-child(odd) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
th:first-child, td:first-child {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
:not(pre) > code {
|
@ -1,19 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Helmet from 'react-helmet';
|
||||
require('prismjs/themes/prism-solarizedlight.css');
|
||||
import './index.css';
|
||||
import './layout.css';
|
||||
import Example from '../components/example';
|
||||
|
||||
require('prismjs/themes/prism-solarizedlight.css');
|
||||
const TemplateWrapper = ({children}) =>
|
||||
<div>
|
||||
<Helmet title="html2canvas - Screenshots with JavaScript" />
|
||||
{children()}
|
||||
{children}
|
||||
<Example />
|
||||
</div>;
|
||||
|
||||
TemplateWrapper.propTypes = {
|
||||
children: PropTypes.func
|
||||
};
|
||||
|
||||
export default TemplateWrapper;
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import React, {Component} from 'react';
|
||||
import Link from 'gatsby-link';
|
||||
import logo from '../images/logo.svg';
|
||||
import menu from '../images/ic_menu_black_24px.svg';
|
||||
|
||||
const lineLinkStyle = {
|
||||
lineHeight: '44px',
|
||||
@ -16,15 +17,17 @@ const lineLinkStyle = {
|
||||
};
|
||||
|
||||
const navStyle = {
|
||||
height: '100%',
|
||||
width: '300px',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
'@media(min-width: 1000px)': {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '300px',
|
||||
boxShadow:
|
||||
'0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)',
|
||||
height: '100%'
|
||||
},
|
||||
fontSize: '13px',
|
||||
backgroundColor: '#fff',
|
||||
boxShadow:
|
||||
'0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)'
|
||||
backgroundColor: '#fff'
|
||||
};
|
||||
|
||||
const links = [
|
||||
@ -36,31 +39,71 @@ const links = [
|
||||
{href: '/faq', text: 'FAQ'}
|
||||
];
|
||||
|
||||
export default () =>
|
||||
<div css={navStyle}>
|
||||
<Link to="/" css={{background: '#558b2f', display: 'block', padding: '24px 30px 24px'}}>
|
||||
<img src={logo} css={{margin: 0}} />
|
||||
</Link>
|
||||
<ul
|
||||
style={{
|
||||
listStyle: 'none',
|
||||
margin: 0,
|
||||
padding: 0
|
||||
}}
|
||||
>
|
||||
{links.map(({href, text}, i) =>
|
||||
<li style={{padding: 0, margin: 0}} key={i}>
|
||||
<Link
|
||||
to={href}
|
||||
css={lineLinkStyle}
|
||||
activeStyle={{
|
||||
backgroundColor: '#7cb342',
|
||||
color: '#fff'
|
||||
export default class Navigation extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {open: false};
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div css={navStyle}>
|
||||
<div
|
||||
css={{
|
||||
background: '#558b2f',
|
||||
alignItems: 'center',
|
||||
padding: '24px 30px 24px 0px',
|
||||
display: 'flex',
|
||||
'@media(min-width: 1000px)': {
|
||||
padding: '24px 30px 24px 30px'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={menu}
|
||||
onClick={() => this.setState(s => ({open: !s.open}))}
|
||||
alt="Menu"
|
||||
css={{
|
||||
width: '50px',
|
||||
cursor: 'pointer',
|
||||
margin: '0 20px 0',
|
||||
display: 'block',
|
||||
'@media(min-width: 1000px)': {
|
||||
display: 'none'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
/>
|
||||
<Link to="/">
|
||||
<img src={logo} css={{margin: 0}} alt="html2canvas" />
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>;
|
||||
</div>
|
||||
|
||||
<ul
|
||||
css={{
|
||||
display: this.state.open ? 'block' : 'none',
|
||||
listStyle: 'none',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
'@media(min-width: 1000px)': {
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{links.map(({href, text}, i) =>
|
||||
<li style={{padding: 0, margin: 0}} key={i}>
|
||||
<Link
|
||||
to={href}
|
||||
css={lineLinkStyle}
|
||||
activeStyle={{
|
||||
backgroundColor: '#7cb342',
|
||||
color: '#fff'
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
4
www/src/images/ic_menu_black_24px.svg
Normal file
4
www/src/images/ic_menu_black_24px.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg fill="#ffffff" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 210 B |
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import Layout from '../components/layout';
|
||||
|
||||
const NotFoundPage = () =>
|
||||
<div>
|
||||
<Layout>
|
||||
<h1>NOT FOUND</h1>
|
||||
<p>You just hit a route that doesn't exist... the sadness.</p>
|
||||
</div>;
|
||||
</Layout>;
|
||||
|
||||
export default NotFoundPage;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Link from 'gatsby-link';
|
||||
import logo from '../images/logo_icon.svg';
|
||||
import Layout from '../components/layout';
|
||||
import Footer from '../components/footer';
|
||||
import Carbon from '../components/carbon';
|
||||
|
||||
@ -23,6 +24,7 @@ const linkStyle = {
|
||||
|
||||
export default ({data}) => {
|
||||
return (
|
||||
<Layout>
|
||||
<div
|
||||
css={{
|
||||
background: '#558b2f',
|
||||
@ -48,15 +50,31 @@ export default ({data}) => {
|
||||
}}
|
||||
>
|
||||
<div css={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
|
||||
<img src={logo} />
|
||||
<img src={logo} alt="html2canvas" />
|
||||
<h1>html2canvas</h1>
|
||||
</div>
|
||||
<h4 css={{color: 'rgba(255, 255, 255, 0.6)', fontWeight: 300}}>
|
||||
Screenshots with JavaScript
|
||||
</h4>
|
||||
|
||||
<div css={{display: 'flex', justifyContent: 'center'}}>
|
||||
<div>
|
||||
<div
|
||||
css={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
'@media(min-width: 1000px)': {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
display: 'none',
|
||||
'@media(min-width: 1000px)': {
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<h4>HTML</h4>
|
||||
<div
|
||||
css={{marginRight: '5px'}}
|
||||
@ -70,7 +88,14 @@ export default ({data}) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
css={{
|
||||
display: 'none',
|
||||
'@media(min-width: 1000px)': {
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<h4>JavaScript</h4>
|
||||
<div
|
||||
css={{marginLeft: '5px'}}
|
||||
@ -88,7 +113,7 @@ export default ({data}) => {
|
||||
|
||||
<div css={{margin: '20px'}}>
|
||||
<a
|
||||
href="#"
|
||||
href="/documentation"
|
||||
css={linkStyle}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
@ -101,14 +126,25 @@ export default ({data}) => {
|
||||
Documentation
|
||||
</Link>
|
||||
</div>
|
||||
<div css={{display: 'flex'}}>
|
||||
<div
|
||||
css={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
'@media(min-width: 1000px)': {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
flex: 1,
|
||||
backgroundColor: '#558b2f',
|
||||
padding: '10px 20px',
|
||||
borderRadius: '10px',
|
||||
marginRight: '5px'
|
||||
marginBottom: '10px',
|
||||
'@media(min-width: 1000px)': {
|
||||
marginRight: '5px'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Carbon />
|
||||
@ -116,12 +152,16 @@ export default ({data}) => {
|
||||
<div
|
||||
css={{
|
||||
flex: 1,
|
||||
marginLeft: '5px',
|
||||
|
||||
backgroundColor: '#558b2f',
|
||||
padding: '10px 20px',
|
||||
borderRadius: '10px',
|
||||
textAlign: 'left',
|
||||
marginRight: '5px'
|
||||
marginBottom: '10px',
|
||||
'@media(min-width: 1000px)': {
|
||||
marginLeft: '5px',
|
||||
marginRight: '5px'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<h6>Install NPM</h6>
|
||||
@ -143,16 +183,20 @@ export default ({data}) => {
|
||||
<div
|
||||
css={{
|
||||
flex: 1,
|
||||
marginLeft: '5px',
|
||||
backgroundColor: '#558b2f',
|
||||
padding: '10px 20px',
|
||||
borderRadius: '10px',
|
||||
textAlign: 'left'
|
||||
textAlign: 'left',
|
||||
marginBottom: '10px',
|
||||
'@media(min-width: 1000px)': {
|
||||
marginLeft: '5px'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<h5>Connect</h5>
|
||||
<div css={{height: '35px'}}>
|
||||
<iframe
|
||||
title="Github"
|
||||
src="https://ghbtns.com/github-btn.html?user=niklasvh&repo=html2canvas&type=star&count=true&size=large"
|
||||
frameBorder="0"
|
||||
scrolling="0"
|
||||
@ -176,6 +220,7 @@ export default ({data}) => {
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,7 @@ import back from '../images/ic_arrow_back_black_24px.svg';
|
||||
import next from '../images/ic_arrow_forward_black_24px.svg';
|
||||
import Carbon from '../components/carbon';
|
||||
import Footer from '../components/footer';
|
||||
import Layout from '../components/layout';
|
||||
import Helmet from 'react-helmet';
|
||||
import Navigation from '../components/navigation';
|
||||
|
||||
@ -11,7 +12,7 @@ export default ({data}) => {
|
||||
const post = data.markdownRemark;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Layout>
|
||||
<Helmet
|
||||
title={`${post.frontmatter.title} - html2canvas`}
|
||||
meta={[{name: 'description', content: post.frontmatter.description}]}
|
||||
@ -19,7 +20,9 @@ export default ({data}) => {
|
||||
<Navigation />
|
||||
<div
|
||||
css={{
|
||||
marginLeft: '300px',
|
||||
'@media(min-width: 1000px)': {
|
||||
marginLeft: '300px'
|
||||
},
|
||||
display: 'flex',
|
||||
minHeight: '100vh',
|
||||
flexDirection: 'column'
|
||||
@ -37,10 +40,23 @@ export default ({data}) => {
|
||||
<div css={{maxWidth: '960px'}}>
|
||||
<div css={{width: '85%', margin: '0 auto', display: 'flex'}}>
|
||||
<div css={{flex: 1}}>
|
||||
<h1 css={{padding: '20px 0'}}>
|
||||
<h1
|
||||
css={{
|
||||
padding: '20px 0',
|
||||
fontSize: '2.8rem',
|
||||
'@media(min-width: 1000px)': {fontSize: '4.2rem'}
|
||||
}}
|
||||
>
|
||||
{post.frontmatter.title}
|
||||
</h1>
|
||||
<h4 css={{fontWeight: 300, color: 'rgba(255, 255, 255, 0.6)'}}>
|
||||
<h4
|
||||
css={{
|
||||
fontWeight: 300,
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
fontSize: '1.8rem',
|
||||
'@media(min-width: 1000px)': {fontSize: '2.28rem'}
|
||||
}}
|
||||
>
|
||||
{post.frontmatter.description}
|
||||
</h4>
|
||||
</div>
|
||||
@ -88,7 +104,7 @@ export default ({data}) => {
|
||||
}}
|
||||
>
|
||||
<div css={{height: '24px'}}>
|
||||
<img src={back} />
|
||||
<img src={back} alt="" />
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
@ -147,7 +163,7 @@ export default ({data}) => {
|
||||
</div>
|
||||
|
||||
<div css={{height: '24px'}}>
|
||||
<img src={next} />
|
||||
<img src={next} alt="" />
|
||||
</div>
|
||||
</Link>
|
||||
: null}
|
||||
@ -156,7 +172,7 @@ export default ({data}) => {
|
||||
: null}
|
||||
<Footer css={{marginLeft: '300px'}} />
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import Typography from 'typography';
|
||||
import githubTheme from 'typography-theme-github';
|
||||
|
||||
const theme = {
|
||||
googleFonts: [
|
||||
|
1
www/static/CNAME
Normal file
1
www/static/CNAME
Normal file
@ -0,0 +1 @@
|
||||
html2canvas.hertzen.com
|
Reference in New Issue
Block a user