mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Compare commits
66 Commits
v1.0.0-alp
...
v1.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
6d0cd2d226 | |||
78c3c7fc71 |
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
|
||||
fqdn: html2canvas.hertzen.com
|
||||
github_token:
|
||||
secure: "PowO/Jat660k3gHcjgI6DlJz15RM7pLUu11UPsLCtYJ8ZwodppE6Keg0DfVkSFSIZttZor+UssDwP/WOEqfZNLqmXbcj3Gec4xolohet/GOe0KJKKuF/HgggbcxumopxMX6sMVePlMBpkLpHh7tgEAEHBWTlzC1c1a7Xa48fZ7k="
|
||||
on:
|
||||
branch: master
|
||||
repo: niklasvh/html2canvas
|
91
CHANGELOG.md
91
CHANGELOG.md
@ -1,35 +1,90 @@
|
||||
### Changelog ###
|
||||
# Change Log
|
||||
|
||||
#### v1.0.0-alpha5 - 21.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.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) (2019-04-07)
|
||||
* 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-alpha4 - 12.12.2017 ####
|
||||
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
|
||||
@ -39,18 +94,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
|
||||
@ -58,11 +113,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.
|
||||
@ -73,14 +128,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
|
||||
@ -90,7 +145,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>)
|
||||
@ -98,7 +153,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>)
|
||||
@ -106,7 +161,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 ####
|
||||
|
||||
|
313
azure-pipelines.yml
Normal file
313
azure-pipelines.yml
Normal file
@ -0,0 +1,313 @@
|
||||
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_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"
|
||||
@ -17,7 +17,9 @@ These are all of the available configuration options.
|
||||
| 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.
|
||||
|
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)
|
||||
|
||||
|
@ -1,89 +1,101 @@
|
||||
// Karma configuration
|
||||
// Generated on Sat Aug 05 2017 23:42:26 GMT+0800 (Malay Peninsula Standard Time)
|
||||
|
||||
const path = require('path');
|
||||
const port = 9876;
|
||||
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'
|
||||
},
|
||||
sl_ie9: {
|
||||
const launchers = {
|
||||
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'];
|
||||
|
||||
config.set({
|
||||
|
||||
@ -93,17 +105,22 @@ 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',
|
||||
{ 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]
|
||||
}
|
||||
],
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
@ -119,7 +136,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) => {
|
||||
|
13737
package-lock.json
generated
13737
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@ -3,7 +3,9 @@
|
||||
"name": "html2canvas",
|
||||
"description": "Screenshots with JavaScript",
|
||||
"main": "dist/npm/index.js",
|
||||
"version": "1.0.0-alpha.5",
|
||||
"module": "dist/html2canvas.js",
|
||||
"browser": "dist/html2canvas.js",
|
||||
"version": "1.0.0-rc.0",
|
||||
"author": {
|
||||
"name": "Niklas von Hertzen",
|
||||
"email": "niklasvh@gmail.com",
|
||||
@ -20,52 +22,58 @@
|
||||
"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",
|
||||
"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",
|
||||
"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 +85,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;
|
||||
|
164
src/Clone.js
164
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);
|
||||
}
|
||||
@ -201,7 +213,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(
|
||||
@ -217,6 +234,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);
|
||||
}
|
||||
|
||||
@ -227,6 +265,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;
|
||||
@ -236,22 +279,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) {
|
||||
@ -362,9 +424,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 ||
|
||||
@ -375,20 +438,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
|
||||
@ -403,13 +475,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';
|
||||
@ -563,14 +628,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`
|
||||
@ -579,7 +651,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(
|
||||
@ -591,3 +663,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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
133
src/ListItem.js
133
src/ListItem.js
@ -92,7 +92,7 @@ export const inlineListItemElement = (
|
||||
}
|
||||
} else if (typeof container.listIndex === 'number') {
|
||||
text = node.ownerDocument.createTextNode(
|
||||
createCounterText(container.listIndex, listStyle.listStyleType)
|
||||
createCounterText(container.listIndex, listStyle.listStyleType, true)
|
||||
);
|
||||
wrapper.appendChild(text);
|
||||
wrapper.style.top = `${container.bounds.top - MARGIN_TOP}px`;
|
||||
@ -363,10 +363,10 @@ const createAdditiveCounter = (
|
||||
max: number,
|
||||
symbols,
|
||||
fallback: ListStyleType,
|
||||
suffix: string = '. '
|
||||
suffix: string
|
||||
) => {
|
||||
if (value < min || value > max) {
|
||||
return createCounterText(value, fallback);
|
||||
return createCounterText(value, fallback, suffix.length > 0);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -404,7 +404,7 @@ const createCounterStyleFromRange = (
|
||||
codePointRangeStart: number,
|
||||
codePointRangeEnd: number,
|
||||
isNumeric: boolean,
|
||||
suffix: string = '. '
|
||||
suffix: string
|
||||
): string => {
|
||||
const codePointRangeLength = codePointRangeEnd - codePointRangeStart + 1;
|
||||
|
||||
@ -451,7 +451,7 @@ const createCJKCounter = (
|
||||
flags: number
|
||||
): string => {
|
||||
if (value < -9999 || value > 9999) {
|
||||
return createCounterText(value, LIST_STYLE_TYPE.CJK_DECIMAL);
|
||||
return createCounterText(value, LIST_STYLE_TYPE.CJK_DECIMAL, suffix.length > 0);
|
||||
}
|
||||
let tmp = Math.abs(value);
|
||||
let string = suffix;
|
||||
@ -488,9 +488,16 @@ const createCJKCounter = (
|
||||
const CHINESE_INFORMAL_MULTIPLIERS = '十百千萬';
|
||||
const CHINESE_FORMAL_MULTIPLIERS = '拾佰仟萬';
|
||||
const JAPANESE_NEGATIVE = 'マイナス';
|
||||
const KOREAN_NEGATIVE = '마이너스 ';
|
||||
const KOREAN_NEGATIVE = '마이너스';
|
||||
|
||||
const createCounterText = (value: number, type: ListStyleType): string => {
|
||||
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 '•';
|
||||
@ -499,48 +506,64 @@ const createCounterText = (value: number, type: ListStyleType): string => {
|
||||
case LIST_STYLE_TYPE.SQUARE:
|
||||
return '◾';
|
||||
case LIST_STYLE_TYPE.DECIMAL_LEADING_ZERO:
|
||||
const string = createCounterStyleFromRange(value, 48, 57, true);
|
||||
const string = createCounterStyleFromRange(value, 48, 57, true, defaultSuffix);
|
||||
return string.length < 4 ? `0${string}` : string;
|
||||
case LIST_STYLE_TYPE.CJK_DECIMAL:
|
||||
return createCounterStyleFromSymbols(value, '〇一二三四五六七八九', '、');
|
||||
return createCounterStyleFromSymbols(value, '〇一二三四五六七八九', cjkSuffix);
|
||||
case LIST_STYLE_TYPE.LOWER_ROMAN:
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
3999,
|
||||
ROMAN_UPPER,
|
||||
LIST_STYLE_TYPE.DECIMAL
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
).toLowerCase();
|
||||
case LIST_STYLE_TYPE.UPPER_ROMAN:
|
||||
return createAdditiveCounter(value, 1, 3999, ROMAN_UPPER, LIST_STYLE_TYPE.DECIMAL);
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
3999,
|
||||
ROMAN_UPPER,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.LOWER_GREEK:
|
||||
return createCounterStyleFromRange(value, 945, 969, false);
|
||||
return createCounterStyleFromRange(value, 945, 969, false, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.LOWER_ALPHA:
|
||||
return createCounterStyleFromRange(value, 97, 122, false);
|
||||
return createCounterStyleFromRange(value, 97, 122, false, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.UPPER_ALPHA:
|
||||
return createCounterStyleFromRange(value, 65, 90, false);
|
||||
return createCounterStyleFromRange(value, 65, 90, false, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.ARABIC_INDIC:
|
||||
return createCounterStyleFromRange(value, 1632, 1641, true);
|
||||
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);
|
||||
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
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
).toLowerCase();
|
||||
case LIST_STYLE_TYPE.BENGALI:
|
||||
return createCounterStyleFromRange(value, 2534, 2543, true);
|
||||
return createCounterStyleFromRange(value, 2534, 2543, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.CAMBODIAN:
|
||||
case LIST_STYLE_TYPE.KHMER:
|
||||
return createCounterStyleFromRange(value, 6112, 6121, true);
|
||||
return createCounterStyleFromRange(value, 6112, 6121, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.CJK_EARTHLY_BRANCH:
|
||||
return createCounterStyleFromSymbols(value, '子丑寅卯辰巳午未申酉戌亥', '、');
|
||||
return createCounterStyleFromSymbols(value, '子丑寅卯辰巳午未申酉戌亥', cjkSuffix);
|
||||
case LIST_STYLE_TYPE.CJK_HEAVENLY_STEM:
|
||||
return createCounterStyleFromSymbols(value, '甲乙丙丁戊己庚辛壬癸', '、');
|
||||
return createCounterStyleFromSymbols(value, '甲乙丙丁戊己庚辛壬癸', cjkSuffix);
|
||||
case LIST_STYLE_TYPE.CJK_IDEOGRAPHIC:
|
||||
case LIST_STYLE_TYPE.TRAD_CHINESE_INFORMAL:
|
||||
return createCJKCounter(
|
||||
@ -548,7 +571,7 @@ const createCounterText = (value: number, type: ListStyleType): string => {
|
||||
'零一二三四五六七八九',
|
||||
CHINESE_INFORMAL_MULTIPLIERS,
|
||||
'負',
|
||||
'、',
|
||||
cjkSuffix,
|
||||
CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.TRAD_CHINESE_FORMAL:
|
||||
@ -557,7 +580,7 @@ const createCounterText = (value: number, type: ListStyleType): string => {
|
||||
'零壹貳參肆伍陸柒捌玖',
|
||||
CHINESE_FORMAL_MULTIPLIERS,
|
||||
'負',
|
||||
'、',
|
||||
cjkSuffix,
|
||||
CJK_ZEROS |
|
||||
CJK_TEN_COEFFICIENTS |
|
||||
CJK_TEN_HIGH_COEFFICIENTS |
|
||||
@ -569,7 +592,7 @@ const createCounterText = (value: number, type: ListStyleType): string => {
|
||||
'零一二三四五六七八九',
|
||||
CHINESE_INFORMAL_MULTIPLIERS,
|
||||
'负',
|
||||
'、',
|
||||
cjkSuffix,
|
||||
CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.SIMP_CHINESE_FORMAL:
|
||||
@ -578,21 +601,21 @@ const createCounterText = (value: number, type: ListStyleType): string => {
|
||||
'零壹贰叁肆伍陆柒捌玖',
|
||||
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, '、', 0);
|
||||
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:
|
||||
@ -601,30 +624,44 @@ const createCounterText = (value: number, type: ListStyleType): string => {
|
||||
'영일이삼사오육칠팔구',
|
||||
'십백천만',
|
||||
KOREAN_NEGATIVE,
|
||||
', ',
|
||||
koreanSuffix,
|
||||
CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS
|
||||
);
|
||||
case LIST_STYLE_TYPE.KOREAN_HANJA_INFORMAL:
|
||||
return createCJKCounter(value, '零一二三四五六七八九', '十百千萬', KOREAN_NEGATIVE, ', ', 0);
|
||||
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);
|
||||
return createCounterStyleFromRange(value, 0x966, 0x96f, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.GEORGIAN:
|
||||
return createAdditiveCounter(value, 1, 19999, GEORGIAN, LIST_STYLE_TYPE.DECIMAL);
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
19999,
|
||||
GEORGIAN,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.GUJARATI:
|
||||
return createCounterStyleFromRange(value, 0xae6, 0xaef, true);
|
||||
return createCounterStyleFromRange(value, 0xae6, 0xaef, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.GURMUKHI:
|
||||
return createCounterStyleFromRange(value, 0xa66, 0xa6f, true);
|
||||
return createCounterStyleFromRange(value, 0xa66, 0xa6f, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.HEBREW:
|
||||
return createAdditiveCounter(value, 1, 10999, HEBREW, LIST_STYLE_TYPE.DECIMAL);
|
||||
return createAdditiveCounter(
|
||||
value,
|
||||
1,
|
||||
10999,
|
||||
HEBREW,
|
||||
LIST_STYLE_TYPE.DECIMAL,
|
||||
defaultSuffix
|
||||
);
|
||||
case LIST_STYLE_TYPE.HIRAGANA:
|
||||
return createCounterStyleFromSymbols(
|
||||
value,
|
||||
@ -636,39 +673,39 @@ const createCounterText = (value: number, type: ListStyleType): string => {
|
||||
'いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす'
|
||||
);
|
||||
case LIST_STYLE_TYPE.KANNADA:
|
||||
return createCounterStyleFromRange(value, 0xce6, 0xcef, true);
|
||||
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);
|
||||
return createCounterStyleFromRange(value, 0xed0, 0xed9, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.MONGOLIAN:
|
||||
return createCounterStyleFromRange(value, 0x1810, 0x1819, true);
|
||||
return createCounterStyleFromRange(value, 0x1810, 0x1819, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.MYANMAR:
|
||||
return createCounterStyleFromRange(value, 0x1040, 0x1049, true);
|
||||
return createCounterStyleFromRange(value, 0x1040, 0x1049, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.ORIYA:
|
||||
return createCounterStyleFromRange(value, 0xb66, 0xb6f, true);
|
||||
return createCounterStyleFromRange(value, 0xb66, 0xb6f, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.PERSIAN:
|
||||
return createCounterStyleFromRange(value, 0x6f0, 0x6f9, true);
|
||||
return createCounterStyleFromRange(value, 0x6f0, 0x6f9, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.TAMIL:
|
||||
return createCounterStyleFromRange(value, 0xbe6, 0xbef, true);
|
||||
return createCounterStyleFromRange(value, 0xbe6, 0xbef, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.TELUGU:
|
||||
return createCounterStyleFromRange(value, 0xc66, 0xc6f, true);
|
||||
return createCounterStyleFromRange(value, 0xc66, 0xc6f, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.THAI:
|
||||
return createCounterStyleFromRange(value, 0xe50, 0xe59, true);
|
||||
return createCounterStyleFromRange(value, 0xe50, 0xe59, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.TIBETAN:
|
||||
return createCounterStyleFromRange(value, 0xf20, 0xf29, true);
|
||||
return createCounterStyleFromRange(value, 0xf20, 0xf29, true, defaultSuffix);
|
||||
case LIST_STYLE_TYPE.DECIMAL:
|
||||
default:
|
||||
return createCounterStyleFromRange(value, 48, 57, true);
|
||||
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,9 +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';
|
||||
@ -17,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';
|
||||
@ -34,9 +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';
|
||||
@ -44,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';
|
||||
@ -65,10 +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,
|
||||
@ -76,6 +84,7 @@ type StyleDeclaration = {
|
||||
textTransform: TextTransform,
|
||||
transform: Transform,
|
||||
visibility: Visibility,
|
||||
wordBreak: WordBreak,
|
||||
zIndex: zIndex
|
||||
};
|
||||
|
||||
@ -134,12 +143,16 @@ export default class NodeContainer {
|
||||
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),
|
||||
@ -147,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')
|
||||
};
|
||||
|
||||
|
@ -7,6 +7,7 @@ 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,
|
||||
@ -74,7 +75,7 @@ const parseNodeTree = (
|
||||
inlineSelectElement(childNode, container);
|
||||
} else if (
|
||||
container.style.listStyle &&
|
||||
container.style.listStyle.listStyleType !== 'none'
|
||||
container.style.listStyle.listStyleType !== LIST_STYLE_TYPE.NONE
|
||||
) {
|
||||
inlineListItemElement(childNode, container, resourceLoader);
|
||||
}
|
||||
|
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)) {
|
||||
@ -215,6 +219,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
|
||||
);
|
||||
};
|
||||
|
@ -1,32 +1,27 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const fromCodePoint = (...codePoints: Array<number>): string => {
|
||||
if (String.fromCodePoint) {
|
||||
return String.fromCodePoint(...codePoints);
|
||||
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());
|
||||
}
|
||||
|
||||
const length = codePoints.length;
|
||||
if (!length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const codeUnits = [];
|
||||
|
||||
let index = -1;
|
||||
let result = '';
|
||||
while (++index < length) {
|
||||
let codePoint = codePoints[index];
|
||||
if (codePoint <= 0xffff) {
|
||||
codeUnits.push(codePoint);
|
||||
} else {
|
||||
codePoint -= 0x10000;
|
||||
codeUnits.push((codePoint >> 10) + 0xd800, codePoint % 0x400 + 0xdc00);
|
||||
}
|
||||
if (index + 1 === length || codeUnits.length > 0x4000) {
|
||||
result += String.fromCharCode(...codeUnits);
|
||||
codeUnits.length = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
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,
|
||||
@ -102,16 +121,33 @@ export const renderElement = (
|
||||
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)) {
|
||||
|
26
src/index.js
26
src/index.js
@ -6,7 +6,6 @@ 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,
|
||||
@ -14,8 +13,10 @@ export type Options = {
|
||||
backgroundColor: string,
|
||||
canvas: ?HTMLCanvasElement,
|
||||
foreignObjectRendering: boolean,
|
||||
ignoreElements?: HTMLElement => boolean,
|
||||
imageTimeout: number,
|
||||
logging: boolean,
|
||||
onclone?: Document => void,
|
||||
proxy: ?string,
|
||||
removeContainer: ?boolean,
|
||||
scale: number,
|
||||
@ -32,14 +33,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(
|
||||
@ -53,15 +49,6 @@ 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,
|
||||
@ -74,10 +61,6 @@ const html2canvas = (element: HTMLElement, conf: ?Options): Promise<*> => {
|
||||
scale: defaultView.devicePixelRatio || 1,
|
||||
target: new CanvasRenderer(config.canvas),
|
||||
useCORS: false,
|
||||
x: left,
|
||||
y: top,
|
||||
width: Math.ceil(width),
|
||||
height: Math.ceil(height),
|
||||
windowWidth: defaultView.innerWidth,
|
||||
windowHeight: defaultView.innerHeight,
|
||||
scrollX: defaultView.pageXOffset,
|
||||
@ -96,5 +79,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;
|
||||
}
|
||||
};
|
@ -75,7 +75,7 @@ export const LIST_STYLE_TYPE = {
|
||||
export type ListStylePosition = $Values<typeof LIST_STYLE_POSITION>;
|
||||
export type ListStyleType = $Values<typeof LIST_STYLE_TYPE>;
|
||||
|
||||
const parseListStyleType = (type: string): ListStyleType => {
|
||||
export const parseListStyleType = (type: string): ListStyleType => {
|
||||
switch (type) {
|
||||
case 'disc':
|
||||
return LIST_STYLE_TYPE.DISC;
|
||||
|
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);
|
||||
}
|
||||
|
||||
@ -250,7 +254,7 @@ export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
|
||||
const {baseline} = this.options.fontMetrics.getMetrics(font);
|
||||
this.rectangle(
|
||||
text.bounds.left,
|
||||
Math.round(text.bounds.top + text.bounds.height - baseline),
|
||||
Math.round(text.bounds.top + baseline),
|
||||
text.bounds.width,
|
||||
1,
|
||||
textDecorationColor
|
||||
|
@ -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: '!'}
|
||||
]);
|
||||
});
|
||||
});
|
@ -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>
|
||||
|
@ -0,0 +1,2 @@
|
||||
[Safari]/tests/reftests/acid2.html
|
||||
[Safari]/tests/reftests/background/encoded.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>
|
@ -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>
|
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>
|
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>
|
@ -21,7 +21,7 @@ var REFTEST = window.location.search.indexOf('reftest') !== -1;
|
||||
.concat([
|
||||
'/node_modules/jquery/dist/jquery.min',
|
||||
'/dist/html2canvas',
|
||||
'/dist/RefTestRenderer'
|
||||
'/build/RefTestRenderer'
|
||||
])
|
||||
.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.4",
|
||||
"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>
|
||||
@ -123,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