Showing preview only (335K chars total). Download the full file or copy to clipboard to get everything.
Repository: bubkoo/html-to-image
Branch: master
Commit: 116ff7a1d43d
Files: 165
Total size: 300.5 KB
Directory structure:
gitextract_eg26j0ob/
├── .editorconfig
├── .eslintrc
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── backup/
│ │ └── stale.yml
│ ├── ci.yml
│ ├── codeql.yml
│ ├── config/
│ │ ├── label-commands.yml
│ │ ├── needs-more-info.yml
│ │ └── pr-label-branch-name.yml
│ ├── label-commands.yml
│ ├── lock.yml
│ ├── needs-more-info.yml
│ ├── potential-duplicates.yml
│ ├── pr-label-branch-name.yml
│ ├── pr-label-patch-size.yml
│ ├── pr-label-status.yml
│ ├── pr-label-title-body.yml
│ ├── release.yml
│ ├── update-authors.yml
│ ├── update-contributors.yml
│ ├── update-license.yml
│ └── welcome.yml
├── .gitignore
├── .husky/
│ ├── .gitignore
│ ├── commit-msg
│ └── pre-commit
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── LICENSE
├── README.md
├── karma.conf.js
├── package.json
├── rollup.config.js
├── src/
│ ├── apply-style.ts
│ ├── clone-node.ts
│ ├── clone-pseudos.ts
│ ├── dataurl.ts
│ ├── embed-images.ts
│ ├── embed-resources.ts
│ ├── embed-webfonts.ts
│ ├── index.ts
│ ├── mimes.ts
│ ├── types.ts
│ └── util.ts
├── test/
│ ├── resources/
│ │ ├── bgcolor/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── bigger/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── border/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── canvas/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── css-bg/
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── custom-element/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── dimensions/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── ext-css/
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── filter/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── fonts/
│ │ │ ├── node.html
│ │ │ ├── style.css
│ │ │ └── web-fonts/
│ │ │ ├── embedded.css
│ │ │ ├── empty.html
│ │ │ ├── remote.css
│ │ │ ├── rules-relative.css
│ │ │ ├── rules-relative.html
│ │ │ ├── rules.css
│ │ │ └── with-query.css
│ │ ├── hash/
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── images/
│ │ │ ├── loading.html
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── input/
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── page.html
│ │ ├── pixeldata/
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── placeholder/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── pseudo/
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── scale/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── scroll/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── select/
│ │ │ ├── first
│ │ │ ├── first-option.html
│ │ │ ├── second
│ │ │ ├── second-option.html
│ │ │ ├── style.css
│ │ │ ├── third
│ │ │ └── third-option.html
│ │ ├── sheet/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ ├── sheet.css
│ │ │ └── style.css
│ │ ├── small/
│ │ │ ├── image
│ │ │ ├── image-jpeg
│ │ │ ├── image-jpeg-low
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── style/
│ │ │ ├── image
│ │ │ ├── image-include-style
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── svg-color/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── svg-image/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── svg-ns/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── svg-rect/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── svg-use-tag/
│ │ │ ├── image
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── text/
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── textarea/
│ │ │ ├── node.html
│ │ │ └── style.css
│ │ ├── video/
│ │ │ ├── flower.webm
│ │ │ ├── image
│ │ │ ├── image-poster
│ │ │ ├── node.html
│ │ │ ├── poster.html
│ │ │ └── style.css
│ │ └── webp/
│ │ ├── node.html
│ │ └── style.css
│ └── spec/
│ ├── basic.spec.ts
│ ├── canvas.sepc.ts
│ ├── embed.spec.ts
│ ├── helper.ts
│ ├── on-image-error-handler.spec.ts
│ ├── options.spec.ts
│ ├── select.sepc.ts
│ ├── setup.ts
│ ├── special.spec.ts
│ ├── svg.spec.ts
│ ├── video.spec.ts
│ └── webfont.spec.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
================================================
FILE: .eslintrc
================================================
{
"extends": "@bubkoo/eslint-config"
}
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
<!--- Provide a general summary of the issue in the Title above. -->
### Expected Behavior
<!--- What should happen. -->
### Current Behavior
<!--- What happens instead. -->
### Possible Solution
<!--- Suggest a fix/reason for the bug. -->
<!--- Any solutions you've considered. -->
### Steps To Reproduce
<!--- Provide a link to a live example, or an unambiguous -->
<!--- set of steps to reproduce this bug. Include code to -->
<!--- reproduce if relevant. -->
1. ...
2. ...
3. ...
<details><summary>Error Message & Stack Trace</summary><p>
```txt
<!-- Provide a log message if relevant -->
```
</p></details>
### Additional Context
<!--- How has this issue affected you? -->
<!--- What are you trying to accomplish? -->
<!--- Provide screenshots if possible. -->
<!--- Providing context helps us come up with a -->
<!--- solution that is most useful in the real world. -->
### Your Environment
<!--- Include as many relevant details about the -->
<!--- environment you experienced the bug in. -->
- html-to-image: [e.g. 0.1.0]
- OS: [e.g. macOS Sierra 10.12.3]
- Browser: [e.g. chrome 78.0.3904.108]
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
assignees: ''
---
<!--- Provide a general summary of the feature in the Title above. -->
### Expected Behavior
<!--- What should happen. -->
### Possible Solution
<!--- Any solutions you've considered. -->
### Additional Context
<!--- How has this issue affected you? -->
<!--- What are you trying to accomplish? -->
<!--- Provide screenshots if possible. -->
<!--- Providing context helps us come up with a -->
<!--- solution that is most useful in the real world. -->
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--- Provide a general summary of your changes in the Title above -->
### Description
<!--- Describe your changes in detail -->
### Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
<!--- GIF or snapshot should be provided if includes UI/interactive modification. -->
<!--- How to fix the problem, and list final API implementation and usage sample if that is an new feature. -->
### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Enhancement (changes that improvement of current feature or performance)
- [ ] Refactoring (changes that neither fixes a bug nor adds a feature)
- [ ] Test Case (changes that add missing tests or correct existing tests)
- [ ] Code style optimization (changes that do not affect the meaning of the code)
- [ ] Docs (changes that only update documentation)
- [ ] Chore (changes that don't modify src or test files)
### Self Check before Merge
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the [**CONTRIBUTING**](https://github.com/bubkoo/html-to-image/blob/master/CONTRIBUTING.md) document.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
================================================
FILE: .github/workflows/backup/stale.yml
================================================
name: 👻 Stale
on:
schedule:
- cron: "0 0 * * *"
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: actions/stale@v3
with:
repo-token: ${{ env.bot_token }}
stale-issue-message: >
Hiya!
This issue has gone quiet. Spooky quiet. 👻
We get a lot of issues, so we currently close issues after 60 days of inactivity. It’s been at least 20 days since the last update here.
If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not-stale" to keep this issue open!
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request.
Thanks for being a part of the Antv community! 💪💯
close-issue-message: >
Hey again!
It’s been 60 days since anything happened on this issue, so our friendly neighborhood robot (that’s me!) is going to close it.
Please keep in mind that I’m only a robot, so if I’ve closed this issue in error, I’m `HUMAN_EMOTION_SORRY`. Please feel free to comment on this issue or create a new one if you need anything else.
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request.
Thanks again for being part of the Antv community! 💪💯
stale-pr-message: >
Hiya!
This PR has gone quiet. Spooky quiet. 👻
We get a lot of PRs, so we currently close PRs after 60 days of inactivity. It’s been at least 20 days since the last update here.
If we missed this PR or if you want to keep it open, please reply here. You can also add the label "not-stale" to keep this PR open!
Thanks for being a part of the Antv community! 💪💯
close-pr-message: >
Hey again!
It’s been 60 days since anything happened on this PR, so our friendly neighborhood robot (that’s me!) is going to close it.
Please keep in mind that I’m only a robot, so if I’ve closed this PR in error, I’m `HUMAN_EMOTION_SORRY`. Please feel free to comment on this PR or create a new one if you need anything else.
Thanks again for being part of the Antv community! 💪💯
days-before-stale: 20
days-before-close: 40
stale-issue-label: 'stale'
exempt-issue-label: 'not-stale,awaiting-approval,work-in-progress'
stale-pr-label: 'stale'
exempt-pr-label: 'not-stale,awaiting-approval,work-in-progress'
================================================
FILE: .github/workflows/ci.yml
================================================
name: 👷 CI
on:
pull_request:
pull_request_target:
push:
branches:
- master
- next
- next-major
- alpha
- beta
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: ⤵️ Checkout
uses: actions/checkout@v3
- name: 🎉 Setup nodejs
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: 🎉 Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 7
run_install: false
- name: 🌱 Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: 🚸 Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: 🚧 Install dependencies
run: pnpm install --no-frozen-lockfile --ignore-scripts
- name: 📦 Build
run: pnpm run build
- name: ✅ Test
run: pnpm run test
- name: 🔑 Generate Token
uses: wow-actions/use-app-token@v2
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
- name: 💡 Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ env.BOT_TOKEN }}
path-to-lcov: ./test/coverage/lcov.info
- name: 💡 Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./test/coverage/lcov.info
================================================
FILE: .github/workflows/codeql.yml
================================================
name: ⛵️ CodeQL
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "21 15 * * 5"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ javascript ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
================================================
FILE: .github/workflows/config/label-commands.yml
================================================
heated:
lock: true
lockReason: too heated
comment: The thread has been temporarily locked.
-heated:
unlock: true
issues:
feature:
close: true
comment: >
:wave: @{{ author }}, please use our idea board to request new features.
-wontfix:
open: true
needs-more-info:
close: true
comment: >
@{{ author }}
In order to communicate effectively, we have a certain format requirement for the issue, your issue is automatically closed because there is no recurring step or reproducible warehouse, and will be REOPEN after the offer.
-needs-more-info:
open: true
================================================
FILE: .github/workflows/config/needs-more-info.yml
================================================
# common config
# -------------
# chenck issue and PR template
checkTemplate: true
# minimum title length required
miniTitleLength: 8
# add label to trigger a label command, see ./label-commands.yml
labelToAdd: needs-more-info
# reactions to add
reactions:
- '-1'
- confused
# config for issues
# -----------------
issue:
badTitles:
- update
- updates
- test
- issue
- debug
- demo
badTitleComment: ''
badBodyComment: ''
# config for PRs
# --------------
pullRequest:
badTitles:
- update
- updates
- test
badTitleComment: >
@{{ author }} Please provide us with more info about this pull request title.
badBodyComment: >
@{{ author }} Please provide us with more info about this pull request.
================================================
FILE: .github/workflows/config/pr-label-branch-name.yml
================================================
# https://github.com/marketplace/actions/PR-labeler
PR(fix): ['fix/*', 'bug/*']
PR(chore): chore/*
PR(test): ['testing/*', 'test/*']
PR(feature): ['feature/*', 'feat/*']
PR(breaking): ['breaking/*', 'break/*']
PR(internal): ['internal/*', 'inter/*']
PR(documentation): ['documentation/*', 'document/*', 'doc/*']
PR(enhancement): ['enhancement/*', 'enhance/*']
PR(dependency): ['dependency/*', 'dep/*']
PR(refactor): ['refactoring/*', 'refactor/*']
================================================
FILE: .github/workflows/label-commands.yml
================================================
name: 👾 Label Commands
on:
pull_request_target:
types: [labeled, unlabeled]
issues:
types: [labeled, unlabeled]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: bubkoo/label-commands@v1
with:
GITHUB_TOKEN: ${{ env.bot_token }}
CONFIG_FILE: .github/workflows/config/label-commands.yml
================================================
FILE: .github/workflows/lock.yml
================================================
name: ⛔️ Lock Threads
on:
schedule:
- cron: 0 0 * * *
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: dessant/lock-threads@v2
with:
github-token: ${{ env.bot_token }}
issue-lock-inactive-days: 365
issue-lock-comment: >
This thread has been automatically locked because it has not had recent
activity. Please open a new issue for related bugs and link to relevant
comments in this thread.
process-only: issues
================================================
FILE: .github/workflows/needs-more-info.yml
================================================
name: 🚨 Needs More Info
on:
pull_request_target:
types: [opened]
issues:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: bubkoo/needs-more-info@v1
with:
GITHUB_TOKEN: ${{ env.bot_token }}
CONFIG_FILE: .github/workflows/config/needs-more-info.yml
================================================
FILE: .github/workflows/potential-duplicates.yml
================================================
name: 🆖 Potential Duplicates
on:
issues:
types: [opened, edited]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: bubkoo/potential-duplicates@v1
with:
GITHUB_TOKEN: ${{ env.bot_token }}
================================================
FILE: .github/workflows/pr-label-branch-name.yml
================================================
name: 🏷️ Label(Branch Name)
on:
pull_request_target:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: TimonVS/pr-labeler-action@v3
with:
configuration-path: .github/workflows/config/pr-label-branch-name.yml
env:
GITHUB_TOKEN: ${{ env.bot_token }}
================================================
FILE: .github/workflows/pr-label-patch-size.yml
================================================
name: 🏷️ Label(Patch Size)
on: pull_request_target
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: pascalgn/size-label-action@v0.1.1
env:
GITHUB_TOKEN: "${{ env.bot_token }}"
IGNORED: "!.gitignore\npnpm-lock.yaml"
================================================
FILE: .github/workflows/pr-label-status.yml
================================================
name: 🏷️ Label(Status)
on:
pull_request_target:
types: [opened, closed, edited, reopened, synchronize, ready_for_review]
pull_request_review:
types: [submitted, dismissed]
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
env_name: bot_token
- uses: bubkoo/pr-triage@v1
with:
GITHUB_TOKEN: ${{ env.bot_token }}
================================================
FILE: .github/workflows/pr-label-title-body.yml
================================================
# Github action for automatically adding label or setting assignee when a new
# Issue or PR is opened. https://github.com/marketplace/actions/issue-labeler
name: 🏷️ Label(Title and Body)
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: Naturalclar/issue-action@v2.0.1
with:
github-token: ${{ env.bot_token }}
title-or-body: title
parameters: >
[
{
"keywords": ["bug", "error"],
"labels": ["bug"]
},
{
"keywords": ["help", "guidance"],
"labels": ["help-wanted"]
}
]
================================================
FILE: .github/workflows/release.yml
================================================
name: 🚀 Release
on:
push:
branches:
- master
- next
- next-major
- alpha
- beta
jobs:
run:
runs-on: ubuntu-latest
steps:
- name: ⤵️ Checkout
uses: actions/checkout@v3
with:
persist-credentials: false
- name: 🎉 Setup nodejs
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: 🎉 Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 7
run_install: false
- name: 🌱 Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: 🚸 Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: 🚧 Install dependencies
run: pnpm install --no-frozen-lockfile --ignore-scripts
- name: 📦 Build
run: pnpm run build
- name: ✅ Test
run: pnpm run test
- name: 🔑 Generate Token
uses: wow-actions/use-app-token@v2
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
- name: 📦 Semantic Release
uses: cycjimmy/semantic-release-action@v3
id: semantic
with:
extends: '@bubkoo/semantic-release-config'
extra_plugins: |
@semantic-release/changelog
@semantic-release/git
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ env.BOT_TOKEN }}
GIT_AUTHOR_NAME: ${{ env.BOT_NAME }}[bot]
GIT_AUTHOR_EMAIL: ${{ env.BOT_NAME }}[bot]@users.noreply.github.com
GIT_COMMITTER_NAME: ${{ env.BOT_NAME }}[bot]
GIT_COMMITTER_EMAIL: ${{ env.BOT_NAME }}[bot]@users.noreply.github.com
- name: 🎉 Setup Node.js with GitHub Package Registry
if: steps.semantic.outputs.new_release_published == 'true'
uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://npm.pkg.github.com
scope: bubkoo
- name: 🔀 Publish To GitHub Package Registry
if: steps.semantic.outputs.new_release_published == 'true'
run: |
sed -i 's/\("name"\:[[:space:]]*"\)\(html-to-image"\)/\1@bubkoo\/\2/' package.json
npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/update-authors.yml
================================================
name: 🎗 Update Authors
on:
push:
branches:
- master
- alpha
- beta
jobs:
authors:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
env_name: bot_token
- uses: bubkoo/update-authors@v1
with:
GITHUB_TOKEN: ${{ env.bot_token }}
bots: false
path: CONTRIBUTORS
commit: 'chore: update CONTRIBUTORS [skip ci]'
================================================
FILE: .github/workflows/update-contributors.yml
================================================
name: 🤝 Update Contributors
on:
schedule:
- cron: '0 1 * * *'
push:
branches:
- master
- alpha
- beta
jobs:
contributors:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
env_name: bot_token
- uses: bubkoo/contributors-list@v1
with:
GITHUB_TOKEN: ${{ env.bot_token }}
excludeUsers: semantic-release-bot ImgBotApp
================================================
FILE: .github/workflows/update-license.yml
================================================
name: 🔑 Update License
on:
schedule:
- cron: '0 3 1 1 *' # At 03:00 on day-of-month 1 in January.
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
env_name: bot_token
- uses: FantasticFiasco/action-update-license-year@v2
with:
token: ${{ env.bot_token }}
================================================
FILE: .github/workflows/welcome.yml
================================================
name: 👋 Welcome
on:
pull_request_target:
types: [opened, closed]
issues:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: bubkoo/use-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
variable_name: bot_token
- uses: bubkoo/welcome-action@v1
with:
GITHUB_TOKEN: ${{ env.bot_token }}
FIRST_ISSUE: |
👋 @{{ author }}
Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it.
To help make it easier for us to investigate your issue, please follow the [contributing guidelines](https://github.com/antvis/X6/blob/master/CONTRIBUTING.md).
We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.
FIRST_PR: |
👋 @{{ author }}
💖 Thanks for opening this pull request! 💖
Please follow the [contributing guidelines](https://github.com/antvis/X6/blob/master/CONTRIBUTING.md). And we use [semantic commit messages](https://www.conventionalcommits.org/en/v1.0.0/) to streamline the release process.
Examples of commit messages with semantic prefixes:
- `fix: don't overwrite prevent_default if default wasn't prevented`
- `feat: add graph.scale() method`
- `docs: graph.getShortestPath is now available`
Things that will help get your PR across the finish line:
- Follow the TypeScript coding style.
- Run `npm run lint` locally to catch formatting errors earlier.
- Document any user-facing changes you've made.
- Include tests when adding/changing behavior.
- Include screenshots and animated GIFs whenever possible.
We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can.
FIRST_PR_MERGED: |
👋 @{{ author }} Congrats on merging your first pull request! 🎉🎉🎉
================================================
FILE: .gitignore
================================================
.DS_Store
.idea
.vscode
.nyc_output
.DS_Store
.vscode
npm-debug.log
yarn-error.log
node_modules
dist
es
lib
test/coverage
================================================
FILE: .husky/.gitignore
================================================
_
================================================
FILE: .husky/commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx @commitlint/cli --extends @bubkoo/commitlint-config --edit "$1"
================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
================================================
FILE: .prettierignore
================================================
*.md
*.sh
*.yml
*.svg
*.gif
.DS_Store
.gitignore
.npmignore
.prettierignore
.babelrc
.editorconfig
.eslintrc
.eslintignore
.stylelintrc.json
package.json
tsconfig.json
tslint.json
CNAME
LICENSE
lib/
es/
dist/
coverage/
yarn.lock
yarn-error.log
npm-debug.log
lerna-debug.log
pnpm-lock.yaml
================================================
FILE: .prettierrc
================================================
{
"semi": false,
"singleQuote": true,
"printWidth": 80,
"trailingComma": "all",
"proseWrap": "never",
"overrides": [
{ "files": ".eslintrc", "options": { "parser": "json" } },
{ "files": ".prettierrc", "options": { "parser": "json" } }
]
}
================================================
FILE: CHANGELOG.md
================================================
## [1.11.13](https://github.com/bubkoo/html-to-image/compare/v1.11.12...v1.11.13) (2025-02-14)
### Bug Fixes
* **mask:** add support for -webkit-mask and -webkit-mask-image ([#382](https://github.com/bubkoo/html-to-image/issues/382)) ([5bdfda7](https://github.com/bubkoo/html-to-image/commit/5bdfda75cc41248f4e161ea434bbfc535e72f95e))
## [1.11.12](https://github.com/bubkoo/html-to-image/compare/v1.11.11...v1.11.12) (2025-02-12)
### Bug Fixes
* add possibility to use own handling of onerror which will not en… ([#453](https://github.com/bubkoo/html-to-image/issues/453)) ([04160c3](https://github.com/bubkoo/html-to-image/commit/04160c3e77f55e95082cbd4879847d0afda92883))
* ensure images are totally prcoessed before using them (ios) ([#478](https://github.com/bubkoo/html-to-image/issues/478)) ([51fb98f](https://github.com/bubkoo/html-to-image/commit/51fb98f69b6eef6c391a5a7859711a5f0601f467))
* Fix `fontEmbedCSS ` incorrect sizing ([#422](https://github.com/bubkoo/html-to-image/issues/422)) ([7020162](https://github.com/bubkoo/html-to-image/commit/702016256cc03b5637e75645f3d70131ddd0e45f))
### Performance Improvements
* embed only used fonts ([#476](https://github.com/bubkoo/html-to-image/issues/476)) ([09bee44](https://github.com/bubkoo/html-to-image/commit/09bee442c27bde2af06442417f4269e763e6b6cd))
* svg cloning optimized using deep clone ([#462](https://github.com/bubkoo/html-to-image/issues/462)) ([9aac2fd](https://github.com/bubkoo/html-to-image/commit/9aac2fd11333ca302d10f2c438cd16be07a1429d))
## [1.11.11](https://github.com/bubkoo/html-to-image/compare/v1.11.10...v1.11.11) (2023-02-01)
## [1.11.10](https://github.com/bubkoo/html-to-image/compare/v1.11.9...v1.11.10) (2023-02-01)
### Bug Fixes
* revert the change in the pre-install hook ([ed7db4d](https://github.com/bubkoo/html-to-image/commit/ed7db4d090c600da632c2c7ef9319ed033d9c3e5)), closes [#365](https://github.com/bubkoo/html-to-image/issues/365)
## [1.11.9](https://github.com/bubkoo/html-to-image/compare/v1.11.8...v1.11.9) (2023-01-31)
### Bug Fixes
* use "secrets.GITHUB_TOKEN" to publish to GPR ([2652288](https://github.com/bubkoo/html-to-image/commit/2652288af04f6a4775cf107981f3292b0a231973))
## [1.11.8](https://github.com/bubkoo/html-to-image/compare/v1.11.7...v1.11.8) (2023-01-31)
### Bug Fixes
* specify plugins ([d90ec23](https://github.com/bubkoo/html-to-image/commit/d90ec23daca23cda0d515cd7dda8d80cdf75546b))
## [1.11.7](https://github.com/bubkoo/html-to-image/compare/v1.11.6...v1.11.7) (2023-01-30)
## [1.11.6](https://github.com/bubkoo/html-to-image/compare/v1.11.5...v1.11.6) (2023-01-30)
### Bug Fixes
* clone iframe nodes better ([#352](https://github.com/bubkoo/html-to-image/issues/352)) ([bc6b865](https://github.com/bubkoo/html-to-image/commit/bc6b8652f0504cf5be19ed77f9c88b986e7aaeed))
## [1.11.5](https://github.com/bubkoo/html-to-image/compare/v1.11.4...v1.11.5) (2023-01-30)
### Bug Fixes
* **cloneCSSStyle:** rounded values of d attr fix ([#358](https://github.com/bubkoo/html-to-image/issues/358)) ([6d28bdb](https://github.com/bubkoo/html-to-image/commit/6d28bdb96f15877666b222067bfb082da300f355)), closes [#357](https://github.com/bubkoo/html-to-image/issues/357)
* include source in npm package ([#316](https://github.com/bubkoo/html-to-image/issues/316)) ([b609415](https://github.com/bubkoo/html-to-image/commit/b6094151fb199fad699e74d93a8cef14089dda71))
* switch lazy loading images to eager ([#359](https://github.com/bubkoo/html-to-image/issues/359)) ([f7c311b](https://github.com/bubkoo/html-to-image/commit/f7c311b5285d4ca8383c5fe7c3dfb0c9fbc6f630))
## [1.11.4](https://github.com/bubkoo/html-to-image/compare/v1.11.3...v1.11.4) (2023-01-01)
## [1.11.3](https://github.com/bubkoo/html-to-image/compare/v1.11.2...v1.11.3) (2022-12-16)
## [1.11.2](https://github.com/bubkoo/html-to-image/compare/v1.11.1...v1.11.2) (2022-12-13)
### Bug Fixes
* fallback to `poster` when `currentSrc` of video is null ([5d79666](https://github.com/bubkoo/html-to-image/commit/5d7966691a0dae64de8fb2bf9e56be7d274cef83))
* use frames for video capture & add iframes ([#346](https://github.com/bubkoo/html-to-image/issues/346)) ([e316c61](https://github.com/bubkoo/html-to-image/commit/e316c610364d6a774b736e36e310be79d0085d60))
## [1.11.1](https://github.com/bubkoo/html-to-image/compare/v1.11.0...v1.11.1) (2022-12-05)
### Bug Fixes
* clone svg symbols ([#344](https://github.com/bubkoo/html-to-image/issues/344)) ([aec6fa1](https://github.com/bubkoo/html-to-image/commit/aec6fa1573d0f64be6e2879e54a8e4d7e9e300ac))
# [1.11.0](https://github.com/bubkoo/html-to-image/compare/v1.10.10...v1.11.0) (2022-12-05)
### Features
* support webp format ([#343](https://github.com/bubkoo/html-to-image/issues/343)) ([09d4810](https://github.com/bubkoo/html-to-image/commit/09d4810ce3084e43f039c63efd65ba500451b9df)), closes [#326](https://github.com/bubkoo/html-to-image/issues/326)
## [1.10.10](https://github.com/bubkoo/html-to-image/compare/v1.10.9...v1.10.10) (2022-12-03)
### Bug Fixes
* build script ([db7d435](https://github.com/bubkoo/html-to-image/commit/db7d43507c9419fb84ee126b8c334ffa1655b8b3))
* type errors ([b516783](https://github.com/bubkoo/html-to-image/commit/b516783244e9aa847c89cd3ca3b8114bc6157934))
## [1.10.9](https://github.com/bubkoo/html-to-image/compare/v1.10.8...v1.10.9) (2022-12-01)
## [1.10.8](https://github.com/bubkoo/html-to-image/compare/v1.10.7...v1.10.8) (2022-09-01)
## [1.10.7](https://github.com/bubkoo/html-to-image/compare/v1.10.6...v1.10.7) (2022-09-01)
### Bug Fixes
* handle 404 status of fetch ([ecfdbcc](https://github.com/bubkoo/html-to-image/commit/ecfdbcc189771c3fe212ee2ce6f641495b0d650a))
## [1.10.6](https://github.com/bubkoo/html-to-image/compare/v1.10.5...v1.10.6) (2022-08-26)
### Bug Fixes
* apply skipFonts option ([6b7e923](https://github.com/bubkoo/html-to-image/commit/6b7e923ca6a82dddb409a8ab2cda24c469640014)), closes [#93](https://github.com/bubkoo/html-to-image/issues/93) [#310](https://github.com/bubkoo/html-to-image/issues/310)
## [1.10.5](https://github.com/bubkoo/html-to-image/compare/v1.10.4...v1.10.5) (2022-08-26)
## [1.10.4](https://github.com/bubkoo/html-to-image/compare/v1.10.3...v1.10.4) (2022-08-16)
## [1.10.3](https://github.com/bubkoo/html-to-image/compare/v1.10.2...v1.10.3) (2022-08-16)
## [1.10.2](https://github.com/bubkoo/html-to-image/compare/v1.10.1...v1.10.2) (2022-08-15)
## [1.10.1](https://github.com/bubkoo/html-to-image/compare/v1.10.0...v1.10.1) (2022-08-15)
### Bug Fixes
* node version ([13a6989](https://github.com/bubkoo/html-to-image/commit/13a6989d00440984ea631bb92cb484d3bedbfb02))
# [1.10.0](https://github.com/bubkoo/html-to-image/compare/v1.9.0...v1.10.0) (2022-08-11)
### Bug Fixes
* 🐛 cloneCSSStyle: copy transformOriginProp ([#297](https://github.com/bubkoo/html-to-image/issues/297)) ([76b978a](https://github.com/bubkoo/html-to-image/commit/76b978a943ee11ad78ef09f9b3363377baebcbb3))
* 🐛 font format could be without qoutation ([#217](https://github.com/bubkoo/html-to-image/issues/217)) ([2a96149](https://github.com/bubkoo/html-to-image/commit/2a9614966f636636be133d3e16d8fe93cf26db0d))
* 🐛 set selected attribute on option to draw it ([#280](https://github.com/bubkoo/html-to-image/issues/280)) ([caf97c8](https://github.com/bubkoo/html-to-image/commit/caf97c80a3b6ef6f7205d12ab59ef42c5ab2f071))
* 🐛 text breaks on the last word ([#270](https://github.com/bubkoo/html-to-image/issues/270)) ([062c98a](https://github.com/bubkoo/html-to-image/commit/062c98ab3491fb731d660780b1a0408e1f53549a))
* test specs ([c7a664e](https://github.com/bubkoo/html-to-image/commit/c7a664e8148bfa813391e124e33ba44c60e1cdae))
### Features
* ✨ add 'fetchRequestInit' option ([#210](https://github.com/bubkoo/html-to-image/issues/210)) ([c51da3a](https://github.com/bubkoo/html-to-image/commit/c51da3a5cc7421c530ffb7cbaa7b5009c677c2d8))
* ✨ added includeQueryParams flag ([#260](https://github.com/bubkoo/html-to-image/issues/260)) ([259d71e](https://github.com/bubkoo/html-to-image/commit/259d71e431445ba0c32bc081d9164fa094b4da32))
# [1.9.0](https://github.com/bubkoo/html-to-image/compare/v1.8.5...v1.9.0) (2021-10-09)
### Bug Fixes
* Large Dom sizes failing to be drawn correctly into canvas when exporting to PNG ([#197](https://github.com/bubkoo/html-to-image/issues/197)) ([1ee2e7f](https://github.com/bubkoo/html-to-image/commit/1ee2e7f366ccbaf247caefdcf479f52a2abd22bb))
### Features
* ✨ add svg image with href support ([#198](https://github.com/bubkoo/html-to-image/issues/198)) ([cb6f916](https://github.com/bubkoo/html-to-image/commit/cb6f91692fd0ff06852bf83751e0606df841f429))
## [1.8.5](https://github.com/bubkoo/html-to-image/compare/v1.8.4...v1.8.5) (2021-09-15)
### Bug Fixes
* 🐛 config changelog ([297b17f](https://github.com/bubkoo/html-to-image/commit/297b17f6e213c2278e7655dec3fd2444a3e705bf))
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
bubkoo.wy@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contribution Guide
If you have any comment or advice, please report your [issue](https://github.com/bubkoo/html-to-image/issues),
or make any change as you wish and submit a [PR](https://github.com/bubkoo/html-to-image/pulls).
## Reporting New Issues
- Please specify what kind of issue it is.
- Before you report an issue, please search for related issues. Make sure you are not going to open a duplicate issue.
- Explain your purpose clearly in labels, title, or content.
We will confirm the purpose of the issue, replace more accurate labels for it, identify related milestone, and assign developers working on it.
## Submitting Code
### Pull Request Guide
1. [Fork][fork] and clone the repository
2. Configure and install the dependencies `pnpm`
3. Make sure the tests pass on your machine `pnpm test`, note: these tests also run the TypeScript compiler (`tsc`) to check for type errors, so there's no need to run these commands separately.
4. Create a new branch `git checkout -b my-branch-name` for development. The name of branch should be semantic, avoiding words like 'update' or 'tmp'. We suggest to use `'feature/xxx'`, if the modification is about to implement a new feature.
5. Run the test `pnpm test` after you finish your modification. Add new test cases or change old ones if you feel necessary.
6. Push to your fork and [submit a pull request][pr]
7. Pat your self on the back and wait for your pull request to be reviewed and merged.
No one can guarantee how much will be remembered about certain PR after some time. To make sure we can easily recap what happened previously, please provide the following information in your PR.
1. Need: What function you want to achieve (Generally, please point out which issue is related).
2. Updating Reason: Different with issue. Briefly describe your reason and logic about why you need to make such modification.
3. Related Testing: Briefly describe what part of testing is relevant to your modification.
4. User Tips: Notice for html-to-image users. You can skip this part, if the PR is not about update in API or potential compatibility problem.
### Style Guide
tslint can help to identify styling issues that may exist in your code. Your code is required to pass the test from tslint. Run the test locally by `$ pnpm lint`.
### Commit Message Format
You are encouraged to use [angular commit-message-format](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format) to write commit message. In this way, we could have a more trackable history and an automatically generated changelog.
```xml
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
(1)type
Must be one of the following:
- feat: A new feature
- fix: A bug fix
- docs: Documentation-only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests
- chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
- deps: Updates about dependencies
(2)scope
The scope could be anything specifying place of the commit change.
(3)subject
Use succinct words to describe what did you do in the commit change.
(4)body
Feel free to add more content in the body, if you think subject is not self-explanatory enough, such as what it is the purpose or reasons of you commit.
(5)footer
- **If the commit is a Breaking Change, please note it clearly in this part.**
- related issues, like `Closes #1, Closes #2, #3`
e.g.
```
fix($compile): [BREAKING_CHANGE] couple of unit tests for IE9
Older IEs serialize html uppercased, but IE9 does not...
Would be better to expect case insensitive, unfortunately jasmine does
not allow to user regexps for throw expectations.
Document change on bubkoo/html-to-image#123
Closes #392
BREAKING CHANGE:
Breaks foo.bar api, foo.baz should be used instead
```
Look at [these files](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit) for more details.
## Release
We use semantic versioning in release process based on [semver](https://semver.org/).
### Branch Strategy
`master` branch is the latest stable version.
- Just checkout develop branch from `master`
- All new features will be added into `master` or `next` branch as well as all bug-fix except security issues. In such way, we can motivate developers to update to the latest stable version.
### Release Strategy
In the release of every stable version, there will be a PM who has the following responsibilities in different stages of the release.
#### Preparation
- Set up milestone. Confirm that request is related to milestone.
#### Before Release
- Confirm that performance test is passed and all issues in current Milestone are either closed or can be delayed to later versions.
- Open a new [Release Proposal MR](https://github.com/nodejs/node/pull/4181), and write `History` as [node CHANGELOG](https://github.com/nodejs/node/blob/master/CHANGELOG.md). Don't forget to correct content in documentation which is related to the releasing version.
- Nominate PM for next stable version.
================================================
FILE: CONTRIBUTORS
================================================
Amirata Khodaparast <amiratak88@gmail.com>
Andrei Gheorghiu <andrei.gheorghiu@kmap.io>
AndrewN93 <31307316+AndrewN93@users.noreply.github.com>
Andrey K <43050331+andreynocap@users.noreply.github.com>
Andy Earnshaw <andyearnshaw@gmail.com>
Anonymous <anon@ymo.us>
ArtemTumin <77966804+ArtemTumin@users.noreply.github.com>
Arun Rana <arun0808rana@gmail.com>
Calvin Zheng <Calvin.Zheng@westpharma.com>
Dan Crevier <dancrevier@hotmail.com>
Danang Arbansa <danang.arbansa@gmail.com>
Darren Impey <darren@jaxxtec.com>
Dima <dima.horror@gmail.com>
Dragos Nedelcu <dragos199993@gmail.com>
Dustin Brett <dustinbrett@gmail.com>
GU Yiling <justice360@gmail.com>
Gordon Smith <GordonSmith@users.noreply.github.com>
Guillermo López <lopermo.g@gmail.com>
Hal Li <leadream4@gmail.com>
Jean-Francois Couture <jeanfrancois8512@gmail.com>
Jim Raptis <dimitrisraptis96@gmail.com>
Jobin <425605679@qq.com>
Lin Jie <linjie997@gmail.com>
Marcel Voß <marcel.voss@web.de>
Marcin Pietruszka <marcin@webskill.pl>
Marcus Delang <marcus.nilsson@scrive.com>
Marshal27 <marshalthompson1980@gmail.com>
Martin Trobäck <lekoaf@users.noreply.github.com>
Michal Biros <birosh@gmail.com>
Nick Loveridge <lovenick@users.noreply.github.com>
Perry Huang <31561213+PerryHuan9@users.noreply.github.com>
PrashoonB <prashoonbhattacharjee@gmail.com>
Sahin D <5789670+seahindeniz@users.noreply.github.com>
Shachar <34343793+ShaMan123@users.noreply.github.com>
Sureshtr1998 <82634485+Sureshtr1998@users.noreply.github.com>
Sérgio Ferreira de Mendonça <sergiofm@CIASC.LOCAL>
W.Y <bubkoo@163.com>
W.Y <pengxingjian.pxj@alibaba-inc.com>
Will <11353590+june07@users.noreply.github.com>
Will McKenzie <will.mckenzie@coolblue.co.uk>
Yuhao <liyuhao20001118@gmail.com>
bubkoo <bubkoo.wy@gmail.com>
isergey87 <isergey87@users.noreply.github.com>
jagermesh <jagermesh@gmail.com>
mjr9804 <mjr9804@gmail.com>
pengxingjian.pxj <pengxingjian.pxj@alibaba-inc.com>
ramadis <rramiro.o@hotmail.com>
runkow <84323714+runkow@users.noreply.github.com>
sasithahtl <sasithaasaranga@gmail.com>
sbel-odoo <151751676+sbel-odoo@users.noreply.github.com>
semantic-release-bot <semantic-release-bot@martynus.net>
tavo <gchava92@gmail.com>
tuhm1 <50200070+tuhm1@users.noreply.github.com>
your-bot <bubkoo.wy@gmail.com>
崖 <bubkoo.wy@gmail.com>
崖崖崖 <bubkoo@163.com>
李尔王 <46747508+happy-func@users.noreply.github.com>
问崖 <bubkoo.wy@gmail.com>
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017-2025 W.Y.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<h1 align="center">html-to-image</h1>
<p align="center"><strong>✂️ Generates an image from a DOM node using HTML5 canvas and SVG.</strong></p>
<p align="center">Fork from <a href="https://github.com/tsayen/dom-to-image" rel="nofollow">dom-to-image</a> with more maintainable code and some new features.</p>
<p align="center">
<a href="https://github.com/bubkoo/html-to-image/actions/workflows/ci.yml"><img alt="build" src="https://img.shields.io/github/actions/workflow/status/bubkoo/html-to-image/ci.yml?branch=master&logo=github&style=for-the-badge"></a>
<a href="https://app.codecov.io/gh/bubkoo/html-to-image"><img alt="coverage" src="https://img.shields.io/codecov/c/gh/bubkoo/html-to-image?logo=codecov&style=for-the-badge&token=BWweeU2uNX"></a>
<a href="https://www.npmjs.com/package/html-to-image" rel="nofollow"><img alt="NPM Package" src="https://img.shields.io/npm/v/html-to-image.svg?logo=npm&style=for-the-badge" /></a>
<a href="https://www.npmjs.com/package/html-to-image" rel="nofollow"><img alt="NPM Downloads" src="http://img.shields.io/npm/dm/html-to-image.svg?logo=npm&style=for-the-badge" /></a>
</p>
<p align="center">
<a href="/LICENSE"><img src="https://img.shields.io/github/license/bubkoo/html-to-image?style=for-the-badge" alt="MIT License"></a>
<a href="https://www.typescriptlang.org"><img alt="Language" src="https://img.shields.io/badge/language-TypeScript-blue.svg?style=for-the-badge"></a>
<a href="https://github.com/bubkoo/html-to-image/pulls"><img alt="PRs Welcome" src="https://img.shields.io/badge/PRs-Welcome-brightgreen.svg?style=for-the-badge"></a>
</p>
## Install
```shell
npm install --save html-to-image
```
## Usage
```js
/* ES6 */
import * as htmlToImage from 'html-to-image';
import { toPng, toJpeg, toBlob, toPixelData, toSvg } from 'html-to-image';
/* ES5 */
var htmlToImage = require('html-to-image');
```
All the top level functions accept DOM node and rendering options, and return a promise fulfilled with corresponding dataURL:
- [toPng](#toPng)
- [toSvg](#toSvg)
- [toJpeg](#toJpeg)
- [toBlob](#toBlob)
- [toCanvas](#toCanvas)
- [toPixelData](#toPixelData)
Go with the following examples.
#### toPng
Get a PNG image base64-encoded data URL and display it right away:
```js
const node = document.getElementById('my-node');
htmlToImage
.toPng(node)
.then((dataUrl) => {
const img = new Image();
img.src = dataUrl;
document.body.appendChild(img);
})
.catch((err) => {
console.error('oops, something went wrong!', err);
});
```
Get a PNG image base64-encoded data URL and download it (using [download](https://github.com/rndme/download)):
```js
htmlToImage
.toPng(document.getElementById('my-node'))
.then((dataUrl) => download(dataUrl, 'my-node.png'));
```
#### toSvg
Get an SVG data URL, but filter out all the `<i>` elements:
```js
function filter (node) {
return (node.tagName !== 'i');
}
htmlToImage
.toSvg(document.getElementById('my-node'), { filter: filter })
.then(function (dataUrl) {
/* do something */
});
```
#### toJpeg
Save and download a compressed JPEG image:
```js
htmlToImage
.toJpeg(document.getElementById('my-node'), { quality: 0.95 })
.then(function (dataUrl) {
var link = document.createElement('a');
link.download = 'my-image-name.jpeg';
link.href = dataUrl;
link.click();
});
```
#### toBlob
Get a PNG image blob and download it (using [FileSaver](https://github.com/eligrey/FileSaver.js)):
```js
htmlToImage
.toBlob(document.getElementById('my-node'))
.then(function (blob) {
if (window.saveAs) {
window.saveAs(blob, 'my-node.png');
} else {
FileSaver.saveAs(blob, 'my-node.png');
}
});
```
#### toCanvas
Get a HTMLCanvasElement, and display it right away:
```js
htmlToImage
.toCanvas(document.getElementById('my-node'))
.then(function (canvas) {
document.body.appendChild(canvas);
});
```
#### toPixelData
Get the raw pixel data as a [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) with every 4 array elements representing the RGBA data of a pixel:
```js
var node = document.getElementById('my-node');
htmlToImage
.toPixelData(node)
.then(function (pixels) {
for (var y = 0; y < node.scrollHeight; ++y) {
for (var x = 0; x < node.scrollWidth; ++x) {
pixelAtXYOffset = (4 * y * node.scrollHeight) + (4 * x);
/* pixelAtXY is a Uint8Array[4] containing RGBA values of the pixel at (x, y) in the range 0..255 */
pixelAtXY = pixels.slice(pixelAtXYOffset, pixelAtXYOffset + 4);
}
}
});
```
#### React
```tsx
import React, { useCallback, useRef } from 'react';
import { toPng } from 'html-to-image';
const App: React.FC = () => {
const ref = useRef<HTMLDivElement>(null)
const onButtonClick = useCallback(() => {
if (ref.current === null) {
return
}
toPng(ref.current, { cacheBust: true, })
.then((dataUrl) => {
const link = document.createElement('a')
link.download = 'my-image-name.png'
link.href = dataUrl
link.click()
})
.catch((err) => {
console.log(err)
})
}, [ref])
return (
<>
<div ref={ref}>
{/* DOM nodes you want to convert to PNG */}
</div>
<button onClick={onButtonClick}>Click me</button>
</>
)
}
```
## Options
### filter
```ts
(domNode: HTMLElement) => boolean
```
A function taking DOM node as argument. Should return true if passed node should be included in the output. Excluding node means excluding it's children as well.
You can add filter to every image function. For example,
```ts
const filter = (node: HTMLElement) => {
const exclusionClasses = ['remove-me', 'secret-div'];
return !exclusionClasses.some((classname) => node.classList?.contains(classname));
}
htmlToImage.toJpeg(node, { quality: 0.95, filter: filter});
```
or
```js
htmlToImage.toPng(node, {filter:filter})
```
Not called on the root node.
### backgroundColor
A string value for the background color, any valid CSS color value.
### width, height
Width and height in pixels to be applied to node before rendering.
### canvasWidth, canvasHeight
Allows to scale the canva's size including the elements inside to a given width and height (in pixels).
### style
An object whose properties to be copied to node's style before rendering. You might want to check [this reference](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Properties_Reference) for JavaScript names of CSS properties.
### quality
A number between `0` and `1` indicating image quality (e.g. `0.92` => `92%`) of the JPEG image.
Defaults to `1.0` (`100%`)
### cacheBust
Set to true to append the current time as a query string to URL requests to enable cache busting.
Defaults to `false`
### includeQueryParams
Set false to use all URL as cache key.
If the value has falsy value, it will exclude query params from the provided URL.
Defaults to `false`
### imagePlaceholder
A data URL for a placeholder image that will be used when fetching an image fails.
Defaults to an empty string and will render empty areas for failed images.
### pixelRatio
The pixel ratio of the captured image. Default use the actual pixel ratio of the device. Set `1` to
use as initial-scale `1` for the image.
### preferredFontFormat
The format required for font embedding. This is a useful optimisation when a webfont provider
specifies several different formats for fonts in the CSS, for example:
```css
@font-face {
name: 'proxima-nova';
src: url("...") format("woff2"), url("...") format("woff"), url("...") format("opentype");
}
```
Instead of embedding each format, all formats other than the one specified will be discarded. If
this option is not specified then all formats will be downloaded and embedded.
### fontEmbedCSS
When supplied, the library will skip the process of parsing and embedding webfont URLs in CSS,
instead using this value. This is useful when combined with `getFontEmbedCSS()` to only perform the
embedding process a single time across multiple calls to library functions.
```javascript
const fontEmbedCSS = await htmlToImage.getFontEmbedCSS(element1);
html2Image.toSVG(element1, { fontEmbedCSS });
html2Image.toSVG(element2, { fontEmbedCSS });
```
### skipAutoScale
When supplied, the library will skip the process of scaling extra large doms into the canvas object.
You may experience loss of parts of the image if set to `true` and you are exporting a very large image.
Defaults to `false`
### type
A string indicating the image format. The default type is image/png; that type is also used if the given type isn't supported.
When supplied, the toCanvas function will return a blob matching the given image type and quality.
Defaults to `image/png`
### includeStyleProperties
An array of style property names. Can be used to manually specify which style properties are included when cloning nodes. This can be useful for performance-critical scenarios.
## Browsers
Only standard lib is currently used, but make sure your browser supports:
- [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
- SVG `<foreignObject>` tag
It's tested on latest Chrome, Firefox and Safari (49, 45 and 16 respectively at the time of writing), with Chrome performing significantly better on big DOM trees, possibly due to it's more performant SVG support, and the fact that it supports `CSSStyleDeclaration.cssText` property.
*Internet Explorer is not (and will not be) supported, as it does not support SVG `<foreignObject>` tag.*
## How it works
There might some day exist (or maybe already exists?) a simple and standard way of exporting parts of the HTML to image (and then this script can only serve as an evidence of all the hoops I had to jump through in order to get such obvious thing done) but I haven't found one so far.
This library uses a feature of SVG that allows having arbitrary HTML content inside of the `<foreignObject>` tag. So, in order to render that DOM node for you, following steps are taken:
1. Clone the original DOM node recursively
2. Compute the style for the node and each sub-node and copy it to corresponding clone
- and don't forget to recreate pseudo-elements, as they are not cloned in any way, of course
3. Embed web fonts
- find all the `@font-face` declarations that might represent web fonts
- parse file URLs, download corresponding files
- base64-encode and inline content as dataURLs
- concatenate all the processed CSS rules and put them into one `<style>` element, then attach it to the clone
4. Embed images
- embed image URLs in `<img>` elements
- inline images used in `background` CSS property, in a fashion similar to fonts
5. Serialize the cloned node to XML
6. Wrap XML into the `<foreignObject>` tag, then into the SVG, then make it a data URL
7. Optionally, to get PNG content or raw pixel data as a Uint8Array, create an Image element with the SVG as a source, and render it on an off-screen canvas, that you have also created, then read the content from the canvas
8. Done!
## Things to watch out for
- If the DOM node you want to render includes a `<canvas>` element with something drawn on it, it should be handled fine, unless the canvas is [tainted](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image) - in this case rendering will rather not succeed.
- Rendering will failed on huge DOM due to the dataURI [limit varies](https://stackoverflow.com/questions/695151/data-protocol-url-size-limitations/41755526#41755526).
## Contributing
Please let us know how can we help. Do check out [issues](https://github.com/bubkoo/html-to-image/issues) for bug reports or suggestions first.
To become a contributor, please follow our [contributing guide](/CONTRIBUTING.md).
<a href="https://github.com/bubkoo/html-to-image/graphs/contributors">
<img src="/CONTRIBUTORS.svg" alt="Contributors" width="740" />
</a>
## License
The scripts and documentation in this project are released under the [MIT License](LICENSE)
================================================
FILE: karma.conf.js
================================================
/* eslint-disable */
const cpuCount = require('os').cpus().length
const reportsDir = 'test/coverage'
module.exports = function (config) {
const hasFlag = (flag) => process.argv.some((arg) => arg === flag)
const isDebug = hasFlag('--debug')
const isWatch = hasFlag('--auto-watch')
config.set({
files: [
{
pattern: 'test/resources/**/*',
included: false,
served: true,
},
{
pattern: 'node_modules/@fortawesome/fontawesome-free/css/*.*',
included: false,
served: true,
},
{
pattern: 'node_modules/@fortawesome/fontawesome-free/webfonts/*.*',
included: false,
served: true,
},
'src/**/*.ts',
'test/spec/**/*.ts',
],
plugins: [
'jasmine-core',
'karma-jasmine',
'karma-typescript',
'karma-spec-reporter',
'karma-chrome-launcher',
],
frameworks: ['jasmine', 'karma-typescript'],
preprocessors: {
'**/*.ts': ['karma-typescript'],
},
reporters: ['spec', 'karma-typescript'],
specReporter: {
suppressPassed: isWatch || isDebug,
},
browsers: [process.env.CI ? 'ChromeHeadless' : 'ChromeHeadless'],
customLaunchers: {
ChromeHeadless: {
base: 'Chrome',
flags: [
'--headless',
'--no-sandbox',
'--disable-gpu',
'--disable-translate',
'--disable-extensions',
'--remote-debugging-port=9222',
],
},
},
karmaTypescriptConfig: {
tsconfig: './tsconfig.json',
include: ['src/**/*.ts', 'test/spec/**/*.ts'],
bundlerOptions: { sourceMap: true },
coverageOptions: {
instrumentation: !isDebug,
exclude: [/spec\/.*\.ts$/],
},
reports: {
html: reportsDir,
lcovonly: {
directory: reportsDir,
subdirectory: './',
filename: 'lcov.info',
},
cobertura: {
directory: reportsDir,
subdirectory: './',
filename: 'coverage.xml',
},
'text-summary': '',
},
},
client: {
jasmine: {
random: false,
},
},
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser should be started simultaneous
concurrency: cpuCount || Infinity,
})
}
================================================
FILE: package.json
================================================
{
"name": "html-to-image",
"version": "1.11.13",
"description": "Generates an image from a DOM node using HTML5 canvas and SVG.",
"main": "lib/index.js",
"module": "es/index.js",
"unpkg": "dist/html-to-image.js",
"types": "lib/index.d.ts",
"files": [
"dist",
"es",
"lib",
"src"
],
"keywords": [
"screenshot",
"capture",
"canvas",
"html",
"dom",
"image",
"vector",
"svg"
],
"scripts": {
"lint": "eslint 'src/**/*.{js,ts}?(x)' --fix",
"clean": "rimraf dist es lib",
"build:esm": "tsc --module esnext --target es2017 --outDir ./es",
"build:cjs": "tsc --module commonjs --target es5 --outDir ./lib",
"build:umd": "rollup -c --bundleConfigAsCjs",
"build": "run-s build:esm build:cjs build:umd",
"prebuild": "run-s lint clean",
"test": "karma start",
"test:watch": "karma start --single-run=false --auto-watch",
"test:debug": "karma start --browsers=Chrome --single-run=false --auto-watch --debug",
"coveralls": "cat ./test/coverage/lcov.info | coveralls",
"pretest": "rimraf ./test/coverage",
"prepare": "is-ci || husky install .husky"
},
"lint-staged": {
"**/*.{js,jsx,tsx,ts,less,md,json}": [
"pretty-quick —-staged"
],
"*.ts": [
"eslint --fix"
]
},
"release": {
"extends": "@bubkoo/semantic-release-config"
},
"license": "MIT",
"author": {
"name": "bubkooo",
"email": "bubkoo.wy@gmail.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/bubkoo/html-to-image.git"
},
"bugs": {
"url": "https://github.com/bubkoo/html-to-image/issues"
},
"homepage": "https://github.com/bubkoo/html-to-image#readme",
"devDependencies": {
"@bubkoo/commitlint-config": "^1.0.2",
"@bubkoo/eslint-config": "^1.3.1",
"@bubkoo/rollup-config": "^1.2.1",
"@bubkoo/semantic-release-config": "^1.6.1",
"@bubkoo/tsconfig": "^1.0.0",
"@fortawesome/fontawesome-free": "^6.1.2",
"@types/jasmine": "^4.3.1",
"@types/pixelmatch": "^5.2.4",
"coveralls": "^3.1.1",
"eslint": "^8.22.0",
"husky": "^8.0.1",
"is-ci": "^3.0.1",
"jasmine-core": "^4.3.0",
"karma": "^6.4.0",
"karma-chrome-launcher": "^3.1.1",
"karma-coverage": "^2.2.0",
"karma-jasmine": "^5.1.0",
"karma-spec-reporter": "^0.0.36",
"karma-typescript": "^5.5.3",
"npm-run-all": "^4.1.5",
"pixelmatch": "^5.3.0",
"prettier": "^2.8.1",
"pretty-quick": "^3.1.3",
"rimraf": "^4.1.2",
"rollup": "^3.7.4",
"tslib": "^2.4.0",
"typescript": "^4.9.5"
}
}
================================================
FILE: rollup.config.js
================================================
import config from '@bubkoo/rollup-config'
export default config({
output: [
{
name: 'htmlToImage',
format: 'umd',
file: 'dist/html-to-image.js',
sourcemap: true,
},
],
})
================================================
FILE: src/apply-style.ts
================================================
import type { Options } from './types'
export function applyStyle<T extends HTMLElement>(
node: T,
options: Options,
): T {
const { style } = node
if (options.backgroundColor) {
style.backgroundColor = options.backgroundColor
}
if (options.width) {
style.width = `${options.width}px`
}
if (options.height) {
style.height = `${options.height}px`
}
const manual = options.style
if (manual != null) {
Object.keys(manual).forEach((key: any) => {
style[key] = manual[key] as string
})
}
return node
}
================================================
FILE: src/clone-node.ts
================================================
import type { Options } from './types'
import { clonePseudoElements } from './clone-pseudos'
import {
createImage,
toArray,
isInstanceOfElement,
getStyleProperties,
} from './util'
import { getMimeType } from './mimes'
import { resourceToDataURL } from './dataurl'
async function cloneCanvasElement(canvas: HTMLCanvasElement) {
const dataURL = canvas.toDataURL()
if (dataURL === 'data:,') {
return canvas.cloneNode(false) as HTMLCanvasElement
}
return createImage(dataURL)
}
async function cloneVideoElement(video: HTMLVideoElement, options: Options) {
if (video.currentSrc) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = video.clientWidth
canvas.height = video.clientHeight
ctx?.drawImage(video, 0, 0, canvas.width, canvas.height)
const dataURL = canvas.toDataURL()
return createImage(dataURL)
}
const poster = video.poster
const contentType = getMimeType(poster)
const dataURL = await resourceToDataURL(poster, contentType, options)
return createImage(dataURL)
}
async function cloneIFrameElement(iframe: HTMLIFrameElement, options: Options) {
try {
if (iframe?.contentDocument?.body) {
return (await cloneNode(
iframe.contentDocument.body,
options,
true,
)) as HTMLBodyElement
}
} catch {
// Failed to clone iframe
}
return iframe.cloneNode(false) as HTMLIFrameElement
}
async function cloneSingleNode<T extends HTMLElement>(
node: T,
options: Options,
): Promise<HTMLElement> {
if (isInstanceOfElement(node, HTMLCanvasElement)) {
return cloneCanvasElement(node)
}
if (isInstanceOfElement(node, HTMLVideoElement)) {
return cloneVideoElement(node, options)
}
if (isInstanceOfElement(node, HTMLIFrameElement)) {
return cloneIFrameElement(node, options)
}
return node.cloneNode(isSVGElement(node)) as T
}
const isSlotElement = (node: HTMLElement): node is HTMLSlotElement =>
node.tagName != null && node.tagName.toUpperCase() === 'SLOT'
const isSVGElement = (node: HTMLElement): node is HTMLSlotElement =>
node.tagName != null && node.tagName.toUpperCase() === 'SVG'
async function cloneChildren<T extends HTMLElement>(
nativeNode: T,
clonedNode: T,
options: Options,
): Promise<T> {
if (isSVGElement(clonedNode)) {
return clonedNode
}
let children: T[] = []
if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
children = toArray<T>(nativeNode.assignedNodes())
} else if (
isInstanceOfElement(nativeNode, HTMLIFrameElement) &&
nativeNode.contentDocument?.body
) {
children = toArray<T>(nativeNode.contentDocument.body.childNodes)
} else {
children = toArray<T>((nativeNode.shadowRoot ?? nativeNode).childNodes)
}
if (
children.length === 0 ||
isInstanceOfElement(nativeNode, HTMLVideoElement)
) {
return clonedNode
}
await children.reduce(
(deferred, child) =>
deferred
.then(() => cloneNode(child, options))
.then((clonedChild: HTMLElement | null) => {
if (clonedChild) {
clonedNode.appendChild(clonedChild)
}
}),
Promise.resolve(),
)
return clonedNode
}
function cloneCSSStyle<T extends HTMLElement>(
nativeNode: T,
clonedNode: T,
options: Options,
) {
const targetStyle = clonedNode.style
if (!targetStyle) {
return
}
const sourceStyle = window.getComputedStyle(nativeNode)
if (sourceStyle.cssText) {
targetStyle.cssText = sourceStyle.cssText
targetStyle.transformOrigin = sourceStyle.transformOrigin
} else {
getStyleProperties(options).forEach((name) => {
let value = sourceStyle.getPropertyValue(name)
if (name === 'font-size' && value.endsWith('px')) {
const reducedFont =
Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1
value = `${reducedFont}px`
}
if (
isInstanceOfElement(nativeNode, HTMLIFrameElement) &&
name === 'display' &&
value === 'inline'
) {
value = 'block'
}
if (name === 'd' && clonedNode.getAttribute('d')) {
value = `path(${clonedNode.getAttribute('d')})`
}
targetStyle.setProperty(
name,
value,
sourceStyle.getPropertyPriority(name),
)
})
}
}
function cloneInputValue<T extends HTMLElement>(nativeNode: T, clonedNode: T) {
if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
clonedNode.innerHTML = nativeNode.value
}
if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
clonedNode.setAttribute('value', nativeNode.value)
}
}
function cloneSelectValue<T extends HTMLElement>(nativeNode: T, clonedNode: T) {
if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
const clonedSelect = clonedNode as any as HTMLSelectElement
const selectedOption = Array.from(clonedSelect.children).find(
(child) => nativeNode.value === child.getAttribute('value'),
)
if (selectedOption) {
selectedOption.setAttribute('selected', '')
}
}
}
function decorate<T extends HTMLElement>(
nativeNode: T,
clonedNode: T,
options: Options,
): T {
if (isInstanceOfElement(clonedNode, Element)) {
cloneCSSStyle(nativeNode, clonedNode, options)
clonePseudoElements(nativeNode, clonedNode, options)
cloneInputValue(nativeNode, clonedNode)
cloneSelectValue(nativeNode, clonedNode)
}
return clonedNode
}
async function ensureSVGSymbols<T extends HTMLElement>(
clone: T,
options: Options,
) {
const uses = clone.querySelectorAll ? clone.querySelectorAll('use') : []
if (uses.length === 0) {
return clone
}
const processedDefs: { [key: string]: HTMLElement } = {}
for (let i = 0; i < uses.length; i++) {
const use = uses[i]
const id = use.getAttribute('xlink:href')
if (id) {
const exist = clone.querySelector(id)
const definition = document.querySelector(id) as HTMLElement
if (!exist && definition && !processedDefs[id]) {
// eslint-disable-next-line no-await-in-loop
processedDefs[id] = (await cloneNode(definition, options, true))!
}
}
}
const nodes = Object.values(processedDefs)
if (nodes.length) {
const ns = 'http://www.w3.org/1999/xhtml'
const svg = document.createElementNS(ns, 'svg')
svg.setAttribute('xmlns', ns)
svg.style.position = 'absolute'
svg.style.width = '0'
svg.style.height = '0'
svg.style.overflow = 'hidden'
svg.style.display = 'none'
const defs = document.createElementNS(ns, 'defs')
svg.appendChild(defs)
for (let i = 0; i < nodes.length; i++) {
defs.appendChild(nodes[i])
}
clone.appendChild(svg)
}
return clone
}
export async function cloneNode<T extends HTMLElement>(
node: T,
options: Options,
isRoot?: boolean,
): Promise<T | null> {
if (!isRoot && options.filter && !options.filter(node)) {
return null
}
return Promise.resolve(node)
.then((clonedNode) => cloneSingleNode(clonedNode, options) as Promise<T>)
.then((clonedNode) => cloneChildren(node, clonedNode, options))
.then((clonedNode) => decorate(node, clonedNode, options))
.then((clonedNode) => ensureSVGSymbols(clonedNode, options))
}
================================================
FILE: src/clone-pseudos.ts
================================================
import type { Options } from './types'
import { uuid, getStyleProperties } from './util'
type Pseudo = ':before' | ':after'
function formatCSSText(style: CSSStyleDeclaration) {
const content = style.getPropertyValue('content')
return `${style.cssText} content: '${content.replace(/'|"/g, '')}';`
}
function formatCSSProperties(style: CSSStyleDeclaration, options: Options) {
return getStyleProperties(options)
.map((name) => {
const value = style.getPropertyValue(name)
const priority = style.getPropertyPriority(name)
return `${name}: ${value}${priority ? ' !important' : ''};`
})
.join(' ')
}
function getPseudoElementStyle(
className: string,
pseudo: Pseudo,
style: CSSStyleDeclaration,
options: Options,
): Text {
const selector = `.${className}:${pseudo}`
const cssText = style.cssText
? formatCSSText(style)
: formatCSSProperties(style, options)
return document.createTextNode(`${selector}{${cssText}}`)
}
function clonePseudoElement<T extends HTMLElement>(
nativeNode: T,
clonedNode: T,
pseudo: Pseudo,
options: Options,
) {
const style = window.getComputedStyle(nativeNode, pseudo)
const content = style.getPropertyValue('content')
if (content === '' || content === 'none') {
return
}
const className = uuid()
try {
clonedNode.className = `${clonedNode.className} ${className}`
} catch (err) {
return
}
const styleElement = document.createElement('style')
styleElement.appendChild(
getPseudoElementStyle(className, pseudo, style, options),
)
clonedNode.appendChild(styleElement)
}
export function clonePseudoElements<T extends HTMLElement>(
nativeNode: T,
clonedNode: T,
options: Options,
) {
clonePseudoElement(nativeNode, clonedNode, ':before', options)
clonePseudoElement(nativeNode, clonedNode, ':after', options)
}
================================================
FILE: src/dataurl.ts
================================================
import { Options } from './types'
function getContentFromDataUrl(dataURL: string) {
return dataURL.split(/,/)[1]
}
export function isDataUrl(url: string) {
return url.search(/^(data:)/) !== -1
}
export function makeDataUrl(content: string, mimeType: string) {
return `data:${mimeType};base64,${content}`
}
export async function fetchAsDataURL<T>(
url: string,
init: RequestInit | undefined,
process: (data: { result: string; res: Response }) => T,
): Promise<T> {
const res = await fetch(url, init)
if (res.status === 404) {
throw new Error(`Resource "${res.url}" not found`)
}
const blob = await res.blob()
return new Promise<T>((resolve, reject) => {
const reader = new FileReader()
reader.onerror = reject
reader.onloadend = () => {
try {
resolve(process({ res, result: reader.result as string }))
} catch (error) {
reject(error)
}
}
reader.readAsDataURL(blob)
})
}
const cache: { [url: string]: string } = {}
function getCacheKey(
url: string,
contentType: string | undefined,
includeQueryParams: boolean | undefined,
) {
let key = url.replace(/\?.*/, '')
if (includeQueryParams) {
key = url
}
// font resource
if (/ttf|otf|eot|woff2?/i.test(key)) {
key = key.replace(/.*\//, '')
}
return contentType ? `[${contentType}]${key}` : key
}
export async function resourceToDataURL(
resourceUrl: string,
contentType: string | undefined,
options: Options,
) {
const cacheKey = getCacheKey(
resourceUrl,
contentType,
options.includeQueryParams,
)
if (cache[cacheKey] != null) {
return cache[cacheKey]
}
// ref: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
if (options.cacheBust) {
// eslint-disable-next-line no-param-reassign
resourceUrl += (/\?/.test(resourceUrl) ? '&' : '?') + new Date().getTime()
}
let dataURL: string
try {
const content = await fetchAsDataURL(
resourceUrl,
options.fetchRequestInit,
({ res, result }) => {
if (!contentType) {
// eslint-disable-next-line no-param-reassign
contentType = res.headers.get('Content-Type') || ''
}
return getContentFromDataUrl(result)
},
)
dataURL = makeDataUrl(content, contentType!)
} catch (error) {
dataURL = options.imagePlaceholder || ''
let msg = `Failed to fetch resource: ${resourceUrl}`
if (error) {
msg = typeof error === 'string' ? error : error.message
}
if (msg) {
console.warn(msg)
}
}
cache[cacheKey] = dataURL
return dataURL
}
================================================
FILE: src/embed-images.ts
================================================
import { Options } from './types'
import { embedResources } from './embed-resources'
import { toArray, isInstanceOfElement } from './util'
import { isDataUrl, resourceToDataURL } from './dataurl'
import { getMimeType } from './mimes'
async function embedProp(
propName: string,
node: HTMLElement,
options: Options,
) {
const propValue = node.style?.getPropertyValue(propName)
if (propValue) {
const cssString = await embedResources(propValue, null, options)
node.style.setProperty(
propName,
cssString,
node.style.getPropertyPriority(propName),
)
return true
}
return false
}
async function embedBackground<T extends HTMLElement>(
clonedNode: T,
options: Options,
) {
;(await embedProp('background', clonedNode, options)) ||
(await embedProp('background-image', clonedNode, options))
;(await embedProp('mask', clonedNode, options)) ||
(await embedProp('-webkit-mask', clonedNode, options)) ||
(await embedProp('mask-image', clonedNode, options)) ||
(await embedProp('-webkit-mask-image', clonedNode, options))
}
async function embedImageNode<T extends HTMLElement | SVGImageElement>(
clonedNode: T,
options: Options,
) {
const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement)
if (
!(isImageElement && !isDataUrl(clonedNode.src)) &&
!(
isInstanceOfElement(clonedNode, SVGImageElement) &&
!isDataUrl(clonedNode.href.baseVal)
)
) {
return
}
const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal
const dataURL = await resourceToDataURL(url, getMimeType(url), options)
await new Promise((resolve, reject) => {
clonedNode.onload = resolve
clonedNode.onerror = options.onImageErrorHandler
? (...attributes) => {
try {
resolve(options.onImageErrorHandler!(...attributes))
} catch (error) {
reject(error)
}
}
: reject
const image = clonedNode as HTMLImageElement
if (image.decode) {
image.decode = resolve as any
}
if (image.loading === 'lazy') {
image.loading = 'eager'
}
if (isImageElement) {
clonedNode.srcset = ''
clonedNode.src = dataURL
} else {
clonedNode.href.baseVal = dataURL
}
})
}
async function embedChildren<T extends HTMLElement>(
clonedNode: T,
options: Options,
) {
const children = toArray<HTMLElement>(clonedNode.childNodes)
const deferreds = children.map((child) => embedImages(child, options))
await Promise.all(deferreds).then(() => clonedNode)
}
export async function embedImages<T extends HTMLElement>(
clonedNode: T,
options: Options,
) {
if (isInstanceOfElement(clonedNode, Element)) {
await embedBackground(clonedNode, options)
await embedImageNode(clonedNode, options)
await embedChildren(clonedNode, options)
}
}
================================================
FILE: src/embed-resources.ts
================================================
import { Options } from './types'
import { resolveUrl } from './util'
import { getMimeType } from './mimes'
import { isDataUrl, makeDataUrl, resourceToDataURL } from './dataurl'
const URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g
const URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g
const FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g
function toRegex(url: string): RegExp {
// eslint-disable-next-line no-useless-escape
const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1')
return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, 'g')
}
export function parseURLs(cssText: string): string[] {
const urls: string[] = []
cssText.replace(URL_REGEX, (raw, quotation, url) => {
urls.push(url)
return raw
})
return urls.filter((url) => !isDataUrl(url))
}
export async function embed(
cssText: string,
resourceURL: string,
baseURL: string | null,
options: Options,
getContentFromUrl?: (url: string) => Promise<string>,
): Promise<string> {
try {
const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL
const contentType = getMimeType(resourceURL)
let dataURL: string
if (getContentFromUrl) {
const content = await getContentFromUrl(resolvedURL)
dataURL = makeDataUrl(content, contentType)
} else {
dataURL = await resourceToDataURL(resolvedURL, contentType, options)
}
return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`)
} catch (error) {
// pass
}
return cssText
}
function filterPreferredFontFormat(
str: string,
{ preferredFontFormat }: Options,
): string {
return !preferredFontFormat
? str
: str.replace(FONT_SRC_REGEX, (match: string) => {
// eslint-disable-next-line no-constant-condition
while (true) {
const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || []
if (!format) {
return ''
}
if (format === preferredFontFormat) {
return `src: ${src};`
}
}
})
}
export function shouldEmbed(url: string): boolean {
return url.search(URL_REGEX) !== -1
}
export async function embedResources(
cssText: string,
baseUrl: string | null,
options: Options,
): Promise<string> {
if (!shouldEmbed(cssText)) {
return cssText
}
const filteredCSSText = filterPreferredFontFormat(cssText, options)
const urls = parseURLs(filteredCSSText)
return urls.reduce(
(deferred, url) =>
deferred.then((css) => embed(css, url, baseUrl, options)),
Promise.resolve(filteredCSSText),
)
}
================================================
FILE: src/embed-webfonts.ts
================================================
import type { Options } from './types'
import { toArray } from './util'
import { fetchAsDataURL } from './dataurl'
import { shouldEmbed, embedResources } from './embed-resources'
interface Metadata {
url: string
cssText: string
}
const cssFetchCache: { [href: string]: Metadata } = {}
async function fetchCSS(url: string) {
let cache = cssFetchCache[url]
if (cache != null) {
return cache
}
const res = await fetch(url)
const cssText = await res.text()
cache = { url, cssText }
cssFetchCache[url] = cache
return cache
}
async function embedFonts(data: Metadata, options: Options): Promise<string> {
let cssText = data.cssText
const regexUrl = /url\(["']?([^"')]+)["']?\)/g
const fontLocs = cssText.match(/url\([^)]+\)/g) || []
const loadFonts = fontLocs.map(async (loc: string) => {
let url = loc.replace(regexUrl, '$1')
if (!url.startsWith('https://')) {
url = new URL(url, data.url).href
}
return fetchAsDataURL<[string, string]>(
url,
options.fetchRequestInit,
({ result }) => {
cssText = cssText.replace(loc, `url(${result})`)
return [loc, result]
},
)
})
return Promise.all(loadFonts).then(() => cssText)
}
function parseCSS(source: string) {
if (source == null) {
return []
}
const result: string[] = []
const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi
// strip out comments
let cssText = source.replace(commentsRegex, '')
// eslint-disable-next-line prefer-regex-literals
const keyframesRegex = new RegExp(
'((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})',
'gi',
)
// eslint-disable-next-line no-constant-condition
while (true) {
const matches = keyframesRegex.exec(cssText)
if (matches === null) {
break
}
result.push(matches[0])
}
cssText = cssText.replace(keyframesRegex, '')
const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi
// to match css & media queries together
const combinedCSSRegex =
'((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]' +
'*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'
// unified regex
const unifiedRegex = new RegExp(combinedCSSRegex, 'gi')
// eslint-disable-next-line no-constant-condition
while (true) {
let matches = importRegex.exec(cssText)
if (matches === null) {
matches = unifiedRegex.exec(cssText)
if (matches === null) {
break
} else {
importRegex.lastIndex = unifiedRegex.lastIndex
}
} else {
unifiedRegex.lastIndex = importRegex.lastIndex
}
result.push(matches[0])
}
return result
}
async function getCSSRules(
styleSheets: CSSStyleSheet[],
options: Options,
): Promise<CSSStyleRule[]> {
const ret: CSSStyleRule[] = []
const deferreds: Promise<number | void>[] = []
// First loop inlines imports
styleSheets.forEach((sheet) => {
if ('cssRules' in sheet) {
try {
toArray<CSSRule>(sheet.cssRules || []).forEach((item, index) => {
if (item.type === CSSRule.IMPORT_RULE) {
let importIndex = index + 1
const url = (item as CSSImportRule).href
const deferred = fetchCSS(url)
.then((metadata) => embedFonts(metadata, options))
.then((cssText) =>
parseCSS(cssText).forEach((rule) => {
try {
sheet.insertRule(
rule,
rule.startsWith('@import')
? (importIndex += 1)
: sheet.cssRules.length,
)
} catch (error) {
console.error('Error inserting rule from remote css', {
rule,
error,
})
}
}),
)
.catch((e) => {
console.error('Error loading remote css', e.toString())
})
deferreds.push(deferred)
}
})
} catch (e) {
const inline =
styleSheets.find((a) => a.href == null) || document.styleSheets[0]
if (sheet.href != null) {
deferreds.push(
fetchCSS(sheet.href)
.then((metadata) => embedFonts(metadata, options))
.then((cssText) =>
parseCSS(cssText).forEach((rule) => {
inline.insertRule(rule, inline.cssRules.length)
}),
)
.catch((err: unknown) => {
console.error('Error loading remote stylesheet', err)
}),
)
}
console.error('Error inlining remote css file', e)
}
}
})
return Promise.all(deferreds).then(() => {
// Second loop parses rules
styleSheets.forEach((sheet) => {
if ('cssRules' in sheet) {
try {
toArray<CSSStyleRule>(sheet.cssRules || []).forEach((item) => {
ret.push(item)
})
} catch (e) {
console.error(`Error while reading CSS rules from ${sheet.href}`, e)
}
}
})
return ret
})
}
function getWebFontRules(cssRules: CSSStyleRule[]): CSSStyleRule[] {
return cssRules
.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE)
.filter((rule) => shouldEmbed(rule.style.getPropertyValue('src')))
}
async function parseWebFontRules<T extends HTMLElement>(
node: T,
options: Options,
) {
if (node.ownerDocument == null) {
throw new Error('Provided element is not within a Document')
}
const styleSheets = toArray<CSSStyleSheet>(node.ownerDocument.styleSheets)
const cssRules = await getCSSRules(styleSheets, options)
return getWebFontRules(cssRules)
}
function normalizeFontFamily(font: string) {
return font.trim().replace(/["']/g, '')
}
function getUsedFonts(node: HTMLElement) {
const fonts = new Set<string>()
function traverse(node: HTMLElement) {
const fontFamily =
node.style.fontFamily || getComputedStyle(node).fontFamily
fontFamily.split(',').forEach((font) => {
fonts.add(normalizeFontFamily(font))
})
Array.from(node.children).forEach((child) => {
if (child instanceof HTMLElement) {
traverse(child)
}
})
}
traverse(node)
return fonts
}
export async function getWebFontCSS<T extends HTMLElement>(
node: T,
options: Options,
): Promise<string> {
const rules = await parseWebFontRules(node, options)
const usedFonts = getUsedFonts(node)
const cssTexts = await Promise.all(
rules
.filter((rule) =>
usedFonts.has(normalizeFontFamily(rule.style.fontFamily)),
)
.map((rule) => {
const baseUrl = rule.parentStyleSheet
? rule.parentStyleSheet.href
: null
return embedResources(rule.cssText, baseUrl, options)
}),
)
return cssTexts.join('\n')
}
export async function embedWebFonts<T extends HTMLElement>(
clonedNode: T,
options: Options,
) {
const cssText =
options.fontEmbedCSS != null
? options.fontEmbedCSS
: options.skipFonts
? null
: await getWebFontCSS(clonedNode, options)
if (cssText) {
const styleNode = document.createElement('style')
const sytleContent = document.createTextNode(cssText)
styleNode.appendChild(sytleContent)
if (clonedNode.firstChild) {
clonedNode.insertBefore(styleNode, clonedNode.firstChild)
} else {
clonedNode.appendChild(styleNode)
}
}
}
================================================
FILE: src/index.ts
================================================
import { Options } from './types'
import { cloneNode } from './clone-node'
import { embedImages } from './embed-images'
import { applyStyle } from './apply-style'
import { embedWebFonts, getWebFontCSS } from './embed-webfonts'
import {
getImageSize,
getPixelRatio,
createImage,
canvasToBlob,
nodeToDataURL,
checkCanvasDimensions,
} from './util'
export async function toSvg<T extends HTMLElement>(
node: T,
options: Options = {},
): Promise<string> {
const { width, height } = getImageSize(node, options)
const clonedNode = (await cloneNode(node, options, true)) as HTMLElement
await embedWebFonts(clonedNode, options)
await embedImages(clonedNode, options)
applyStyle(clonedNode, options)
const datauri = await nodeToDataURL(clonedNode, width, height)
return datauri
}
export async function toCanvas<T extends HTMLElement>(
node: T,
options: Options = {},
): Promise<HTMLCanvasElement> {
const { width, height } = getImageSize(node, options)
const svg = await toSvg(node, options)
const img = await createImage(svg)
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')!
const ratio = options.pixelRatio || getPixelRatio()
const canvasWidth = options.canvasWidth || width
const canvasHeight = options.canvasHeight || height
canvas.width = canvasWidth * ratio
canvas.height = canvasHeight * ratio
if (!options.skipAutoScale) {
checkCanvasDimensions(canvas)
}
canvas.style.width = `${canvasWidth}`
canvas.style.height = `${canvasHeight}`
if (options.backgroundColor) {
context.fillStyle = options.backgroundColor
context.fillRect(0, 0, canvas.width, canvas.height)
}
context.drawImage(img, 0, 0, canvas.width, canvas.height)
return canvas
}
export async function toPixelData<T extends HTMLElement>(
node: T,
options: Options = {},
): Promise<Uint8ClampedArray> {
const { width, height } = getImageSize(node, options)
const canvas = await toCanvas(node, options)
const ctx = canvas.getContext('2d')!
return ctx.getImageData(0, 0, width, height).data
}
export async function toPng<T extends HTMLElement>(
node: T,
options: Options = {},
): Promise<string> {
const canvas = await toCanvas(node, options)
return canvas.toDataURL()
}
export async function toJpeg<T extends HTMLElement>(
node: T,
options: Options = {},
): Promise<string> {
const canvas = await toCanvas(node, options)
return canvas.toDataURL('image/jpeg', options.quality || 1)
}
export async function toBlob<T extends HTMLElement>(
node: T,
options: Options = {},
): Promise<Blob | null> {
const canvas = await toCanvas(node, options)
const blob = await canvasToBlob(canvas)
return blob
}
export async function getFontEmbedCSS<T extends HTMLElement>(
node: T,
options: Options = {},
): Promise<string> {
return getWebFontCSS(node, options)
}
================================================
FILE: src/mimes.ts
================================================
const WOFF = 'application/font-woff'
const JPEG = 'image/jpeg'
const mimes: { [key: string]: string } = {
woff: WOFF,
woff2: WOFF,
ttf: 'application/font-truetype',
eot: 'application/vnd.ms-fontobject',
png: 'image/png',
jpg: JPEG,
jpeg: JPEG,
gif: 'image/gif',
tiff: 'image/tiff',
svg: 'image/svg+xml',
webp: 'image/webp',
}
function getExtension(url: string): string {
const match = /\.([^./]*?)$/g.exec(url)
return match ? match[1] : ''
}
export function getMimeType(url: string): string {
const extension = getExtension(url).toLowerCase()
return mimes[extension] || ''
}
================================================
FILE: src/types.ts
================================================
export interface Options {
/**
* Width in pixels to be applied to node before rendering.
*/
width?: number
/**
* Height in pixels to be applied to node before rendering.
*/
height?: number
/**
* A string value for the background color, any valid CSS color value.
*/
backgroundColor?: string
/**
* Width in pixels to be applied to canvas on export.
*/
canvasWidth?: number
/**
* Height in pixels to be applied to canvas on export.
*/
canvasHeight?: number
/**
* An object whose properties to be copied to node's style before rendering.
*/
style?: Partial<CSSStyleDeclaration>
/**
* An array of style properties to be copied to node's style before rendering.
* For performance-critical scenarios, users may want to specify only the
* required properties instead of all styles.
*/
includeStyleProperties?: string[]
/**
* A function taking DOM node as argument. Should return `true` if passed
* node should be included in the output. Excluding node means excluding
* it's children as well.
*/
filter?: (domNode: HTMLElement) => boolean
/**
* A number between `0` and `1` indicating image quality (e.g. 0.92 => 92%)
* of the JPEG image.
*/
quality?: number
/**
* Set to `true` to append the current time as a query string to URL
* requests to enable cache busting.
*/
cacheBust?: boolean
/**
* Set false to use all URL as cache key.
* Default: false | undefined - which strips away the query parameters
*/
includeQueryParams?: boolean
/**
* A data URL for a placeholder image that will be used when fetching
* an image fails. Defaults to an empty string and will render empty
* areas for failed images.
*/
imagePlaceholder?: string
/**
* The pixel ratio of captured image. Defalut is the actual pixel ratio of
* the device. Set 1 to use as initial-scale 1 for the image
*/
pixelRatio?: number
/**
* Option to skip the fonts download and embed.
*/
skipFonts?: boolean
/**
* The preferred font format. If specified all other font formats are ignored.
*/
preferredFontFormat?:
| 'woff'
| 'woff2'
| 'truetype'
| 'opentype'
| 'embedded-opentype'
| 'svg'
| string
/**
* A CSS string to specify for font embeds. If specified only this CSS will
* be present in the resulting image. Use with `getFontEmbedCSS()` to
* create embed CSS for use across multiple calls to library functions.
*/
fontEmbedCSS?: string
/**
* A boolean to turn off auto scaling for truly massive images..
*/
skipAutoScale?: boolean
/**
* A string indicating the image format. The default type is image/png; that type is also used if the given type isn't supported.
*/
type?: string
/**
*
*the second parameter of window.fetch (Promise<Response> fetch(input[, init]))
*
*/
fetchRequestInit?: RequestInit
/**
* An event handler for the error event when any image in html has problem with loading.
*/
onImageErrorHandler?: OnErrorEventHandler
}
================================================
FILE: src/util.ts
================================================
import type { Options } from './types'
export function resolveUrl(url: string, baseUrl: string | null): string {
// url is absolute already
if (url.match(/^[a-z]+:\/\//i)) {
return url
}
// url is absolute already, without protocol
if (url.match(/^\/\//)) {
return window.location.protocol + url
}
// dataURI, mailto:, tel:, etc.
if (url.match(/^[a-z]+:/i)) {
return url
}
const doc = document.implementation.createHTMLDocument()
const base = doc.createElement('base')
const a = doc.createElement('a')
doc.head.appendChild(base)
doc.body.appendChild(a)
if (baseUrl) {
base.href = baseUrl
}
a.href = url
return a.href
}
export const uuid = (() => {
// generate uuid for className of pseudo elements.
// We should not use GUIDs, otherwise pseudo elements sometimes cannot be captured.
let counter = 0
// ref: http://stackoverflow.com/a/6248722/2519373
const random = () =>
// eslint-disable-next-line no-bitwise
`0000${((Math.random() * 36 ** 4) << 0).toString(36)}`.slice(-4)
return () => {
counter += 1
return `u${random()}${counter}`
}
})()
export function delay<T>(ms: number) {
return (args: T) =>
new Promise<T>((resolve) => {
setTimeout(() => resolve(args), ms)
})
}
export function toArray<T>(arrayLike: any): T[] {
const arr: T[] = []
for (let i = 0, l = arrayLike.length; i < l; i++) {
arr.push(arrayLike[i])
}
return arr
}
let styleProps: string[] | null = null
export function getStyleProperties(options: Options = {}): string[] {
if (styleProps) {
return styleProps
}
if (options.includeStyleProperties) {
styleProps = options.includeStyleProperties
return styleProps
}
styleProps = toArray(window.getComputedStyle(document.documentElement))
return styleProps
}
function px(node: HTMLElement, styleProperty: string) {
const win = node.ownerDocument.defaultView || window
const val = win.getComputedStyle(node).getPropertyValue(styleProperty)
return val ? parseFloat(val.replace('px', '')) : 0
}
function getNodeWidth(node: HTMLElement) {
const leftBorder = px(node, 'border-left-width')
const rightBorder = px(node, 'border-right-width')
return node.clientWidth + leftBorder + rightBorder
}
function getNodeHeight(node: HTMLElement) {
const topBorder = px(node, 'border-top-width')
const bottomBorder = px(node, 'border-bottom-width')
return node.clientHeight + topBorder + bottomBorder
}
export function getImageSize(targetNode: HTMLElement, options: Options = {}) {
const width = options.width || getNodeWidth(targetNode)
const height = options.height || getNodeHeight(targetNode)
return { width, height }
}
export function getPixelRatio() {
let ratio
let FINAL_PROCESS
try {
FINAL_PROCESS = process
} catch (e) {
// pass
}
const val =
FINAL_PROCESS && FINAL_PROCESS.env
? FINAL_PROCESS.env.devicePixelRatio
: null
if (val) {
ratio = parseInt(val, 10)
if (Number.isNaN(ratio)) {
ratio = 1
}
}
return ratio || window.devicePixelRatio || 1
}
// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size
const canvasDimensionLimit = 16384
export function checkCanvasDimensions(canvas: HTMLCanvasElement) {
if (
canvas.width > canvasDimensionLimit ||
canvas.height > canvasDimensionLimit
) {
if (
canvas.width > canvasDimensionLimit &&
canvas.height > canvasDimensionLimit
) {
if (canvas.width > canvas.height) {
canvas.height *= canvasDimensionLimit / canvas.width
canvas.width = canvasDimensionLimit
} else {
canvas.width *= canvasDimensionLimit / canvas.height
canvas.height = canvasDimensionLimit
}
} else if (canvas.width > canvasDimensionLimit) {
canvas.height *= canvasDimensionLimit / canvas.width
canvas.width = canvasDimensionLimit
} else {
canvas.width *= canvasDimensionLimit / canvas.height
canvas.height = canvasDimensionLimit
}
}
}
export function canvasToBlob(
canvas: HTMLCanvasElement,
options: Options = {},
): Promise<Blob | null> {
if (canvas.toBlob) {
return new Promise((resolve) => {
canvas.toBlob(
resolve,
options.type ? options.type : 'image/png',
options.quality ? options.quality : 1,
)
})
}
return new Promise((resolve) => {
const binaryString = window.atob(
canvas
.toDataURL(
options.type ? options.type : undefined,
options.quality ? options.quality : undefined,
)
.split(',')[1],
)
const len = binaryString.length
const binaryArray = new Uint8Array(len)
for (let i = 0; i < len; i += 1) {
binaryArray[i] = binaryString.charCodeAt(i)
}
resolve(
new Blob([binaryArray], {
type: options.type ? options.type : 'image/png',
}),
)
})
}
export function createImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
img.decode().then(() => {
requestAnimationFrame(() => resolve(img))
})
}
img.onerror = reject
img.crossOrigin = 'anonymous'
img.decoding = 'async'
img.src = url
})
}
export async function svgToDataURL(svg: SVGElement): Promise<string> {
return Promise.resolve()
.then(() => new XMLSerializer().serializeToString(svg))
.then(encodeURIComponent)
.then((html) => `data:image/svg+xml;charset=utf-8,${html}`)
}
export async function nodeToDataURL(
node: HTMLElement,
width: number,
height: number,
): Promise<string> {
const xmlns = 'http://www.w3.org/2000/svg'
const svg = document.createElementNS(xmlns, 'svg')
const foreignObject = document.createElementNS(xmlns, 'foreignObject')
svg.setAttribute('width', `${width}`)
svg.setAttribute('height', `${height}`)
svg.setAttribute('viewBox', `0 0 ${width} ${height}`)
foreignObject.setAttribute('width', '100%')
foreignObject.setAttribute('height', '100%')
foreignObject.setAttribute('x', '0')
foreignObject.setAttribute('y', '0')
foreignObject.setAttribute('externalResourcesRequired', 'true')
svg.appendChild(foreignObject)
foreignObject.appendChild(node)
return svgToDataURL(svg)
}
export const isInstanceOfElement = <
T extends typeof Element | typeof HTMLElement | typeof SVGImageElement,
>(
node: Element | HTMLElement | SVGImageElement,
instance: T,
): node is T['prototype'] => {
if (node instanceof instance) return true
const nodePrototype = Object.getPrototypeOf(node)
if (nodePrototype === null) return false
return (
nodePrototype.constructor.name === instance.name ||
isInstanceOfElement(nodePrototype, instance)
)
}
================================================
FILE: test/resources/bgcolor/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABAUlEQVR4nO3RsQ0AIADDsP7/NJxBhDx4r5ptOz84n9jrIwURJE2QGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBInZ6wEIkiZIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEiNIjCAxgsQIEnMB2SoboVG5JJoAAAAASUVORK5CYII=
================================================
FILE: test/resources/bgcolor/node.html
================================================
<div id="content"></div>
================================================
FILE: test/resources/bgcolor/style.css
================================================
#dom-node {
height: 100px;
width: 100px;
}
#content {
height: 50px;
width: 50px;
background-color: black;
}
================================================
FILE: test/resources/bigger/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABjCAYAAABt56XsAAAB10lEQVR4Xu3dgW3CQBAAQegHV0OdVPP0A7IRj54KRvKmggujnLxvO1zHGK9LP8wncN1BbtvGDHTmQZ5jXA6QM38I2u8eCCZygGyPVpbgMu6tLMFhmaGVhZF8VtZ2w8Y65zhjPLvK0uhbWZhIYQiBFIYQxneUVhaGUhhCIIUhhNHKAjH2kQpDCKYwhDBaWSDGXFndMTR0CkPDoeN30GGOVBhCOoUhhNFVFohRGGIohSEGMv9CwLlOO1J3DCH6whDC6CoLxPhdZfUoKcFTGBIM6xA95IChdMcQAikMIYyuskCM7hhiKIUhBtJZlgrSO4aGTGFoOCxTFIYYSmEIgRSGEEZhCGIUhhhKYYiBFIYqSGFoyBSGhkNhCDrMkQpDSKcwhDAKQxCjMMRQCkMMpDBUQQpDQ6YwNBwKQ9ChMBRRCkNQpYccMJTeMYRACkMIo7MsEGOWemFo6BSGhkNhCDoUhiJKYQiqFIYYSmEIgRSGEEZhCGIUhhhKYYiB9JCDCtL3GBoyhaHh0FkW6LCeZfV1FQZRYWg4tLJAh7/j9/4rKWFUGBIM6xAdv2MovWMIgRSGEEbH7yDGPFwsDA2dwtBwKAxBh8JQRCkMQZXCEEMpDCGQwhDC+I7yBsViutvRkyz3AAAAAElFTkSuQmCC
================================================
FILE: test/resources/bigger/node.html
================================================
<div class="dom-child-node">
<div class="red"></div>
<div class="green"></div>
<div class="blue"></div>
</div>
================================================
FILE: test/resources/bigger/style.css
================================================
#dom-node {
width: 100px;
}
.child-node {
height: auto;
}
.red {
background-color: red;
}
.green {
background-color: green;
}
.blue {
background-color: blue;
}
.red,
.green,
.blue {
height: 3px;
border: 1px solid lightgrey;
}
================================================
FILE: test/resources/border/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAACBElEQVR4Xu3dQQrDQAzAwPT/j25pX2DoYHxQzkZrJJaQU17P87yfnjMGXgU50+K3SEFu9SjIsR4FKcg1A8f26R1SkGMGjq3TDSnIMQPH1hnfkD7n/yv3FT15CjKxBGYKAiRKREGkTcAqCJAoEQWRNgGrIECiRBRE2gSsggCJElEQaROwCgIkSkRBpE3AKgiQKBEFkTYBqyBAokQURNoErIIAiRJREGkTsAoCJEpEQaRNwCoIkCgRBZE2AasgQKJEFETaBKyCAIkSURBpE7AKAiRKREGkTcAqCJAoEQWRNgGrIECiRBRE2gSsggCJElEQaROwCgIkSkRBpE3AKgiQKBEFkTYBqyBAokQURNoErIIAiRJREGkTsAoCJEpEQaRNwCoIkCgRBZE2AasgQKJEFETaBKyCAIkSURBpE7AKAiRKREGkTcAqCJAoEQWRNgGrIECiRBRE2gSsggCJElEQaROwCgIkSkRBpE3AKgiQKBEFkTYBqyBAokQURNoErIIAiRJREGkTsAoCJEpEQaRNwCoIkCgRBZE2AasgQKJEFETaBKyCAIkSURBpE7AKAiRKREGkTcAqCJAoEQWRNgGrIECiRBRE2gQsHgTsFGJg4Buuf9cPRG2NFGTL9PCcggxFbY0VZMv08JyCDEVtjRVky/TwnIIMRW2NFWTL9PCcggxFbY19AL3RtAEdMr15AAAAAElFTkSuQmCC
================================================
FILE: test/resources/border/node.html
================================================
================================================
FILE: test/resources/border/style.css
================================================
#dom-node {
font-size: 16px;
width: 100px;
height: 100px;
background-color: red;
border-color: black;
border: solid;
border-width: 10px 10px 0.625em 10px;
}
================================================
FILE: test/resources/canvas/image
================================================
data:image/png;base64,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
================================================
FILE: test/resources/canvas/node.html
================================================
<canvas id="content"></canvas>
================================================
FILE: test/resources/canvas/style.css
================================================
#dom-node {
height: 100px;
width: 200px;
}
#content {
height: 100px;
width: 200px;
}
================================================
FILE: test/resources/css-bg/node.html
================================================
<div class="with-background"></div>
================================================
FILE: test/resources/css-bg/style.css
================================================
#dom-node {
width: 100px;
height: 100px;
}
.with-background {
background: url(/base/test/resources/images/image.jpeg) no-repeat left top;
background-size: 100px 100px;
width: 100px;
height: 100px;
}
================================================
FILE: test/resources/custom-element/image
================================================
data:image/png;base64,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
================================================
FILE: test/resources/custom-element/node.html
================================================
<math-field
value="S_{\triangle }=\frac{ab\sin \left(\gamma \right)}{2}"
scrollingelement="[object HTMLHtmlElement]"
class="math no-touch"
role="textbox"
tabindex="0"
read-only=""
>S_{\triangle }=\frac{ab\sin \left(\gamma \right)}{2}
</math-field>
================================================
FILE: test/resources/custom-element/style.css
================================================
#dom-node {
width: 200px;
height: 100px;
}
================================================
FILE: test/resources/dimensions/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAFJklEQVR4Xu3VsRGAQAwEsaf/oqEBINj0RO7A8u9w3efcx0eAwKvAJRAvg8C3gEC8DgI/AgLxPAgIxBsg0AT8QZqbqREBgYwc2ppNQCDNzdSIgEBGDm3NJiCQ5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCAhk5NDWbAICaW6mRgQEMnJoazYBgTQ3UyMCAhk5tDWbgECam6kRAYGMHNqaTUAgzc3UiIBARg5tzSYgkOZmakRAICOHtmYTEEhzMzUiIJCRQ1uzCQikuZkaERDIyKGt2QQE0txMjQgIZOTQ1mwCAmlupkYEBDJyaGs2AYE0N1MjAgIZObQ1m4BAmpupEQGBjBzamk1AIM3N1IiAQEYObc0mIJDmZmpEQCAjh7ZmExBIczM1IiCQkUNbswkIpLmZGhEQyMihrdkEBNLcTI0ICGTk0NZsAgJpbqZGBAQycmhrNgGBNDdTIwICGTm0NZuAQJqbqREBgYwc2ppNQCDNzdSIgEBGDm3NJiCQ5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCAhk5NDWbAICaW6mRgQEMnJoazYBgTQ3UyMCAhk5tDWbgECam6kRAYGMHNqaTUAgzc3UiIBARg5tzSYgkOZmakRAICOHtmYTEEhzMzUiIJCRQ1uzCQikuZkaERDIyKGt2QQE0txMjQgIZOTQ1mwCAmlupkYEBDJyaGs2AYE0N1MjAgIZObQ1m4BAmpupEQGBjBzamk1AIM3N1IiAQEYObc0mIJDmZmpEQCAjh7ZmExBIczM1IiCQkUNbswkIpLmZGhEQyMihrdkEBNLcTI0ICGTk0NZsAgJpbqZGBAQycmhrNgGBNDdTIwICGTm0NZuAQJqbqREBgYwc2ppNQCDNzdSIgEBGDm3NJiCQ5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCAhk5NDWbAICaW6mRgQEMnJoazYBgTQ3UyMCAhk5tDWbgECam6kRAYGMHNqaTUAgzc3UiIBARg5tzSYgkOZmakRAICOHtmYTEEhzMzUiIJCRQ1uzCQikuZkaERDIyKGt2QQE0txMjQgIZOTQ1mwCAmlupkYEBDJyaGs2AYE0N1MjAgIZObQ1m4BAmpupEQGBjBzamk1AIM3N1IiAQEYObc0mIJDmZmpEQCAjh7ZmExBIczM1IiCQkUNbswkIpLmZGhEQyMihrdkEBNLcTI0ICGTk0NZsAgJpbqZGBAQycmhrNgGBNDdTIwICGTm0NZuAQJqbqREBgYwc2ppNQCDNzdSIgEBGDm3NJiCQ5mZqREAgI4e2ZhMQSHMzNSIgkJFDW7MJCKS5mRoREMjIoa3ZBATS3EyNCAhk5NDWbAICaW6mRgQEMnJoazYBgTQ3UyMCAhk5tDWbgECam6kRAYGMHNqaTUAgzc3UiIBARg5tzSYgkOZmakRAICOHtmYTEEhzMzUiIJCRQ1uzCQikuZkaERDIyKGt2QQE0txMjQgIZOTQ1mwCAmlupkYEBDJyaGs2AYE0N1MjAgIZObQ1m4BAmpupEQGBjBzamk1AIM3N1IiAQEYObc0mIJDmZmpEQCAjh7ZmExBIczM1IiCQkUNbswkIpLmZGhEQyMihrdkEBNLcTI0ICGTk0NZsAgJpbqZGBAQycmhrNgGBNDdTIwICGTm0NZuAQJqbqREBgYwc2ppNQCDNzdSIwAOn4Y9IyHT+ZAAAAABJRU5ErkJggg==
================================================
FILE: test/resources/dimensions/node.html
================================================
================================================
FILE: test/resources/dimensions/style.css
================================================
#dom-node {
width: 100px;
height: 100px;
background-color: red;
}
================================================
FILE: test/resources/ext-css/node.html
================================================
<link
href="http://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600"
crossorigin="anonymous"
rel="stylesheet"
/>
================================================
FILE: test/resources/ext-css/style.css
================================================
#dom-node {
width: 100px;
height: 100px;
background-color: black;
}
================================================
FILE: test/resources/filter/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABbUlEQVR4Xu3VgQkAIAzEwHb/oRWc4sA4QUj6uDPnTI8xsAVhWjyQglg9CoL1KEhBNAMYT39IQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcxXi+xykIdgIFKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFpIQTADGM4FBilkAQH5WFMAAAAASUVORK5CYII=
================================================
FILE: test/resources/filter/node.html
================================================
<div class="include"></div>
<div class="omit"></div>
================================================
FILE: test/resources/filter/style.css
================================================
#dom-node {
width: 100px;
}
.include {
height: 50px;
background-color: blue;
}
.omit {
height: 50px;
background-color: red;
}
================================================
FILE: test/resources/fonts/node.html
================================================
<link
rel="stylesheet"
href="/base/node_modules/@fortawesome/fontawesome-free/css/all.css"
/>
<i class="fab fa-apper"></i>
================================================
FILE: test/resources/fonts/style.css
================================================
#dom-node {
padding: 16px;
width: 100px;
height: 100px;
font-size: 60px;
background-color: white;
}
================================================
FILE: test/resources/fonts/web-fonts/embedded.css
================================================
@font-face {
font-family: 'Font1';
src: url('data:application/x-font-woff2;base64,AAA') format('woff2'),
local(Arial);
font-weight: normal;
font-style: normal;
}
================================================
FILE: test/resources/fonts/web-fonts/empty.html
================================================
<div style="font-family: Font1"></div>
================================================
FILE: test/resources/fonts/web-fonts/remote.css
================================================
@font-face {
font-family: 'Font1';
src: url('https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2')
format('woff2'),
url('https://fonts.gstatic.com/s/raleway/v28/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCFPrcVIT9d0c-dYA.woff')
format('woff'),
local(Arial);
font-weight: normal;
font-style: normal;
}
================================================
FILE: test/resources/fonts/web-fonts/rules-relative.css
================================================
@font-face {
font-family: 'Font1';
src: url('../font1.woff') format('woff');
}
@font-face {
font-family: 'Font2';
src: url('font2.woff2') format('woff2');
}
================================================
FILE: test/resources/fonts/web-fonts/rules-relative.html
================================================
<link
rel="stylesheet"
href="/base/resources/fonts/web-fonts/rules-relative.css"
/>
================================================
FILE: test/resources/fonts/web-fonts/rules.css
================================================
@font-face {
font-family: 'Font1';
src: url('https://fonts.gstatic.com/s/raleway/v28/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCFPrcVIT9d0c-dYA.woff')
format('woff'),
url('https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2')
format('woff2'),
local('Arial');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Font2';
src: url('http://fonts.com/font2.ttf?v1.1.3') format('truetype'),
local('Ubuntu');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Font3';
src: url('data:font/woff2;base64,AAA') format('woff2'), local('Ubuntu');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Font4';
src: local('Comic Sans');
font-weight: normal;
font-style: normal;
}
================================================
FILE: test/resources/fonts/web-fonts/with-query.css
================================================
@font-face {
font-family: 'Font1';
src: url('https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2?v=4.3.0')
format('woff2'),
local(Arial);
font-weight: normal;
font-style: normal;
}
================================================
FILE: test/resources/hash/node.html
================================================
<div class="red" data-stroke="#234"></div>
<div class="green"></div>
<div class="blue"></div>
================================================
FILE: test/resources/hash/style.css
================================================
#dom-node {
width: 100px;
}
.red {
background-color: #ff0000;
}
.green {
background-color: green;
}
.blue {
background-color: #0000ff;
}
.red,
.green,
.blue {
height: 3px;
border: 1px solid lightgrey;
}
================================================
FILE: test/resources/images/loading.html
================================================
<div>
<div>
<img src="/base/test/resources/images/image.jpeg" loading="lazy" />
</div>
<span>
<img src="/base/test/resources/images/image.png" loading="lazy" />
</span>
</div>
================================================
FILE: test/resources/images/node.html
================================================
<div>
<div>
<img src="/base/test/resources/images/image.jpeg" />
</div>
<span>
<img src="/base/test/resources/images/image.png" />
</span>
</div>
================================================
FILE: test/resources/images/style.css
================================================
#dom-node {
width: 300px;
height: 300px;
background-color: white;
}
================================================
FILE: test/resources/input/node.html
================================================
<input id="input" />
================================================
FILE: test/resources/input/style.css
================================================
#dom-node {
width: 400px;
background-color: white;
padding: 1em;
}
input {
height: 100%;
width: 100%;
font-family: monospace;
font-size: 20px;
border: 1px solid grey;
padding: 1em;
}
================================================
FILE: test/resources/page.html
================================================
<style>
* {
box-sizing: border-box;
}
</style>
<style id="style"></style>
<div>
<h2>DOM Node</h2>
<div id="dom-node"></div>
</div>
<div>
<h2>Captured Image</h2>
<canvas id="canvas"></canvas>
</div>
<div>
<h2>Reference Image</h2>
<img id="ref-image" />
</div>
================================================
FILE: test/resources/pixeldata/node.html
================================================
<div id="dom-node">
<div class="pixel top left"></div>
<div class="pixel top center"></div>
<div class="pixel top right"></div>
<div class="pixel middle left"></div>
<div class="pixel middle center"></div>
<div class="pixel middle right"></div>
<div class="pixel bottom left"></div>
<div class="pixel bottom center"></div>
<div class="pixel bottom right"></div>
</div>
================================================
FILE: test/resources/pixeldata/style.css
================================================
.pixel {
width: 10px;
height: 10px;
float: left;
}
.top {
background-color: rgb(255, 0, 0);
}
.middle {
background-color: rgb(0, 255, 0);
}
.bottom {
background-color: rgb(0, 0, 255);
}
.left {
clear: left;
}
.center {
opacity: 0.4;
}
.right {
opacity: 0.2;
}
#dom-node {
width: 30px;
height: 30px;
}
================================================
FILE: test/resources/placeholder/image
================================================
data: image / png; base64, iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAAErpJREFUeF7tnQdsV9UXx0 / dWveWOhGVqbhNGlONUkXrHlVqiuCKGgfuusVRF2hE48IBsWDjQoIjaGKiNpq4UEFxoajg3nvWfI459fGzpa / 9rfveOy / 5p / 75vXfffeee7z3jnlFRWVnZLiKyyiqrSHV1tfz999 / 8X7 + cApmkwBJLLCFtbW3y3Xff6fdXVFdXt19xxRVy9913Cz + eeuqp0qdPH1l++eUzSSD / 6GxS4JdffpGFCxfK9ddfr0LiyCOPlHPPPVcqampq2u + 8805Zd911ZcGCBdLa2iqffvqpnH766VJVVSVLL720VFRUZJNq / tWppkB7e7v88ccfyvfjxo1TDNTX1yvfg4HRo0f / C5BJkybJRhttJDzw888 / y9y5c2XGjBny008 / SUNDg / Tr108qKytTTSz / uGxRAN5 + 9913paWlRXm7rq5O + vfvLyussIIKhPnz58vIkSMXBYiRCBHz++ + /y5w5c3SAddZZR2pra2WLLbaQZZddVpZccslsUdO/NhUU + Ouvv + S3336Tt956S2bOnCmfffaZCoBBgwbJMsssoyaGXYsFSJQav / 76q3z88cdy3nnnyXrrrScnnXSS / l1uueUWGTAVFPSPSCUF2PDh408++UQmTJigfy +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
================================================
FILE: test/resources/placeholder/node.html
================================================
<img src="./bg.jpg" />
================================================
FILE: test/resources/placeholder/style.css
================================================
#dom-node {
width: 100px;
height: 50px;
}
#dom-node img {
width: 100px;
height: 50px;
}
================================================
FILE: test/resources/pseudo/node.html
================================================
<div class="with-before"></div>
<div class="with-after"></div>
<div class="with-both"></div>
================================================
FILE: test/resources/pseudo/style.css
================================================
.with-before::before {
content: 'JUSTBEFORE';
}
.with-after::after {
content: 'JUSTAFTER';
}
.with-both::before {
content: 'BOTHBEFORE';
}
.with-both::after {
content: 'BOTHAFTER';
}
#dom-node {
background-color: white;
font-family: sans-serif;
font-size: 20px;
}
================================================
FILE: test/resources/scale/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAFOklEQVR4Xu3TwQ2EMBRDwaVzGko1FMQW8W5mcv+RPJav53ne34feOedDaUWtApeBVEL3ywIGstyubFnAQDKhD5YFDGS5XdmygIFkQh8sCxjIcruyZQEDyYQ+WBYwkOV2ZcsCBpIJfbAsYCDL7cqWBQwkE/pgWcBAltuVLQsYSCb0wbKAgSy3K1sWMJBM6INlAQNZble2LGAgmdAHywIGstyubFnAQDKhD5YFDGS5XdmygIFkQh8sCxjIcruyZQEDyYQ+WBYwkOV2ZcsCBpIJfbAsYCDL7cqWBQwkE/pgWcBAltuVLQsYSCb0wbKAgSy3K1sWMJBM6INlAQNZble2LGAgmdAHywIGstyubFnAQDKhD5YFDGS5XdmygIFkQh8sCxjIcruyZQEDyYQ+WBYwkOV2ZcsCBpIJfbAsYCDL7cqWBQwkE/pgWcBAltuVLQsYSCb0wbKAgSy3K1sWMJBM6INlAQNZble2LGAgmdAHywIGstyubFnAQDKhD5YFDGS5XdmygIFkQh8sCxjIcruyZQEDyYQ+WBYwkOV2ZcsCBpIJfbAsYCDL7cqWBQwkE/pgWeC67/tdDigbgSJgIEXP7byAgcxXLGARMJCi53ZewEDmKxawCBhI0XM7L2Ag8xULWAQMpOi5nRcwkPmKBSwCBlL03M4LGMh8xQIWAQMpem7nBQxkvmIBi4CBFD238wIGMl+xgEXAQIqe23kBA5mvWMAiYCBFz+28gIHMVyxgETCQoud2XsBA5isWsAgYSNFzOy9gIPMVC1gEDKTouZ0XMJD5igUsAgZS9NzOCxjIfMUCFgEDKXpu5wUMZL5iAYuAgRQ9t/MCBjJfsYBFwECKntt5AQOZr1jAImAgRc/tvICBzFcsYBEwkKLndl7AQOYrFrAIGEjRczsvYCDzFQtYBAyk6LmdFzCQ+YoFLAIGUvTczgsYyHzFAhYBAyl6bucFDGS+YgGLgIEUPbfzAgYyX7GARcBAip7beQEDma9YwCJgIEXP7byAgcxXLGARMJCi53ZewEDmKxawCBhI0XM7L2Ag8xULWAQMpOi5nRcwkPmKBSwCBlL03M4LGMh8xQIWAQMpem7nBQxkvmIBi4CBFD238wIGMl+xgEXAQIqe23kBA5mvWMAiYCBFz+28gIHMVyxgETCQoud2XsBA5isWsAgYSNFzOy9gIPMVC1gEDKTouZ0XMJD5igUsAgZS9NzOCxjIfMUCFgEDKXpu5wUMZL5iAYuAgRQ9t/MCBjJfsYBFwECKntt5AQOZr1jAImAgRc/tvICBzFcsYBEwkKLndl7AQOYrFrAIGEjRczsvYCDzFQtYBAyk6LmdFzCQ+YoFLAIGUvTczgsYyHzFAhYBAyl6bucFDGS+YgGLgIEUPbfzAgYyX7GARcBAip7beQEDma9YwCJgIEXP7byAgcxXLGARMJCi53ZewEDmKxawCBhI0XM7L2Ag8xULWAQMpOi5nRcwkPmKBSwCBlL03M4LGMh8xQIWAQMpem7nBQxkvmIBi4CBFD238wIGMl+xgEXAQIqe23kBA5mvWMAiYCBFz+28gIHMVyxgETCQoud2XsBA5isWsAgYSNFzOy9gIPMVC1gEDKTouZ0XMJD5igUsAgZS9NzOCxjIfMUCFgEDKXpu5wUMZL5iAYuAgRQ9t/MCBjJfsYBFwECKntt5AQOZr1jAImAgRc/tvICBzFcsYBEwkKLndl7AQOYrFrAIGEjRczsvYCDzFQtYBAyk6LmdFzCQ+YoFLAIGUvTczgsYyHzFAhYBAyl6bucF/iqvqB9WL0mLAAAAAElFTkSuQmCC
================================================
FILE: test/resources/scale/node.html
================================================
<div id="child"></div>
================================================
FILE: test/resources/scale/style.css
================================================
#dom-node {
width: 100px;
height: 100px;
background-color: grey;
}
#child {
height: 30px;
width: 30px;
background-color: lightgrey;
}
================================================
FILE: test/resources/scroll/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABa0lEQVR4Xu3VwQkAQAjEQO2/aA+uijzGCkLC4s7cjcsYWEEyLT6IIK0egsR6CCJIzUCMxw8RJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhPE5Hx53K44yoAAAAAElFTkSuQmCC
================================================
FILE: test/resources/scroll/node.html
================================================
<div id="scrolled"></div>
================================================
FILE: test/resources/scroll/style.css
================================================
#dom-node {
width: 50px;
height: 50px;
overflow: auto;
}
#scrolled {
width: 100px;
height: 100px;
background-color: blue;
}
================================================
FILE: test/resources/select/first
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAoCAYAAAAIeF9DAAAAAXNSR0IArs4c6QAAA3JJREFUaEPtmTtIa0EQhv9jEomi8W2VxlLFF1YihIhaiAqx1M4ihQ/QwieCGkSDsZBAQGxVUASx0UIRtDBoaQixFgtRRLDw1d3LLCRE71X2qBlz4p4qCbM7u9+XndkQbWRk5A9++ePz+bRkQaCRkPn5+WRZD/s6RkdHoYSwY38/oRKSRDJoKYYRcn19je3tbTw8PKCqqgpFRUWoqanRjfP5+RkZGRm6x3ENMIyQhoYGnJ+fo6mpCY+PjygtLYXX69XFaWJiAllZWRgfH9c1jjPYMEJsNhuWl5fR2dmJp6cnpKWlwWq16mLV2NgohHIKmZ6exuDgIHJzc1+t9f7+Hn6/H1NTU68+N4SQuro6nJ6eori4GAMDA7i5uUFJSYnYaF9fnyhBq6urKCsrw9raGtxuN46Pj5GTk4Ouri7MzMxgcXERY2NjyM7OxtDQECYnJ3XJ/EwwyfB4PKiursbh4WFMCsmgE392diaEUFz0MYQQKlXl5eUIBALo6OhAb29vrGS1tbVhd3dXAM/Pz8fJyQkikYiIvbq6QktLC9bX1+FwOOByuUByh4eHYbfbP8NY1xgC73Q6EQqFYlJogqgM6oVHR0evTo8hhNAmNE3Dzs4OWltbBdhoDyEhmZmZ2NzcFLBI1t7eHmZnZ9Hc3IyXlxcUFBSI8vYTJeutFFojnYz/yTDULesjIRUVFbEGf3t7i56eHmxtbQlBJIV6D5W4nxBCa4iXQu/fk5EyQmpra0WtpiccDoveQafi4OAA/f39okSsrKz8mJB4KfT6bZmKr4MpUbLihXR3d+Pi4kL0DbPZjPb2dlRWVopTQqUuLy8PCwsLKCws1NUPviOYTgo9b29chhRC116CHO0hdKOam5sD9ZB4IZeXl+JmFQwGxT7r6+uxsbEhmvjS0pLoMVTG9vf3v4Pxt89hmBOid+d3d3cwmUz/fBvpR6XFYkF6erreKVniU1YIC70EJFFCEgD1K1MqIV+hl4CxSSkkAfs01JRJ9QeVocj9gsUmzX/Jv4C11BaVEClMfEFKCB9rqUxKiBQmviAlhI+1VCYlRAoTX5ASwsdaKpMSIoWJL0gJ4WMtlUkJkcLEF6SE8LGWyqSESGHiC1JC+FhLZVJCpDDxBSkhfKylMikhUpj4gpQQPtZSmZQQKUx8QUoIH2upTEqIFCa+oL93XZA4iVClpQAAAABJRU5ErkJggg==
================================================
FILE: test/resources/select/first-option.html
================================================
<select>
<option selected>first</option>
<option>second</option>
<option>third</option>
</select>
================================================
FILE: test/resources/select/second
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAoCAYAAAAIeF9DAAAAAXNSR0IArs4c6QAABKZJREFUaEPtmVkormsUx5eQebpBkpKp7EK4ciEujBlSQgnZGZIIF4aQKcRuy0wRtgvbkEQhN7gxJRluKIlMF8o2tUWGc/qv0yefDGdHer5znlXi87zv8673/1v/tR5Ry87O/ov+51FdXa0migRqAFJVVSVKPp+eR05ODkkgny77yw+UQASCgVQkkHcC2djYoN3dXfL19X3nTv/cLoG8U8ampibq6OigpaWld+4kgXyIgG8BKS4upoyMDDI2NlZ63unpKdXV1VFRUZHS71XCIbe3t5SZmUk/fvzg5P39/am2tpYsLCzo+vqaSktLqbu7m87OzigiIoK+fftGJiYmdH9/zy/c09PD90VGRvK1GhoatLi4SG1tbdTb20vW1tYE4cLCwvg6Nzc3io2N5crf3t6m0NBQgvCGhoZ0dXVFWVlZNDg4SObm5mRnZ8fXPOcQ7FlSUkIuLi40NTX1AAUwvL29aWVlhfPDdYpQCSAQLSkpicbGxkhHR4fF8vT0pJaWFsrPz6f29nZqbGwkfX19ys3NJQcHB+rv7+eXbW1tpZqaGrKysqLw8HAWKD4+nj8HBQVRYmIiDQ0N4ahJMzMz5OHhQWpqamRqakrNzc2kra3N19XX11NaWhp/IQ9U9/7+PqWkpJCrq+uzQCC8l5cXra6uPkCB8AoYzs7OND09reQelQDS19dHUVFRLEpgYCAZGRmRpqYmf0fVQmBULWJiYoKSk5Pp5OSEKx2uqKio4LXZ2Vl2zc7ODsXExNDl5SUDRtjY2PBgBmQAgXsSEhJ4LSQkhGxtben79+/8TFS04nlw5NbW1osz5CkU7AdnPAdDZYY6WlZBQQFX7MXFBdnb23MLcXd359aEMDAwUOrFqDwAGRkZoeDgYKU1VDVOR2gjioD4h4eHXP0Agu8BAQG8DHiYAYWFhWRmZsb3ofIRcGZnZ+erQ/0xFNzzEgyVAbK3t8cV7+joSAsLC1RWVkbLy8t0cHDATkFLwoxBYI6sr69zG9HS0mJ35OXl8drk5CRtbm7S0dERz5lfv37xPEF8+fKF/Pz8eC8AGR8f51n1GAjalLq6OkNITU3lNbQwOO+tU5YCCu552qYeV4tKtKyuri5KT0+nubk5HsBwy/DwMLeK6Ohomp+f58GN2YFZg98DHKoew/vnz59c4T4+PryO6oa7Kisr+TNchLancNNLQBoaGtgta2trvOf5+TkPfEtLyzeBQHRAQTw9cakcELSsuLi4h9MSBu7AwAAPdlT7169faXR0lN8LYqOSnZyceA0HAMwVRaVjyOvq6vIJCsdRtEC0u/Lycq52BIDgHsUfe4qWBSCAgHkGByEA+fj4+F8BeSz8Sz+rhEMUyf/+/ZsFxHHzaWDt5ubm2epDG0Nr0tPTU7rt7u6OoWE/QPiTQAsFWLTFjwyVAvKRLy7qXhKIYGQkEAnkVQX4H1SCafTp6Qj1D6pPf3v5wNcdIvURS4E/O3uKlft/MhsJRDCsEogEIpgCgqUjHSKBCKaAYOlIh0gggikgWDrSIRKIYAoIlo50iAQimAKCpSMdIoEIpoBg6UiHSCCCKSBYOtIhEohgCgiWjnSIYED+BpSYaEcn+mrLAAAAAElFTkSuQmCC
================================================
FILE: test/resources/select/second-option.html
================================================
<select>
<option>first</option>
<option selected>second</option>
<option>third</option>
</select>
================================================
FILE: test/resources/select/style.css
================================================
#dom-node {
width: 100px;
height: 40px;
}
================================================
FILE: test/resources/select/third
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAoCAYAAAAIeF9DAAAAAXNSR0IArs4c6QAAA3xJREFUaEPtmU0odGEUx/+ToXwUWSiNhQUL8pmUjMVYKhQbJbFnwWxM2fgqH5OaRA01U77K18JC2BBpFpNSviY2ymoWksiK5H07T9E1M7y3dMe5vedsZu7tmfuc+//d/zlnupbe3t4/+M/D7XZbuEhgISDj4+Nc8ol7Hi6XCwIk7rJ/vaEAYQSDUjENkLe3N7y+viIpKQkHBwdIT09HeXl5lJyXl5e4ublBXV2dLqnX19dRU1OD7OxsXeuNXmQKIATDbrdjYmJCfTY2NqKoqAgjIyNR+kxNTWFxcRFHR0e6tLNYLNje3tYNUNdFf7DIFEDIGYmJiQgEAv8E8vz8rJyUmpqqSxajgQwMDKCnpwcZGRmf8nl4eMDk5CT6+/s/nTcFkObmZmxsbCAnJwczMzOYnZ1VN/H09ITj42NUVlbC7/cjNzcXS0tL2NrawvLyMrq6upCcnKwcU1hYiL29Pezu7mJoaAhnZ2fo6OjA9PS0YQ4hGIODgygrK8P+/v4HFIJRW1uLk5MTBYTWvYcpgFxdXaGgoADz8/NoaGhQQm5ubsLn8yEvLw+dnZ2qhK2urkJbsurr6xUcj8eDzMxMVFdXIz8/H06nE01NTRgdHcXOzo5hQEh4h8OB09PTDygk/DuM0tJS1Q+17jEFkFgli558AkDhdrsVrFAoFAUkJSUFa2trap3X61XwXl5eVAk8Pz9HSUmJYUBoz0godI6cEQuGaaasWECoBI2NjSmh5+bmMDw8jOvr6yggxcXFygkU5AwqcYeHh+qYhoWEhARDgURCoeOvYJgaiHbK+g5IRUWFquMU1C/6+vpwf38Pq9WKcDgMm81mOBAtFPoeWaa0Xd0UJYsSpmmISk5bWxtaW1s/jb16gVxcXIAcQ+Nze3u7cg71l3iNvVS+KCInLlMCaWlpUb2ARAwGg0pYKlMUCwsLygWxSpbWIbR2ZWUF3d3duL29RVVVleo71Iv0/pHUimfEd9M4hG7+8fERaWlpqu7/JKh33N3dISsr6yeXMeS3pgJiiALMLipABMi3Csj7EI7vQ5g9tHFPh9ULqrjfvWz4fckSfXgpwOblPi9Zfi8bAfJ72sfcWYAIEGYKMEtHHCJAmCnALB1xiABhpgCzdMQhAoSZAszSEYcIEGYKMEtHHCJAmCnALB1xiABhpgCzdMQhAoSZAszSEYcwA/IXhpr/ON427icAAAAASUVORK5CYII=
================================================
FILE: test/resources/select/third-option.html
================================================
<select>
<option>first</option>
<option>second</option>
<option selected>third</option>
</select>
================================================
FILE: test/resources/sheet/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABa0lEQVR4Xu3VwQkAQAjEQO2/aA+uijzGCkLC4t7MjcsYWEEyLT6IIK0egsR6CCJIzUCMxw8RJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhPBWOx51YKJcYAAAAAElFTkSuQmCC
================================================
FILE: test/resources/sheet/node.html
================================================
<link rel="stylesheet" href="/base/test/resources/sheet/sheet.css" />
================================================
FILE: test/resources/sheet/sheet.css
================================================
#dom-node {
background-color: red;
}
================================================
FILE: test/resources/sheet/style.css
================================================
#dom-node {
width: 100px;
height: 100px !important;
}
#root {
border: none !important;
}
================================================
FILE: test/resources/small/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAJCAYAAADEiInQAAAAQElEQVRIie3WsQ0AMQzDQI3+O3IgfREgK0QFi5uAkOEA1Y4AbaIBN4h2GGRMgOaLBriQQQYZc07W+wdDqQtZ9ANeMk+9LbUR+gAAAABJRU5ErkJggg==
================================================
FILE: test/resources/small/image-jpeg
================================================
data:image/jpeg;base64,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
================================================
FILE: test/resources/small/image-jpeg-low
================================================
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wAARCAAJAGQDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDRnurdZ5Fa5tgQxBB1J1I59O30qP7Xa/8AP1a/+DR63aKfLT7P8P8AIfPU/mZhfa7X/n6tf/Bo9H2u1/5+rX/waPW7RRy0+z+9f5D9pV/nZhfa7X/n6tf/AAaPR9rtf+fq1/8ABo9btFHLT7P71/kHtKv87ML7Xa/8/Vr/AODR6Ptdr/z9Wv8A4NHrdoo5afZ/ev8AIPaVf52YX2u1/wCfq1/8Gj0fa7X/AJ+rX/waPW7RRy0+z+9f5B7Sr/OzC+12v/P1a/8Ag0ej7Xa/8/Vr/wCDR63aKOWn2f3r/IPaVf52VNOkjlgZo5I5BuxlLgzDoO5/lRVuilp0Ju3uf//Z
================================================
FILE: test/resources/small/node.html
================================================
<div class="red"></div>
<div class="green"></div>
<div class="blue"></div>
================================================
FILE: test/resources/small/style.css
================================================
#dom-node {
width: 100px;
height: auto;
}
.red {
background-color: red;
}
.green {
background-color: green;
}
.blue {
background-color: blue;
}
.red,
.green,
.blue {
border: 1px solid lightgrey;
height: 3px;
}
================================================
FILE: test/resources/style/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAA+ElEQVR4nO3RwQmAQAADweu/af3nq7AiM5AGNucAAAAAAAAAAAAAz1znXH9Y3fE1dUiHjDqkQ0Yd0iGjDumQUYd0yKhDOmTUIR0y6pAOGXVIh4w6pENGHdIhow7pkFGHdMioQzpk1CEdMuqQDhl1SIeMOqRDRh3SIaMO6ZBRh3TIqEM6ZNQhHTLqkA4ZdUiHjDqkQ0Yd0iGjDumQUYd0yKhDOmTUIR0y6pAOGXVIh4w6pENGHdIhow7pkFGHdMioQzpk1CEdMuqQDhl1SIeMOqRDRh3SIaMO6ZBRh3TIqEM6ZNQhHTLqkA4ZdUiHAAAAAAAAAAAAwEfcyBN1ls/XAjAAAAAASUVORK5CYII=
================================================
FILE: test/resources/style/image-include-style
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAXJJREFUeF7t3LENwDAMxEBp/6FlJFNcQU9AkHio897MTY8xsAVhWvwgBbF6FATrUZCCaAYwnm5IQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcnbl+lIOiFASK8aEUpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgPsePHnRi+YEEAAAAASUVORK5CYII=
================================================
FILE: test/resources/style/node.html
================================================
<div class="child"></div>
================================================
FILE: test/resources/style/style.css
================================================
#dom-node {
width: 100px;
height: 100px;
background-color: blue;
}
.child {
width: 100%;
height: 50%;
background-color: red;
}
================================================
FILE: test/resources/svg-color/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB8CAYAAACi9XTEAAAAAXNSR0IArs4c6QAAAqBJREFUeF7t2DEOAkEQA8G7/z8aJPjABB30jBoRriyvi014nz6nF3hP367LPQEf/xEEHPDxBY5frxcc8PEFjl+vFxzw8QWOX68XHPB/gc/v28eywPvM/sMYv+CALbT/HgG7PPA2AeOTugIDdnngbQLGJ3UFBuzywNsEjE/qCgzY5YG3CRif1BUYsMsDbxMwPqkrMGCXB94mYHxSV2DALg+8TcD4pK7AgF0eeJuA8UldgQG7PPA2AeOTugIDdnngbQLGJ3UFBuzywNsEjE/qCgzY5YG3CRif1BUYsMsDbxMwPqkrMGCXB94mYHxSV2DALg+8TcD4pK7AgF0eeJuA8UldgQG7PPA2AeOTugIDdnngbQLGJ3UFBuzywNsEjE/qCgzY5YG3CRif1BUYsMsDbxMwPqkrMGCXB94mYHxSV2DALg+8TcD4pK7AgF0eeJuA8UldgQG7PPA2AeOTugIDdnngbQLGJ3UFBuzywNsEjE/qCgzY5YG3CRif1BUYsMsDbxMwPqkrMGCXB94mYHxSV2DALg+8TcD4pK7AgF0eeJuA8UldgQG7PPA2AeOTugIDdnngbQLGJ3UFBuzywNsEjE/qCgzY5YG3CRif1BUYsMsDbxMwPqkrMGCXB94mYHxSV2DALg+8TcD4pK7AgF0eeJuA8UldgQG7PPA2AeOTugIDdnngbQLGJ3UFBuzywNsEjE/qCgzY5YG3CRif1BUYsMsDbxMwPqkrMGCXB94mYHxSV2DALg+8TcD4pK7AgF0eeJuA8UldgQG7PPA2AeOTugJxYNf1ajNd4J0e7NzOBQLe6TZuHfB4qp0HA97pNm4d8HiqnQcD3uk2bh3weKqdBwPe6TZuHfB4qp0HA97pNm4d8HiqnQcD3uk2bh3weKqdB7/P2Mh9XPMukgAAAABJRU5ErkJggg==
================================================
FILE: test/resources/svg-color/node.html
================================================
<svg
width="120"
height="120"
viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg"
>
<rect class="rect" x="10" y="10" fill="red" width="100" height="100" />
<foreignObject
x="20"
y="20"
width="120"
height="120"
requiredExtensions="http://www.w3.org/1999/xhtml"
>
<span
style="display: inline-block; width: 32px; height: 32px; background: red"
/>
</foreignObject>
</svg>
================================================
FILE: test/resources/svg-color/style.css
================================================
#dom-node {
width: 120px;
overflow: hidden;
}
.rect {
fill: black;
}
================================================
FILE: test/resources/svg-image/image
================================================
data:image/png;base64,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
================================================
FILE: test/resources/svg-image/node.html
================================================
<svg
width="200"
height="200"
viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg"
>
<image
height="200"
width="200"
href="/base/test/resources/svg-image/svg-image.png"
/>
</svg>
================================================
FILE: test/resources/svg-image/style.css
================================================
#dom-node {
height: 200px;
width: 200px;
background-color: #fff;
}
================================================
FILE: test/resources/svg-ns/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAipJREFUeF7tnbFtQkEYxu7tlhWyQoZiBUYIuxEFKSnSgc/SrzxT81mHzVFAwXFf6756jDFwfAc51jrGnOjEB3m0KMicd0BB5rR4nKQgBRlmYNhxuiEFGWZg2HG6IQUZZmDYcbohBRlmYNhxuiEFGWZg2HG6If81yNv7R1/hb4j7eb2sLd/2FmRDjbUWDlKIPSF+KAXZ6xPTtge5XS/98vhElr+fMAV5Qp7x1IIYVgGzIECeMS2IYRUwCwLkGdOCGFYBsyBAnjEtiGEVMAsC5BnTghhWAbMgQJ4xLYhhFTALAuQZ04IYVgGzIECeMS2IYRUwCwLkGdOCGFYBsyBAnjEtiGEVMAsC5BnTghhWAbMgQJ4xLYhhFTALAuQZ04IYVgGzIECeMS2IYRUwCwLkGdOCGFYBsyBAnjEtiGEVMAsC5BnTghhWAbMgQJ4xLYhhFTALAuQZ04IYVgGzIECeMS2IYRUwCwLkGdOCGFYBsyBAnjEtiGEVMAsC5BnTghhWAbMgQJ4xLYhhFTALAuQZ04IYVgGzIECeMS2IYRUwCwLkGdOCGFYBsyBAnjEtiGEVMAsC5BnTghhWAbMgQJ4xLYhhFTALAuQZ04IYVgGzIECeMS2IYRUwCwLkGdOCGFYBsyBAnjEtiGEVMAsC5BnTghhWAbMgQJ4xLYhhFTALAuQZ04IYVgGzIECeMdWDGIc+E3P7f+GeSZ7xWgtiWAXMggB5xvQ3iAGP+ZqBL6cNY8qpfgubAAAAAElFTkSuQmCC
================================================
FILE: test/resources/svg-ns/node.html
================================================
<div id="root">
<svg xmlns="http://www.w3.org/1999/xhtml" height="94px" width="94px">
<path
d="M10 10 H 90 V 90 H 10 L 10 10"
transform="translate(1,1)"
pointer-events="visibleStroke"
version="1.1"
xmlns="http://www.w3.org/1999/xhtml"
fill="none"
stroke="#456"
stroke-width="4"
shape-rendering="crispEdges"
/>
</svg>
</div>
================================================
FILE: test/resources/svg-ns/style.css
================================================
#dom-node {
width: 100px;
overflow: hidden;
}
#root {
border: 1px solid red;
position: relative;
height: 100px;
}
svg {
position: absolute;
left: 5px;
top: 5px;
}
================================================
FILE: test/resources/svg-rect/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB8CAYAAACi9XTEAAABWElEQVR4nO3RgQnAMAACwey/dLuExRLv4QYQz5EkSZIkSZJu6+FX4rUH4eAp8dqDcPCUeO1BOHhKvPYgHDwlXnsQDp4Srz0IB0+J1x6Eg6fEaw/CwVPitQfh4Cnx2oNw8JR47UE4eEq89iAcPCVeexAOnhKvPQgHT4nXHoSDp8RrD8LBU+K1B+HgKfHag3DwlHjtQTh4Srz2IBw8JV57EA6eEq89CAdPidcehIOnxGsPwsFT4rUH4eAp8dqDcPCUeO1BOHhKvPYgHDwlXnsQDp4Srz0IB0+J1x6Eg6fEaw/CwVPitQfh4Cnx2oNw8JR47UE4eEq89iAcPCVeexAOnhKvPQgHT4nXHoSDp8RrD8LBU+K1B+HgKfHag3DwlHjtQTh4Srz2IBw8JV57EA6eEq89CAdPidcehIOnxGsPwsFT4rUH4eAp8dqD+PhgSZIkSZIkSZIkrfUCfAHrK3AZDRwAAAAASUVORK5CYII=
================================================
FILE: test/resources/svg-rect/node.html
================================================
<svg
width="120"
height="120"
viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="10" y="10" width="100" height="100" />
</svg>
================================================
FILE: test/resources/svg-rect/style.css
================================================
#dom-node {
width: 120px;
overflow: hidden;
}
================================================
FILE: test/resources/svg-use-tag/image
================================================
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAwpJREFUeF7tmPGNzFEYRe90QAWUoAN0QAXogApQAR2gAjpAB3RABzpY+TZvZCLDnkl+bkb2vGT+mdzMfXPOvt/3ZncXyUVcZ0NgN0J2ye5sdnSNN3LpQiHn8xegkPNxcbkTIuRekudJXib5lORxkkdJPid5cWbf57/fzlVCBv6bg2/5JMntJWiEjCzXhgT+JuTtOgnv1kmY0zAn40eSG+uEKGRDGX96ZA3sj0nuJJkTMdBHxAj6muTV2oMnZGMZx4SMhPdJbq5ZMbNj3tuvL2uWjJz57XI/ybzn2ojA4SPr4ZoX35O8XidhTsvv69uS8jTJrXWKPmy0n2v/MYdC5hY1j6eBOyfjmIw9sJkjk3+w8iPQtQGBY0N9rrZ3wWc7QwCkUyNEyLM1J2aW7Af69CjkVNogT4TM4J5TM4+zuX3tl0IA4FMjCjmV2D/OHxMyv87ntV9zm5qr7TyyDof3XH3n5dqQwFX/Otmwyo8iBBRCKBUzCinCJlUKIZSKGYUUYZMqhRBKxYxCirBJlUIIpWJGIUXYpEohhFIxo5AibFKlEEKpmFFIETapUgihVMwopAibVCmEUCpmFFKETaoUQigVMwopwiZVCiGUihmFFGGTKoUQSsWMQoqwSZVCCKViRiFF2KRKIYRSMaOQImxSpRBCqZhRSBE2qVIIoVTMKKQIm1QphFAqZhRShE2qFEIoFTMKKcImVQohlIoZhRRhkyqFEErFjEKKsEmVQgilYkYhRdikSiGEUjGjkCJsUqUQQqmYUUgRNqlSCKFUzCikCJtUKYRQKmYUUoRNqhRCKBUzCinCJlUKIZSKGYUUYZMqhRBKxYxCirBJlUIIpWJGIUXYpEohhFIxo5AibFKlEEKpmFFIETapUgihVMwopAibVCmEUCpmFFKETaoUQigVMwopwiZVCiGUihmFFGGTKoUQSsWMQoqwSZVCCKViRiFF2KRKIYRSMaOQImxSpRBCqZhRSBE2qVIIoVTMKKQIm1QphFAqZhRShE2qFEIoFTMKKcImVb+EkLCZDoGfbnm/rNO8R/AAAAAASUVORK5CYII=
================================================
FILE: test/resources/svg-use-tag/node.html
================================================
<div id="root">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class="icon"
>
<use xlink:href="#icon-home"></use>
</svg>
</div>
</div>
<div style="position: absolute; width: 0; height: 0; overflow: hidden">
<svg
style="position: absolute; width: 0; height: 0; overflow: hidden"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<symbol id="icon-home" viewBox="0 0 26 28">
<title>home</title>
<path
d="M22 15.5v7.5c0 0.547-0.453 1-1 1h-6v-6h-4v6h-6c-0.547 0-1-0.453-1-1v-7.5c0-0.031 0.016-0.063 0.016-0.094l8.984-7.406 8.984 7.406c0.016 0.031 0.016 0.063 0.016 0.094zM25.484 14.422l-0.969 1.156c-0.078 0.094-0.203 0.156-0.328 0.172h-0.047c-0.125 0-0.234-0.031-0.328-0.109l-10.813-9.016-10.813 9.016c-0.109 0.078-0.234 0.125-0.375 0.109-0.125-0.016-0.25-0.078-0.328-0.172l-0.969-1.156c-0.172-0.203-0.141-0.531 0.063-0.703l11.234-9.359c0.656-0.547 1.719-0.547 2.375 0l3.813 3.187v-3.047c0-0.281 0.219-0.5 0.5-0.5h3c0.281 0 0.5 0.219 0.5 0.5v6.375l3.422 2.844c0.203 0.172 0.234 0.5 0.063 0.703z"
></path>
</symbol>
</defs>
</svg>
</div>
================================================
FILE: test/resources/svg-use-tag/style.css
================================================
#dom-node {
width: 100px;
overflow: hidden;
}
#root {
border: 1px solid red;
position: relative;
height: 100px;
}
svg {
width: 100%;
height: 100%;
}
.icon {
display: inline-block;
width: 0.9285714285714285em;
height: 1em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
vertical-align: middle;
top: -1px;
position: relative;
}
================================================
FILE: test/resources/text/node.html
================================================
<div>
SOME TEXT
<div>SOME MORE TEXT</div>
</div>
================================================
FILE: test/resources/text/style.css
================================================
#dom-node {
background-color: white;
font-family: sans-serif;
font-size: 20px;
}
================================================
FILE: test/resources/textarea/node.html
================================================
<textarea id="input"></textarea>
================================================
FILE: test/resources/textarea/style.css
================================================
#dom-node {
width: 400px;
height: 200px;
background-color: white;
padding: 1em;
}
textarea {
height: 100%;
width: 100%;
font-family: monospace;
font-size: 20px;
border: 1px solid grey;
padding: 1em;
}
================================================
FILE: test/resources/video/image
================================================
data:image/png;base64,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
gitextract_eg26j0ob/ ├── .editorconfig ├── .eslintrc ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── backup/ │ │ └── stale.yml │ ├── ci.yml │ ├── codeql.yml │ ├── config/ │ │ ├── label-commands.yml │ │ ├── needs-more-info.yml │ │ └── pr-label-branch-name.yml │ ├── label-commands.yml │ ├── lock.yml │ ├── needs-more-info.yml │ ├── potential-duplicates.yml │ ├── pr-label-branch-name.yml │ ├── pr-label-patch-size.yml │ ├── pr-label-status.yml │ ├── pr-label-title-body.yml │ ├── release.yml │ ├── update-authors.yml │ ├── update-contributors.yml │ ├── update-license.yml │ └── welcome.yml ├── .gitignore ├── .husky/ │ ├── .gitignore │ ├── commit-msg │ └── pre-commit ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── karma.conf.js ├── package.json ├── rollup.config.js ├── src/ │ ├── apply-style.ts │ ├── clone-node.ts │ ├── clone-pseudos.ts │ ├── dataurl.ts │ ├── embed-images.ts │ ├── embed-resources.ts │ ├── embed-webfonts.ts │ ├── index.ts │ ├── mimes.ts │ ├── types.ts │ └── util.ts ├── test/ │ ├── resources/ │ │ ├── bgcolor/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── bigger/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── border/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── canvas/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── css-bg/ │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── custom-element/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── dimensions/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── ext-css/ │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── filter/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── fonts/ │ │ │ ├── node.html │ │ │ ├── style.css │ │ │ └── web-fonts/ │ │ │ ├── embedded.css │ │ │ ├── empty.html │ │ │ ├── remote.css │ │ │ ├── rules-relative.css │ │ │ ├── rules-relative.html │ │ │ ├── rules.css │ │ │ └── with-query.css │ │ ├── hash/ │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── images/ │ │ │ ├── loading.html │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── input/ │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── page.html │ │ ├── pixeldata/ │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── placeholder/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── pseudo/ │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── scale/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── scroll/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── select/ │ │ │ ├── first │ │ │ ├── first-option.html │ │ │ ├── second │ │ │ ├── second-option.html │ │ │ ├── style.css │ │ │ ├── third │ │ │ └── third-option.html │ │ ├── sheet/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ ├── sheet.css │ │ │ └── style.css │ │ ├── small/ │ │ │ ├── image │ │ │ ├── image-jpeg │ │ │ ├── image-jpeg-low │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── style/ │ │ │ ├── image │ │ │ ├── image-include-style │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── svg-color/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── svg-image/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── svg-ns/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── svg-rect/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── svg-use-tag/ │ │ │ ├── image │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── text/ │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── textarea/ │ │ │ ├── node.html │ │ │ └── style.css │ │ ├── video/ │ │ │ ├── flower.webm │ │ │ ├── image │ │ │ ├── image-poster │ │ │ ├── node.html │ │ │ ├── poster.html │ │ │ └── style.css │ │ └── webp/ │ │ ├── node.html │ │ └── style.css │ └── spec/ │ ├── basic.spec.ts │ ├── canvas.sepc.ts │ ├── embed.spec.ts │ ├── helper.ts │ ├── on-image-error-handler.spec.ts │ ├── options.spec.ts │ ├── select.sepc.ts │ ├── setup.ts │ ├── special.spec.ts │ ├── svg.spec.ts │ ├── video.spec.ts │ └── webfont.spec.ts └── tsconfig.json
SYMBOL INDEX (100 symbols across 13 files)
FILE: src/apply-style.ts
function applyStyle (line 3) | function applyStyle<T extends HTMLElement>(
FILE: src/clone-node.ts
function cloneCanvasElement (line 12) | async function cloneCanvasElement(canvas: HTMLCanvasElement) {
function cloneVideoElement (line 20) | async function cloneVideoElement(video: HTMLVideoElement, options: Optio...
function cloneIFrameElement (line 37) | async function cloneIFrameElement(iframe: HTMLIFrameElement, options: Op...
function cloneSingleNode (line 53) | async function cloneSingleNode<T extends HTMLElement>(
function cloneChildren (line 78) | async function cloneChildren<T extends HTMLElement>(
function cloneCSSStyle (line 122) | function cloneCSSStyle<T extends HTMLElement>(
function cloneInputValue (line 166) | function cloneInputValue<T extends HTMLElement>(nativeNode: T, clonedNod...
function cloneSelectValue (line 176) | function cloneSelectValue<T extends HTMLElement>(nativeNode: T, clonedNo...
function decorate (line 189) | function decorate<T extends HTMLElement>(
function ensureSVGSymbols (line 204) | async function ensureSVGSymbols<T extends HTMLElement>(
function cloneNode (line 251) | async function cloneNode<T extends HTMLElement>(
FILE: src/clone-pseudos.ts
type Pseudo (line 4) | type Pseudo = ':before' | ':after'
function formatCSSText (line 6) | function formatCSSText(style: CSSStyleDeclaration) {
function formatCSSProperties (line 11) | function formatCSSProperties(style: CSSStyleDeclaration, options: Option...
function getPseudoElementStyle (line 22) | function getPseudoElementStyle(
function clonePseudoElement (line 36) | function clonePseudoElement<T extends HTMLElement>(
function clonePseudoElements (line 62) | function clonePseudoElements<T extends HTMLElement>(
FILE: src/dataurl.ts
function getContentFromDataUrl (line 3) | function getContentFromDataUrl(dataURL: string) {
function isDataUrl (line 7) | function isDataUrl(url: string) {
function makeDataUrl (line 11) | function makeDataUrl(content: string, mimeType: string) {
function fetchAsDataURL (line 15) | async function fetchAsDataURL<T>(
function getCacheKey (line 42) | function getCacheKey(
function resourceToDataURL (line 61) | async function resourceToDataURL(
FILE: src/embed-images.ts
function embedProp (line 7) | async function embedProp(
function embedBackground (line 25) | async function embedBackground<T extends HTMLElement>(
function embedImageNode (line 37) | async function embedImageNode<T extends HTMLElement | SVGImageElement>(
function embedChildren (line 86) | async function embedChildren<T extends HTMLElement>(
function embedImages (line 95) | async function embedImages<T extends HTMLElement>(
FILE: src/embed-resources.ts
constant URL_REGEX (line 6) | const URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g
constant URL_WITH_FORMAT_REGEX (line 7) | const URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g
constant FONT_SRC_REGEX (line 8) | const FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g
function toRegex (line 10) | function toRegex(url: string): RegExp {
function parseURLs (line 16) | function parseURLs(cssText: string): string[] {
function embed (line 27) | async function embed(
function filterPreferredFontFormat (line 51) | function filterPreferredFontFormat(
function shouldEmbed (line 72) | function shouldEmbed(url: string): boolean {
function embedResources (line 76) | async function embedResources(
FILE: src/embed-webfonts.ts
type Metadata (line 6) | interface Metadata {
function fetchCSS (line 13) | async function fetchCSS(url: string) {
function embedFonts (line 28) | async function embedFonts(data: Metadata, options: Options): Promise<str...
function parseCSS (line 51) | function parseCSS(source: string) {
function getCSSRules (line 104) | async function getCSSRules(
function getWebFontRules (line 185) | function getWebFontRules(cssRules: CSSStyleRule[]): CSSStyleRule[] {
function parseWebFontRules (line 191) | async function parseWebFontRules<T extends HTMLElement>(
function normalizeFontFamily (line 205) | function normalizeFontFamily(font: string) {
function getUsedFonts (line 209) | function getUsedFonts(node: HTMLElement) {
function getWebFontCSS (line 228) | async function getWebFontCSS<T extends HTMLElement>(
function embedWebFonts (line 250) | async function embedWebFonts<T extends HTMLElement>(
FILE: src/index.ts
function toSvg (line 15) | async function toSvg<T extends HTMLElement>(
function toCanvas (line 28) | async function toCanvas<T extends HTMLElement>(
function toPixelData (line 61) | async function toPixelData<T extends HTMLElement>(
function toPng (line 71) | async function toPng<T extends HTMLElement>(
function toJpeg (line 79) | async function toJpeg<T extends HTMLElement>(
function toBlob (line 87) | async function toBlob<T extends HTMLElement>(
function getFontEmbedCSS (line 96) | async function getFontEmbedCSS<T extends HTMLElement>(
FILE: src/mimes.ts
constant WOFF (line 1) | const WOFF = 'application/font-woff'
constant JPEG (line 2) | const JPEG = 'image/jpeg'
function getExtension (line 17) | function getExtension(url: string): string {
function getMimeType (line 22) | function getMimeType(url: string): string {
FILE: src/types.ts
type Options (line 1) | interface Options {
FILE: src/util.ts
function resolveUrl (line 3) | function resolveUrl(url: string, baseUrl: string | null): string {
function delay (line 51) | function delay<T>(ms: number) {
function toArray (line 58) | function toArray<T>(arrayLike: any): T[] {
function getStyleProperties (line 69) | function getStyleProperties(options: Options = {}): string[] {
function px (line 84) | function px(node: HTMLElement, styleProperty: string) {
function getNodeWidth (line 90) | function getNodeWidth(node: HTMLElement) {
function getNodeHeight (line 96) | function getNodeHeight(node: HTMLElement) {
function getImageSize (line 102) | function getImageSize(targetNode: HTMLElement, options: Options = {}) {
function getPixelRatio (line 109) | function getPixelRatio() {
function checkCanvasDimensions (line 135) | function checkCanvasDimensions(canvas: HTMLCanvasElement) {
function canvasToBlob (line 161) | function canvasToBlob(
function createImage (line 199) | function createImage(url: string): Promise<HTMLImageElement> {
function svgToDataURL (line 214) | async function svgToDataURL(svg: SVGElement): Promise<string> {
function nodeToDataURL (line 221) | async function nodeToDataURL(
FILE: test/spec/helper.ts
function getCaptureNode (line 6) | function getCaptureNode() {
function getReferenceImage (line 10) | function getReferenceImage() {
function getCanvasNode (line 14) | function getCanvasNode() {
function getStyleNode (line 18) | function getStyleNode() {
constant BASE_URL (line 22) | const BASE_URL = '/base/test/resources/'
constant ROOT_ID (line 23) | const ROOT_ID = 'test-root'
function clean (line 25) | function clean() {
function setup (line 32) | async function setup() {
function bootstrap (line 41) | async function bootstrap(
function fetchFile (line 65) | async function fetchFile(fileName: string) {
function makeImage (line 71) | function makeImage(src: string) {
function makeCanvas (line 80) | function makeCanvas(
function drawImg (line 103) | function drawImg(
function drawDataUrl (line 114) | async function drawDataUrl(
function check (line 126) | async function check(dataUrl: string) {
function logDataUrl (line 132) | async function logDataUrl(node: HTMLDivElement = getCaptureNode()) {
function renderAndCheck (line 143) | async function renderAndCheck(
function compareToRefImage (line 150) | function compareToRefImage(sourceData: ImageData, threshold = 0.1) {
function getSvgDocument (line 160) | async function getSvgDocument(dataUrl: string): Promise<XMLDocument> {
constant PASS_TEXT_MATCH (line 167) | const PASS_TEXT_MATCH = true
function assertTextRendered (line 169) | function assertTextRendered(lines: string[], options?: Options) {
function recognizeImage (line 178) | async function recognizeImage(node: HTMLDivElement, options?: Options) {
function recognize (line 185) | async function recognize(dataUrl: string) {
FILE: test/spec/options.spec.ts
method filter (line 104) | filter(node) {
method filter (line 121) | filter(node) {
Condensed preview — 165 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (323K chars).
[
{
"path": ".editorconfig",
"chars": 300,
"preview": "# EditorConfig is awesome: http://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\ncharset = utf-8\nend_of"
},
{
"path": ".eslintrc",
"chars": 41,
"preview": "{\n \"extends\": \"@bubkoo/eslint-config\"\n}\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 1227,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: 'bug'\nassignees: ''\n---\n\n<!--- Provide "
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 576,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: 'enhancement'\nassignees: ''\n---\n\n<!-"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 1823,
"preview": "<!--- Provide a general summary of your changes in the Title above -->\n\n### Description\n\n<!--- Describe your changes in "
},
{
"path": ".github/workflows/backup/stale.yml",
"chars": 2747,
"preview": "name: 👻 Stale\non:\n schedule:\n - cron: \"0 0 * * *\"\njobs:\n run:\n runs-on: ubuntu-latest\n steps:\n - uses: bub"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1681,
"preview": "name: 👷 CI\non:\n pull_request: \n pull_request_target:\n push:\n branches:\n - master\n - next\n - next-ma"
},
{
"path": ".github/workflows/codeql.yml",
"chars": 843,
"preview": "name: ⛵️ CodeQL\n\non:\n push:\n branches: [ \"master\" ]\n pull_request:\n branches: [ \"master\" ]\n schedule:\n - cro"
},
{
"path": ".github/workflows/config/label-commands.yml",
"chars": 616,
"preview": "heated:\n lock: true\n lockReason: too heated\n comment: The thread has been temporarily locked.\n\n-heated:\n unlock: tru"
},
{
"path": ".github/workflows/config/needs-more-info.yml",
"chars": 758,
"preview": "# common config\n# -------------\n\n# chenck issue and PR template\ncheckTemplate: true\n# minimum title length required\nmini"
},
{
"path": ".github/workflows/config/pr-label-branch-name.yml",
"chars": 449,
"preview": "# https://github.com/marketplace/actions/PR-labeler\n\nPR(fix): ['fix/*', 'bug/*']\nPR(chore): chore/*\nPR(test): ['testing/"
},
{
"path": ".github/workflows/label-commands.yml",
"chars": 517,
"preview": "name: 👾 Label Commands\non:\n pull_request_target:\n types: [labeled, unlabeled]\n issues:\n types: [labeled, unlabel"
},
{
"path": ".github/workflows/lock.yml",
"chars": 693,
"preview": "name: ⛔️ Lock Threads\non:\n schedule:\n - cron: 0 0 * * *\njobs:\n run:\n runs-on: ubuntu-latest\n steps:\n - u"
},
{
"path": ".github/workflows/needs-more-info.yml",
"chars": 497,
"preview": "name: 🚨 Needs More Info\non:\n pull_request_target:\n types: [opened]\n issues:\n types: [opened]\njobs:\n run:\n ru"
},
{
"path": ".github/workflows/potential-duplicates.yml",
"chars": 403,
"preview": "name: 🆖 Potential Duplicates\non:\n issues:\n types: [opened, edited]\njobs:\n run:\n runs-on: ubuntu-latest\n steps"
},
{
"path": ".github/workflows/pr-label-branch-name.yml",
"chars": 498,
"preview": "name: 🏷️ Label(Branch Name)\non:\n pull_request_target:\n types: [opened]\njobs:\n run:\n runs-on: ubuntu-latest\n s"
},
{
"path": ".github/workflows/pr-label-patch-size.yml",
"chars": 470,
"preview": "name: 🏷️ Label(Patch Size)\non: pull_request_target\njobs:\n run:\n runs-on: ubuntu-latest\n steps:\n - uses: acti"
},
{
"path": ".github/workflows/pr-label-status.yml",
"chars": 503,
"preview": "name: 🏷️ Label(Status)\non:\n pull_request_target:\n types: [opened, closed, edited, reopened, synchronize, ready_for_r"
},
{
"path": ".github/workflows/pr-label-title-body.yml",
"chars": 918,
"preview": "# Github action for automatically adding label or setting assignee when a new\n# Issue or PR is opened. https://github.co"
},
{
"path": ".github/workflows/release.yml",
"chars": 2585,
"preview": "name: 🚀 Release\non:\n push:\n branches:\n - master\n - next\n - next-major\n - alpha\n - beta\njobs"
},
{
"path": ".github/workflows/update-authors.yml",
"chars": 598,
"preview": "name: 🎗 Update Authors\non:\n push:\n branches:\n - master\n - alpha\n - beta\njobs:\n authors:\n runs-on:"
},
{
"path": ".github/workflows/update-contributors.yml",
"chars": 520,
"preview": "name: 🤝 Update Contributors\non:\n schedule:\n - cron: '0 1 * * *'\n push:\n branches:\n - master\n - alpha\n "
},
{
"path": ".github/workflows/update-license.yml",
"chars": 493,
"preview": "name: 🔑 Update License\non:\n schedule:\n - cron: '0 3 1 1 *' # At 03:00 on day-of-month 1 in January.\njobs:\n update:\n"
},
{
"path": ".github/workflows/welcome.yml",
"chars": 2124,
"preview": "name: 👋 Welcome\non:\n pull_request_target:\n types: [opened, closed]\n issues:\n types: [opened]\njobs:\n run:\n ru"
},
{
"path": ".gitignore",
"chars": 122,
"preview": ".DS_Store\n.idea\n.vscode\n.nyc_output\n.DS_Store\n.vscode\nnpm-debug.log\nyarn-error.log\nnode_modules\ndist\nes\nlib\ntest/coverag"
},
{
"path": ".husky/.gitignore",
"chars": 2,
"preview": "_\n"
},
{
"path": ".husky/commit-msg",
"chars": 110,
"preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx @commitlint/cli --extends @bubkoo/commitlint-config --edit \"$1\"\n"
},
{
"path": ".husky/pre-commit",
"chars": 58,
"preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
},
{
"path": ".prettierignore",
"chars": 289,
"preview": "*.md\n*.sh\n*.yml\n*.svg\n*.gif\n.DS_Store\n.gitignore\n.npmignore\n.prettierignore\n.babelrc\n.editorconfig\n.eslintrc\n.eslintigno"
},
{
"path": ".prettierrc",
"chars": 262,
"preview": "{\n \"semi\": false,\n \"singleQuote\": true,\n \"printWidth\": 80,\n \"trailingComma\": \"all\",\n \"proseWrap\": \"never\",\n \"overr"
},
{
"path": "CHANGELOG.md",
"chars": 8890,
"preview": "## [1.11.13](https://github.com/bubkoo/html-to-image/compare/v1.11.12...v1.11.13) (2025-02-14)\n\n\n### Bug Fixes\n\n* **mask"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5221,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "CONTRIBUTING.md",
"chars": 5272,
"preview": "# Contribution Guide\n\nIf you have any comment or advice, please report your [issue](https://github.com/bubkoo/html-to-im"
},
{
"path": "CONTRIBUTORS",
"chars": 2363,
"preview": "Amirata Khodaparast <amiratak88@gmail.com>\nAndrei Gheorghiu <andrei.gheorghiu@kmap.io>\nAndrewN93 <31307316+AndrewN93@use"
},
{
"path": "LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2017-2025 W.Y.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "README.md",
"chars": 12116,
"preview": "<h1 align=\"center\">html-to-image</h1>\n\n<p align=\"center\"><strong>✂️ Generates an image from a DOM node using HTML5 canva"
},
{
"path": "karma.conf.js",
"chars": 2846,
"preview": "/* eslint-disable */\n\nconst cpuCount = require('os').cpus().length\nconst reportsDir = 'test/coverage'\n\nmodule.exports = "
},
{
"path": "package.json",
"chars": 2598,
"preview": "{\n \"name\": \"html-to-image\",\n \"version\": \"1.11.13\",\n \"description\": \"Generates an image from a DOM node using HTML5 ca"
},
{
"path": "rollup.config.js",
"chars": 209,
"preview": "import config from '@bubkoo/rollup-config'\n\nexport default config({\n output: [\n {\n name: 'htmlToImage',\n f"
},
{
"path": "src/apply-style.ts",
"chars": 554,
"preview": "import type { Options } from './types'\n\nexport function applyStyle<T extends HTMLElement>(\n node: T,\n options: Options"
},
{
"path": "src/clone-node.ts",
"chars": 7279,
"preview": "import type { Options } from './types'\nimport { clonePseudoElements } from './clone-pseudos'\nimport {\n createImage,\n t"
},
{
"path": "src/clone-pseudos.ts",
"chars": 1854,
"preview": "import type { Options } from './types'\nimport { uuid, getStyleProperties } from './util'\n\ntype Pseudo = ':before' | ':af"
},
{
"path": "src/dataurl.ts",
"chars": 2648,
"preview": "import { Options } from './types'\n\nfunction getContentFromDataUrl(dataURL: string) {\n return dataURL.split(/,/)[1]\n}\n\ne"
},
{
"path": "src/embed-images.ts",
"chars": 2872,
"preview": "import { Options } from './types'\nimport { embedResources } from './embed-resources'\nimport { toArray, isInstanceOfEleme"
},
{
"path": "src/embed-resources.ts",
"chars": 2604,
"preview": "import { Options } from './types'\nimport { resolveUrl } from './util'\nimport { getMimeType } from './mimes'\nimport { isD"
},
{
"path": "src/embed-webfonts.ts",
"chars": 7491,
"preview": "import type { Options } from './types'\nimport { toArray } from './util'\nimport { fetchAsDataURL } from './dataurl'\nimpor"
},
{
"path": "src/index.ts",
"chars": 2877,
"preview": "import { Options } from './types'\nimport { cloneNode } from './clone-node'\nimport { embedImages } from './embed-images'\n"
},
{
"path": "src/mimes.ts",
"chars": 607,
"preview": "const WOFF = 'application/font-woff'\nconst JPEG = 'image/jpeg'\nconst mimes: { [key: string]: string } = {\n woff: WOFF,\n"
},
{
"path": "src/types.ts",
"chars": 3074,
"preview": "export interface Options {\n /**\n * Width in pixels to be applied to node before rendering.\n */\n width?: number\n /"
},
{
"path": "src/util.ts",
"chars": 6805,
"preview": "import type { Options } from './types'\n\nexport function resolveUrl(url: string, baseUrl: string | null): string {\n // u"
},
{
"path": "test/resources/bgcolor/image",
"chars": 443,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABAUlEQVR4nO3RsQ0AIADDsP7/NJxBhDx4r5ptOz84n9jrIwURJE"
},
{
"path": "test/resources/bgcolor/node.html",
"chars": 25,
"preview": "<div id=\"content\"></div>\n"
},
{
"path": "test/resources/bgcolor/style.css",
"chars": 119,
"preview": "#dom-node {\n height: 100px;\n width: 100px;\n}\n\n#content {\n height: 50px;\n width: 50px;\n background-color: black;\n}\n"
},
{
"path": "test/resources/bigger/image",
"chars": 727,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABjCAYAAABt56XsAAAB10lEQVR4Xu3dgW3CQBAAQegHV0OdVPP0A7IRj54KRvKmggujnL"
},
{
"path": "test/resources/bigger/node.html",
"chars": 117,
"preview": "<div class=\"dom-child-node\">\n <div class=\"red\"></div>\n <div class=\"green\"></div>\n <div class=\"blue\"></div>\n</div>\n"
},
{
"path": "test/resources/bigger/style.css",
"chars": 245,
"preview": "#dom-node {\n width: 100px;\n}\n\n.child-node {\n height: auto;\n}\n\n.red {\n background-color: red;\n}\n\n.green {\n background"
},
{
"path": "test/resources/border/image",
"chars": 786,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAACBElEQVR4Xu3dQQrDQAzAwPT/j25pX2DoYHxQzkZrJJaQU17P87"
},
{
"path": "test/resources/border/node.html",
"chars": 0,
"preview": ""
},
{
"path": "test/resources/border/style.css",
"chars": 171,
"preview": "#dom-node {\n font-size: 16px;\n width: 100px;\n height: 100px;\n background-color: red;\n border-color: black;\n border"
},
{
"path": "test/resources/canvas/image",
"chars": 4331,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAADFlJREFUeF7tnAeMVVUQhocmvaiANOlBVK"
},
{
"path": "test/resources/canvas/node.html",
"chars": 31,
"preview": "<canvas id=\"content\"></canvas>\n"
},
{
"path": "test/resources/canvas/style.css",
"chars": 94,
"preview": "#dom-node {\n height: 100px;\n width: 200px;\n}\n\n#content {\n height: 100px;\n width: 200px;\n}\n"
},
{
"path": "test/resources/css-bg/node.html",
"chars": 36,
"preview": "<div class=\"with-background\"></div>\n"
},
{
"path": "test/resources/css-bg/style.css",
"chars": 211,
"preview": "#dom-node {\n width: 100px;\n height: 100px;\n}\n.with-background {\n background: url(/base/test/resources/images/image.jp"
},
{
"path": "test/resources/custom-element/image",
"chars": 3363,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAACYJJREFUeF7tnQWILcsRhr8Xd3d7UeLuSt"
},
{
"path": "test/resources/custom-element/node.html",
"chars": 262,
"preview": "<math-field\n value=\"S_{\\triangle }=\\frac{ab\\sin \\left(\\gamma \\right)}{2}\"\n scrollingelement=\"[object HTMLHtmlElement]\""
},
{
"path": "test/resources/custom-element/style.css",
"chars": 47,
"preview": "#dom-node {\n width: 200px;\n height: 100px;\n}\n"
},
{
"path": "test/resources/dimensions/image",
"chars": 1858,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAFJklEQVR4Xu3VsRGAQAwEsaf/oqEBINj0RO7A8u9w3efcx0eAwK"
},
{
"path": "test/resources/dimensions/node.html",
"chars": 0,
"preview": ""
},
{
"path": "test/resources/dimensions/style.css",
"chars": 72,
"preview": "#dom-node {\n width: 100px;\n height: 100px;\n background-color: red;\n}\n"
},
{
"path": "test/resources/ext-css/node.html",
"chars": 140,
"preview": "<link\n href=\"http://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600\"\n crossorigin=\"anonymous\"\n "
},
{
"path": "test/resources/ext-css/style.css",
"chars": 74,
"preview": "#dom-node {\n width: 100px;\n height: 100px;\n background-color: black;\n}\n"
},
{
"path": "test/resources/filter/image",
"chars": 587,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABbUlEQVR4Xu3VgQkAIAzEwHb/oRWc4sA4QUj6uDPnTI8xsAVhWj"
},
{
"path": "test/resources/filter/node.html",
"chars": 53,
"preview": "<div class=\"include\"></div>\n<div class=\"omit\"></div>\n"
},
{
"path": "test/resources/filter/style.css",
"chars": 138,
"preview": "#dom-node {\n width: 100px;\n}\n\n.include {\n height: 50px;\n background-color: blue;\n}\n\n.omit {\n height: 50px;\n backgro"
},
{
"path": "test/resources/fonts/node.html",
"chars": 127,
"preview": "<link\n rel=\"stylesheet\"\n href=\"/base/node_modules/@fortawesome/fontawesome-free/css/all.css\"\n/>\n<i class=\"fab fa-apper"
},
{
"path": "test/resources/fonts/style.css",
"chars": 110,
"preview": "#dom-node {\n padding: 16px;\n width: 100px;\n height: 100px;\n font-size: 60px;\n background-color: white;\n}\n"
},
{
"path": "test/resources/fonts/web-fonts/embedded.css",
"chars": 174,
"preview": "@font-face {\n font-family: 'Font1';\n src: url('data:application/x-font-woff2;base64,AAA') format('woff2'),\n local(A"
},
{
"path": "test/resources/fonts/web-fonts/empty.html",
"chars": 39,
"preview": "<div style=\"font-family: Font1\"></div>\n"
},
{
"path": "test/resources/fonts/web-fonts/remote.css",
"chars": 342,
"preview": "@font-face {\n font-family: 'Font1';\n src: url('https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.wof"
},
{
"path": "test/resources/fonts/web-fonts/rules-relative.css",
"chars": 165,
"preview": "@font-face {\n font-family: 'Font1';\n src: url('../font1.woff') format('woff');\n}\n@font-face {\n font-family: 'Font2';\n"
},
{
"path": "test/resources/fonts/web-fonts/rules-relative.html",
"chars": 88,
"preview": "<link\n rel=\"stylesheet\"\n href=\"/base/resources/fonts/web-fonts/rules-relative.css\"\n/>\n"
},
{
"path": "test/resources/fonts/web-fonts/rules.css",
"chars": 791,
"preview": "@font-face {\n font-family: 'Font1';\n src: url('https://fonts.gstatic.com/s/raleway/v28/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVv"
},
{
"path": "test/resources/fonts/web-fonts/with-query.css",
"chars": 221,
"preview": "@font-face {\n font-family: 'Font1';\n src: url('https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.wof"
},
{
"path": "test/resources/hash/node.html",
"chars": 94,
"preview": "<div class=\"red\" data-stroke=\"#234\"></div>\n<div class=\"green\"></div>\n<div class=\"blue\"></div>\n"
},
{
"path": "test/resources/hash/style.css",
"chars": 219,
"preview": "#dom-node {\n width: 100px;\n}\n\n.red {\n background-color: #ff0000;\n}\n\n.green {\n background-color: green;\n}\n\n.blue {\n b"
},
{
"path": "test/resources/images/loading.html",
"chars": 192,
"preview": "<div>\n <div>\n <img src=\"/base/test/resources/images/image.jpeg\" loading=\"lazy\" />\n </div>\n <span>\n <img src=\"/b"
},
{
"path": "test/resources/images/node.html",
"chars": 162,
"preview": "<div>\n <div>\n <img src=\"/base/test/resources/images/image.jpeg\" />\n </div>\n <span>\n <img src=\"/base/test/resour"
},
{
"path": "test/resources/images/style.css",
"chars": 74,
"preview": "#dom-node {\n width: 300px;\n height: 300px;\n background-color: white;\n}\n"
},
{
"path": "test/resources/input/node.html",
"chars": 21,
"preview": "<input id=\"input\" />\n"
},
{
"path": "test/resources/input/style.css",
"chars": 201,
"preview": "#dom-node {\n width: 400px;\n background-color: white;\n padding: 1em;\n}\ninput {\n height: 100%;\n width: 100%;\n font-f"
},
{
"path": "test/resources/page.html",
"chars": 283,
"preview": "<style>\n * {\n box-sizing: border-box;\n }\n</style>\n\n<style id=\"style\"></style>\n\n<div>\n <h2>DOM Node</h2>\n <div id="
},
{
"path": "test/resources/pixeldata/node.html",
"chars": 387,
"preview": "<div id=\"dom-node\">\n <div class=\"pixel top left\"></div>\n <div class=\"pixel top center\"></div>\n <div class=\"pixel top "
},
{
"path": "test/resources/pixeldata/style.css",
"chars": 323,
"preview": ".pixel {\n width: 10px;\n height: 10px;\n float: left;\n}\n.top {\n background-color: rgb(255, 0, 0);\n}\n.middle {\n backgr"
},
{
"path": "test/resources/placeholder/image",
"chars": 6561,
"preview": "data: image / png; base64, iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAAXNSR0IArs4c6QAAErpJREFUeF7tnQdsV9UXx0 / dWve"
},
{
"path": "test/resources/placeholder/node.html",
"chars": 23,
"preview": "<img src=\"./bg.jpg\" />\n"
},
{
"path": "test/resources/placeholder/style.css",
"chars": 97,
"preview": "#dom-node {\n width: 100px;\n height: 50px;\n}\n\n#dom-node img {\n width: 100px;\n height: 50px;\n}\n"
},
{
"path": "test/resources/pseudo/node.html",
"chars": 93,
"preview": "<div class=\"with-before\"></div>\n<div class=\"with-after\"></div>\n<div class=\"with-both\"></div>\n"
},
{
"path": "test/resources/pseudo/style.css",
"chars": 279,
"preview": ".with-before::before {\n content: 'JUSTBEFORE';\n}\n.with-after::after {\n content: 'JUSTAFTER';\n}\n.with-both::before {\n "
},
{
"path": "test/resources/scale/image",
"chars": 1882,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAFOklEQVR4Xu3TwQ2EMBRDwaVzGko1FMQW8W5mcv+RPJav53ne34"
},
{
"path": "test/resources/scale/node.html",
"chars": 23,
"preview": "<div id=\"child\"></div>\n"
},
{
"path": "test/resources/scale/style.css",
"chars": 147,
"preview": "#dom-node {\n width: 100px;\n height: 100px;\n background-color: grey;\n}\n\n#child {\n height: 30px;\n width: 30px;\n back"
},
{
"path": "test/resources/scroll/image",
"chars": 583,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABa0lEQVR4Xu3VwQkAQAjEQO2/aA+uijzGCkLC4s7cjcsYWEEyLT"
},
{
"path": "test/resources/scroll/node.html",
"chars": 26,
"preview": "<div id=\"scrolled\"></div>\n"
},
{
"path": "test/resources/scroll/style.css",
"chars": 137,
"preview": "#dom-node {\n width: 50px;\n height: 50px;\n overflow: auto;\n}\n\n#scrolled {\n width: 100px;\n height: 100px;\n backgroun"
},
{
"path": "test/resources/select/first",
"chars": 1295,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAoCAYAAAAIeF9DAAAAAXNSR0IArs4c6QAAA3JJREFUaEPtmTtIa0EQhv9jEomi8W2Vxl"
},
{
"path": "test/resources/select/first-option.html",
"chars": 104,
"preview": "<select>\n <option selected>first</option>\n <option>second</option>\n <option>third</option>\n</select>\n"
},
{
"path": "test/resources/select/second",
"chars": 1703,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAoCAYAAAAIeF9DAAAAAXNSR0IArs4c6QAABKZJREFUaEPtmVkormsUx5eQebpBkpKp7E"
},
{
"path": "test/resources/select/second-option.html",
"chars": 104,
"preview": "<select>\n <option>first</option>\n <option selected>second</option>\n <option>third</option>\n</select>\n"
},
{
"path": "test/resources/select/style.css",
"chars": 46,
"preview": "#dom-node {\n width: 100px;\n height: 40px;\n}\n"
},
{
"path": "test/resources/select/third",
"chars": 1307,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAoCAYAAAAIeF9DAAAAAXNSR0IArs4c6QAAA3xJREFUaEPtmU0odGEUx/+ToXwUWSiNhQ"
},
{
"path": "test/resources/select/third-option.html",
"chars": 104,
"preview": "<select>\n <option>first</option>\n <option>second</option>\n <option selected>third</option>\n</select>\n"
},
{
"path": "test/resources/sheet/image",
"chars": 583,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABa0lEQVR4Xu3VwQkAQAjEQO2/aA+uijzGCkLC4t7MjcsYWEEyLT"
},
{
"path": "test/resources/sheet/node.html",
"chars": 70,
"preview": "<link rel=\"stylesheet\" href=\"/base/test/resources/sheet/sheet.css\" />\n"
},
{
"path": "test/resources/sheet/sheet.css",
"chars": 39,
"preview": "#dom-node {\n background-color: red;\n}\n"
},
{
"path": "test/resources/sheet/style.css",
"chars": 96,
"preview": "#dom-node {\n width: 100px;\n height: 100px !important;\n}\n\n#root {\n border: none !important;\n}\n"
},
{
"path": "test/resources/small/image",
"chars": 187,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAJCAYAAADEiInQAAAAQElEQVRIie3WsQ0AMQzDQI3+O3IgfREgK0QFi5uAkOEA1Y4Aba"
},
{
"path": "test/resources/small/image-jpeg",
"chars": 2395,
"preview": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA"
},
{
"path": "test/resources/small/image-jpeg-low",
"chars": 1195,
"preview": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV"
},
{
"path": "test/resources/small/node.html",
"chars": 75,
"preview": "<div class=\"red\"></div>\n<div class=\"green\"></div>\n<div class=\"blue\"></div>\n"
},
{
"path": "test/resources/small/style.css",
"chars": 228,
"preview": "#dom-node {\n width: 100px;\n height: auto;\n}\n\n.red {\n background-color: red;\n}\n\n.green {\n background-color: green;\n}\n"
},
{
"path": "test/resources/style/image",
"chars": 430,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAA+ElEQVR4nO3RwQmAQAADweu/af3nq7AiM5AGNucAAAAAAAAAAA"
},
{
"path": "test/resources/style/image-include-style",
"chars": 610,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAXJJREFUeF7t3LENwDAMxEBp/6FlJFNcQU"
},
{
"path": "test/resources/style/node.html",
"chars": 26,
"preview": "<div class=\"child\"></div>\n"
},
{
"path": "test/resources/style/style.css",
"chars": 140,
"preview": "#dom-node {\n width: 100px;\n height: 100px;\n background-color: blue;\n}\n\n.child {\n width: 100%;\n height: 50%;\n backg"
},
{
"path": "test/resources/svg-color/image",
"chars": 1015,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB8CAYAAACi9XTEAAAAAXNSR0IArs4c6QAAAqBJREFUeF7t2DEOAkEQA8G7/z8aJPjABB"
},
{
"path": "test/resources/svg-color/node.html",
"chars": 424,
"preview": "<svg\n width=\"120\"\n height=\"120\"\n viewBox=\"0 0 120 120\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <rect class=\"rect\" x="
},
{
"path": "test/resources/svg-color/style.css",
"chars": 76,
"preview": "#dom-node {\n width: 120px;\n overflow: hidden;\n}\n\n.rect {\n fill: black;\n}\n"
},
{
"path": "test/resources/svg-image/image",
"chars": 8019,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAFyZJREFUeF7tnQnYVVMXx398ytRAhigqJF"
},
{
"path": "test/resources/svg-image/node.html",
"chars": 207,
"preview": "<svg\n width=\"200\"\n height=\"200\"\n viewBox=\"0 0 200 200\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <image\n height=\"20"
},
{
"path": "test/resources/svg-image/style.css",
"chars": 73,
"preview": "#dom-node {\n height: 200px;\n width: 200px;\n background-color: #fff;\n}\n"
},
{
"path": "test/resources/svg-ns/image",
"chars": 855,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAipJREFUeF7tnbFtQkEYxu7tlhWyQoZiBU"
},
{
"path": "test/resources/svg-ns/node.html",
"chars": 390,
"preview": "<div id=\"root\">\n <svg xmlns=\"http://www.w3.org/1999/xhtml\" height=\"94px\" width=\"94px\">\n <path\n d=\"M10 10 H 90 V"
},
{
"path": "test/resources/svg-ns/style.css",
"chars": 181,
"preview": "#dom-node {\n width: 100px;\n overflow: hidden;\n}\n\n#root {\n border: 1px solid red;\n position: relative;\n height: 100p"
},
{
"path": "test/resources/svg-rect/image",
"chars": 558,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB8CAYAAACi9XTEAAABWElEQVR4nO3RgQnAMAACwey/dLuExRLv4QYQz5EkSZIkSZJu6+"
},
{
"path": "test/resources/svg-rect/node.html",
"chars": 154,
"preview": "<svg\n width=\"120\"\n height=\"120\"\n viewBox=\"0 0 120 120\"\n xmlns=\"http://www.w3.org/2000/svg\"\n>\n <rect x=\"10\" y=\"10\" w"
},
{
"path": "test/resources/svg-rect/style.css",
"chars": 50,
"preview": "#dom-node {\n width: 120px;\n overflow: hidden;\n}\n"
},
{
"path": "test/resources/svg-use-tag/image",
"chars": 1155,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAwpJREFUeF7tmPGNzFEYRe90QAWUoAN0QA"
},
{
"path": "test/resources/svg-use-tag/node.html",
"chars": 1242,
"preview": "<div id=\"root\">\n <div>\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlin"
},
{
"path": "test/resources/svg-use-tag/style.css",
"chars": 373,
"preview": "#dom-node {\n width: 100px;\n overflow: hidden;\n}\n\n#root {\n border: 1px solid red;\n position: relative;\n height: 100p"
},
{
"path": "test/resources/text/node.html",
"chars": 53,
"preview": "<div>\n SOME TEXT\n <div>SOME MORE TEXT</div>\n</div>\n"
},
{
"path": "test/resources/text/style.css",
"chars": 87,
"preview": "#dom-node {\n background-color: white;\n font-family: sans-serif;\n font-size: 20px;\n}\n"
},
{
"path": "test/resources/textarea/node.html",
"chars": 33,
"preview": "<textarea id=\"input\"></textarea>\n"
},
{
"path": "test/resources/textarea/style.css",
"chars": 221,
"preview": "#dom-node {\n width: 400px;\n height: 200px;\n background-color: white;\n padding: 1em;\n}\ntextarea {\n height: 100%;\n w"
},
{
"path": "test/resources/video/image",
"chars": 110623,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAACgCAYAAAAy2+FlAAAAAXNSR0IArs4c6QAAIABJREFUeF6EvcmPbem23TW+VVd774g4mf"
},
{
"path": "test/resources/video/image-poster",
"chars": 8371,
"preview": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAACgCAYAAAAy2+FlAAAAAXNSR0IArs4c6QAAGC1JREFUeF7tnfeP3DYThnWu6d0pcApy6b"
},
{
"path": "test/resources/video/node.html",
"chars": 193,
"preview": "<video controls autoplay=\"false\">\n <source src=\"/base/test/resources/video/flower.webm\" type=\"video/webm\" />\n <source "
},
{
"path": "test/resources/video/poster.html",
"chars": 96,
"preview": "<video\n controls\n poster=\"/base/test/resources/video/poster.png\"\n autoplay=\"false\"\n></video>\n"
},
{
"path": "test/resources/video/style.css",
"chars": 89,
"preview": "#dom-node {\n width: 240px;\n height: 160px;\n}\n\nvideo {\n width: 100%;\n height: 100%;\n}\n"
},
{
"path": "test/resources/webp/node.html",
"chars": 66,
"preview": "<div>\n <img src=\"/base/test/resources/webp/image.webp\" />\n</div>\n"
},
{
"path": "test/resources/webp/style.css",
"chars": 74,
"preview": "#dom-node {\n width: 300px;\n height: 300px;\n background-color: white;\n}\n"
},
{
"path": "test/spec/basic.spec.ts",
"chars": 6719,
"preview": "/* eslint-disable promise/no-callback-in-promise */\n\nimport * as htmlToImage from '../../src'\nimport { delay } from '../"
},
{
"path": "test/spec/canvas.sepc.ts",
"chars": 755,
"preview": "/* eslint-disable promise/no-callback-in-promise */\n\nimport './setup'\nimport { bootstrap, renderAndCheck } from './helpe"
},
{
"path": "test/spec/embed.spec.ts",
"chars": 1629,
"preview": "/* eslint-disable promise/no-callback-in-promise */\n\nimport * as embeding from '../../src/embed-resources'\n\ndescribe('em"
},
{
"path": "test/spec/helper.ts",
"chars": 5497,
"preview": "import pixelmatch from 'pixelmatch'\nimport { toPng } from '../../src'\nimport { Options } from '../../src/types'\nimport {"
},
{
"path": "test/spec/on-image-error-handler.spec.ts",
"chars": 907,
"preview": "import { embedImages } from '../../src/embed-images'\n\ndescribe('Error Handling in resourceToDataURL', () => {\n it('shou"
},
{
"path": "test/spec/options.spec.ts",
"chars": 7917,
"preview": "/* eslint-disable promise/no-callback-in-promise */\n\nimport './setup'\nimport {\n bootstrap,\n check,\n drawDataUrl,\n //"
},
{
"path": "test/spec/select.sepc.ts",
"chars": 485,
"preview": "/* eslint-disable promise/no-callback-in-promise */\n\nimport './setup'\nimport { bootstrap, renderAndCheck } from './helpe"
},
{
"path": "test/spec/setup.ts",
"chars": 204,
"preview": "import { clean } from './helper'\n\nbeforeAll(() => {\n process.env.devicePixelRatio = '1'\n jasmine.DEFAULT_TIMEOUT_INTER"
},
{
"path": "test/spec/special.spec.ts",
"chars": 1805,
"preview": "/* eslint-disable promise/no-callback-in-promise */\n\nimport '../spec/setup'\nimport { toPng } from '../../src'\nimport { d"
},
{
"path": "test/spec/svg.spec.ts",
"chars": 1817,
"preview": "/* eslint-disable promise/no-callback-in-promise */\n\nimport '../spec/setup'\nimport { toSvg } from '../../src'\nimport { b"
},
{
"path": "test/spec/video.spec.ts",
"chars": 652,
"preview": "/* eslint-disable promise/no-callback-in-promise */\n\nimport './setup'\nimport { bootstrap, renderAndCheck } from './helpe"
},
{
"path": "test/spec/webfont.spec.ts",
"chars": 4107,
"preview": "import * as htmlToImage from '../../src'\nimport { getSvgDocument } from './helper'\n\ndescribe('font embedding', () => {\n "
},
{
"path": "tsconfig.json",
"chars": 73,
"preview": "{\n \"extends\": \"@bubkoo/tsconfig\",\n \"include\": [\n \"src/**/*.ts\"\n ]\n}\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the bubkoo/html-to-image GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 165 files (300.5 KB), approximately 153.7k tokens, and a symbol index with 100 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.