feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+21
View File
@@ -0,0 +1,21 @@
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.
+357
View File
@@ -0,0 +1,357 @@
<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 diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
import type { Options } from './types';
export declare function applyStyle<T extends HTMLElement>(node: T, options: Options): T;
+20
View File
@@ -0,0 +1,20 @@
export function applyStyle(node, options) {
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) => {
style[key] = manual[key];
});
}
return node;
}
//# sourceMappingURL=apply-style.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"apply-style.js","sourceRoot":"","sources":["../src/apply-style.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CACxB,IAAO,EACP,OAAgB;IAEhB,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IAEtB,IAAI,OAAO,CAAC,eAAe,EAAE;QAC3B,KAAK,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAA;KAChD;IAED,IAAI,OAAO,CAAC,KAAK,EAAE;QACjB,KAAK,CAAC,KAAK,GAAG,GAAG,OAAO,CAAC,KAAK,IAAI,CAAA;KACnC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE;QAClB,KAAK,CAAC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,IAAI,CAAA;KACrC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAA;IAC5B,IAAI,MAAM,IAAI,IAAI,EAAE;QAClB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,GAAQ,EAAE,EAAE;YACvC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAW,CAAA;QACpC,CAAC,CAAC,CAAA;KACH;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
+2
View File
@@ -0,0 +1,2 @@
import type { Options } from './types';
export declare function cloneNode<T extends HTMLElement>(node: T, options: Options, isRoot?: boolean): Promise<T | null>;
+184
View File
@@ -0,0 +1,184 @@
import { clonePseudoElements } from './clone-pseudos';
import { createImage, toArray, isInstanceOfElement, getStyleProperties, } from './util';
import { getMimeType } from './mimes';
import { resourceToDataURL } from './dataurl';
async function cloneCanvasElement(canvas) {
const dataURL = canvas.toDataURL();
if (dataURL === 'data:,') {
return canvas.cloneNode(false);
}
return createImage(dataURL);
}
async function cloneVideoElement(video, options) {
if (video.currentSrc) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = video.clientWidth;
canvas.height = video.clientHeight;
ctx === null || ctx === void 0 ? void 0 : 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, options) {
var _a;
try {
if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
return (await cloneNode(iframe.contentDocument.body, options, true));
}
}
catch (_b) {
// Failed to clone iframe
}
return iframe.cloneNode(false);
}
async function cloneSingleNode(node, options) {
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));
}
const isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === 'SLOT';
const isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === 'SVG';
async function cloneChildren(nativeNode, clonedNode, options) {
var _a, _b;
if (isSVGElement(clonedNode)) {
return clonedNode;
}
let children = [];
if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
children = toArray(nativeNode.assignedNodes());
}
else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) &&
((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
children = toArray(nativeNode.contentDocument.body.childNodes);
}
else {
children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
}
if (children.length === 0 ||
isInstanceOfElement(nativeNode, HTMLVideoElement)) {
return clonedNode;
}
await children.reduce((deferred, child) => deferred
.then(() => cloneNode(child, options))
.then((clonedChild) => {
if (clonedChild) {
clonedNode.appendChild(clonedChild);
}
}), Promise.resolve());
return clonedNode;
}
function cloneCSSStyle(nativeNode, clonedNode, 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(nativeNode, clonedNode) {
if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
clonedNode.innerHTML = nativeNode.value;
}
if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
clonedNode.setAttribute('value', nativeNode.value);
}
}
function cloneSelectValue(nativeNode, clonedNode) {
if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
const clonedSelect = clonedNode;
const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute('value'));
if (selectedOption) {
selectedOption.setAttribute('selected', '');
}
}
}
function decorate(nativeNode, clonedNode, options) {
if (isInstanceOfElement(clonedNode, Element)) {
cloneCSSStyle(nativeNode, clonedNode, options);
clonePseudoElements(nativeNode, clonedNode, options);
cloneInputValue(nativeNode, clonedNode);
cloneSelectValue(nativeNode, clonedNode);
}
return clonedNode;
}
async function ensureSVGSymbols(clone, options) {
const uses = clone.querySelectorAll ? clone.querySelectorAll('use') : [];
if (uses.length === 0) {
return clone;
}
const processedDefs = {};
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);
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(node, options, isRoot) {
if (!isRoot && options.filter && !options.filter(node)) {
return null;
}
return Promise.resolve(node)
.then((clonedNode) => cloneSingleNode(clonedNode, options))
.then((clonedNode) => cloneChildren(node, clonedNode, options))
.then((clonedNode) => decorate(node, clonedNode, options))
.then((clonedNode) => ensureSVGSymbols(clonedNode, options));
}
//# sourceMappingURL=clone-node.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
import type { Options } from './types';
export declare function clonePseudoElements<T extends HTMLElement>(nativeNode: T, clonedNode: T, options: Options): void;
+43
View File
@@ -0,0 +1,43 @@
import { uuid, getStyleProperties } from './util';
function formatCSSText(style) {
const content = style.getPropertyValue('content');
return `${style.cssText} content: '${content.replace(/'|"/g, '')}';`;
}
function formatCSSProperties(style, 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, pseudo, style, options) {
const selector = `.${className}:${pseudo}`;
const cssText = style.cssText
? formatCSSText(style)
: formatCSSProperties(style, options);
return document.createTextNode(`${selector}{${cssText}}`);
}
function clonePseudoElement(nativeNode, clonedNode, pseudo, 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(nativeNode, clonedNode, options) {
clonePseudoElement(nativeNode, clonedNode, ':before', options);
clonePseudoElement(nativeNode, clonedNode, ':after', options);
}
//# sourceMappingURL=clone-pseudos.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"clone-pseudos.js","sourceRoot":"","sources":["../src/clone-pseudos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAA;AAIjD,SAAS,aAAa,CAAC,KAA0B;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAA;IACjD,OAAO,GAAG,KAAK,CAAC,OAAO,cAAc,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAA;AACtE,CAAC;AAED,SAAS,mBAAmB,CAAC,KAA0B,EAAE,OAAgB;IACvE,OAAO,kBAAkB,CAAC,OAAO,CAAC;SAC/B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAA;QAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAEhD,OAAO,GAAG,IAAI,KAAK,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,CAAA;IAC7D,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC;AAED,SAAS,qBAAqB,CAC5B,SAAiB,EACjB,MAAc,EACd,KAA0B,EAC1B,OAAgB;IAEhB,MAAM,QAAQ,GAAG,IAAI,SAAS,IAAI,MAAM,EAAE,CAAA;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO;QAC3B,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC;QACtB,CAAC,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAEvC,OAAO,QAAQ,CAAC,cAAc,CAAC,GAAG,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAA;AAC3D,CAAC;AAED,SAAS,kBAAkB,CACzB,UAAa,EACb,UAAa,EACb,MAAc,EACd,OAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IACzD,MAAM,OAAO,GAAG,KAAK,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAA;IACjD,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,MAAM,EAAE;QACxC,OAAM;KACP;IAED,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;IACxB,IAAI;QACF,UAAU,CAAC,SAAS,GAAG,GAAG,UAAU,CAAC,SAAS,IAAI,SAAS,EAAE,CAAA;KAC9D;IAAC,OAAO,GAAG,EAAE;QACZ,OAAM;KACP;IAED,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;IACpD,YAAY,CAAC,WAAW,CACtB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CACzD,CAAA;IACD,UAAU,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,UAAa,EACb,UAAa,EACb,OAAgB;IAEhB,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;IAC9D,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;AAC/D,CAAC"}
+8
View File
@@ -0,0 +1,8 @@
import { Options } from './types';
export declare function isDataUrl(url: string): boolean;
export declare function makeDataUrl(content: string, mimeType: string): string;
export declare function fetchAsDataURL<T>(url: string, init: RequestInit | undefined, process: (data: {
result: string;
res: Response;
}) => T): Promise<T>;
export declare function resourceToDataURL(resourceUrl: string, contentType: string | undefined, options: Options): Promise<string>;
+76
View File
@@ -0,0 +1,76 @@
function getContentFromDataUrl(dataURL) {
return dataURL.split(/,/)[1];
}
export function isDataUrl(url) {
return url.search(/^(data:)/) !== -1;
}
export function makeDataUrl(content, mimeType) {
return `data:${mimeType};base64,${content}`;
}
export async function fetchAsDataURL(url, init, process) {
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((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onloadend = () => {
try {
resolve(process({ res, result: reader.result }));
}
catch (error) {
reject(error);
}
};
reader.readAsDataURL(blob);
});
}
const cache = {};
function getCacheKey(url, contentType, includeQueryParams) {
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, contentType, 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;
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;
}
//# sourceMappingURL=dataurl.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"dataurl.js","sourceRoot":"","sources":["../src/dataurl.ts"],"names":[],"mappings":"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"}
+2
View File
@@ -0,0 +1,2 @@
import { Options } from './types';
export declare function embedImages<T extends HTMLElement>(clonedNode: T, options: Options): Promise<void>;
+73
View File
@@ -0,0 +1,73 @@
import { embedResources } from './embed-resources';
import { toArray, isInstanceOfElement } from './util';
import { isDataUrl, resourceToDataURL } from './dataurl';
import { getMimeType } from './mimes';
async function embedProp(propName, node, options) {
var _a;
const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.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(clonedNode, 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(clonedNode, 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;
if (image.decode) {
image.decode = resolve;
}
if (image.loading === 'lazy') {
image.loading = 'eager';
}
if (isImageElement) {
clonedNode.srcset = '';
clonedNode.src = dataURL;
}
else {
clonedNode.href.baseVal = dataURL;
}
});
}
async function embedChildren(clonedNode, options) {
const children = toArray(clonedNode.childNodes);
const deferreds = children.map((child) => embedImages(child, options));
await Promise.all(deferreds).then(() => clonedNode);
}
export async function embedImages(clonedNode, options) {
if (isInstanceOfElement(clonedNode, Element)) {
await embedBackground(clonedNode, options);
await embedImageNode(clonedNode, options);
await embedChildren(clonedNode, options);
}
}
//# sourceMappingURL=embed-images.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"embed-images.js","sourceRoot":"","sources":["../src/embed-images.ts"],"names":[],"mappings":"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"}
+5
View File
@@ -0,0 +1,5 @@
import { Options } from './types';
export declare function parseURLs(cssText: string): string[];
export declare function embed(cssText: string, resourceURL: string, baseURL: string | null, options: Options, getContentFromUrl?: (url: string) => Promise<string>): Promise<string>;
export declare function shouldEmbed(url: string): boolean;
export declare function embedResources(cssText: string, baseUrl: string | null, options: Options): Promise<string>;
+66
View File
@@ -0,0 +1,66 @@
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) {
// eslint-disable-next-line no-useless-escape
const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1');
return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, 'g');
}
export function parseURLs(cssText) {
const urls = [];
cssText.replace(URL_REGEX, (raw, quotation, url) => {
urls.push(url);
return raw;
});
return urls.filter((url) => !isDataUrl(url));
}
export async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
try {
const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
const contentType = getMimeType(resourceURL);
let dataURL;
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, { preferredFontFormat }) {
return !preferredFontFormat
? str
: str.replace(FONT_SRC_REGEX, (match) => {
// 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) {
return url.search(URL_REGEX) !== -1;
}
export async function embedResources(cssText, baseUrl, options) {
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));
}
//# sourceMappingURL=embed-resources.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"embed-resources.js","sourceRoot":"","sources":["../src/embed-resources.ts"],"names":[],"mappings":"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"}
+3
View File
@@ -0,0 +1,3 @@
import type { Options } from './types';
export declare function getWebFontCSS<T extends HTMLElement>(node: T, options: Options): Promise<string>;
export declare function embedWebFonts<T extends HTMLElement>(clonedNode: T, options: Options): Promise<void>;
+205
View File
@@ -0,0 +1,205 @@
import { toArray } from './util';
import { fetchAsDataURL } from './dataurl';
import { shouldEmbed, embedResources } from './embed-resources';
const cssFetchCache = {};
async function fetchCSS(url) {
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, options) {
let cssText = data.cssText;
const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
const loadFonts = fontLocs.map(async (loc) => {
let url = loc.replace(regexUrl, '$1');
if (!url.startsWith('https://')) {
url = new URL(url, data.url).href;
}
return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => {
cssText = cssText.replace(loc, `url(${result})`);
return [loc, result];
});
});
return Promise.all(loadFonts).then(() => cssText);
}
function parseCSS(source) {
if (source == null) {
return [];
}
const result = [];
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, options) {
const ret = [];
const deferreds = [];
// First loop inlines imports
styleSheets.forEach((sheet) => {
if ('cssRules' in sheet) {
try {
toArray(sheet.cssRules || []).forEach((item, index) => {
if (item.type === CSSRule.IMPORT_RULE) {
let importIndex = index + 1;
const url = item.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) => {
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(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) {
return cssRules
.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE)
.filter((rule) => shouldEmbed(rule.style.getPropertyValue('src')));
}
async function parseWebFontRules(node, options) {
if (node.ownerDocument == null) {
throw new Error('Provided element is not within a Document');
}
const styleSheets = toArray(node.ownerDocument.styleSheets);
const cssRules = await getCSSRules(styleSheets, options);
return getWebFontRules(cssRules);
}
function normalizeFontFamily(font) {
return font.trim().replace(/["']/g, '');
}
function getUsedFonts(node) {
const fonts = new Set();
function traverse(node) {
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(node, options) {
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(clonedNode, 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);
}
}
}
//# sourceMappingURL=embed-webfonts.js.map
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
import { Options } from './types';
export declare function toSvg<T extends HTMLElement>(node: T, options?: Options): Promise<string>;
export declare function toCanvas<T extends HTMLElement>(node: T, options?: Options): Promise<HTMLCanvasElement>;
export declare function toPixelData<T extends HTMLElement>(node: T, options?: Options): Promise<Uint8ClampedArray>;
export declare function toPng<T extends HTMLElement>(node: T, options?: Options): Promise<string>;
export declare function toJpeg<T extends HTMLElement>(node: T, options?: Options): Promise<string>;
export declare function toBlob<T extends HTMLElement>(node: T, options?: Options): Promise<Blob | null>;
export declare function getFontEmbedCSS<T extends HTMLElement>(node: T, options?: Options): Promise<string>;
+60
View File
@@ -0,0 +1,60 @@
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(node, options = {}) {
const { width, height } = getImageSize(node, options);
const clonedNode = (await cloneNode(node, options, true));
await embedWebFonts(clonedNode, options);
await embedImages(clonedNode, options);
applyStyle(clonedNode, options);
const datauri = await nodeToDataURL(clonedNode, width, height);
return datauri;
}
export async function toCanvas(node, options = {}) {
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(node, options = {}) {
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(node, options = {}) {
const canvas = await toCanvas(node, options);
return canvas.toDataURL();
}
export async function toJpeg(node, options = {}) {
const canvas = await toCanvas(node, options);
return canvas.toDataURL('image/jpeg', options.quality || 1);
}
export async function toBlob(node, options = {}) {
const canvas = await toCanvas(node, options);
const blob = await canvasToBlob(canvas);
return blob;
}
export async function getFontEmbedCSS(node, options = {}) {
return getWebFontCSS(node, options);
}
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
+1
View File
@@ -0,0 +1 @@
export declare function getMimeType(url: string): string;
+24
View File
@@ -0,0 +1,24 @@
const WOFF = 'application/font-woff';
const JPEG = 'image/jpeg';
const mimes = {
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) {
const match = /\.([^./]*?)$/g.exec(url);
return match ? match[1] : '';
}
export function getMimeType(url) {
const extension = getExtension(url).toLowerCase();
return mimes[extension] || '';
}
//# sourceMappingURL=mimes.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"mimes.js","sourceRoot":"","sources":["../src/mimes.ts"],"names":[],"mappings":"AAAA,MAAM,IAAI,GAAG,uBAAuB,CAAA;AACpC,MAAM,IAAI,GAAG,YAAY,CAAA;AACzB,MAAM,KAAK,GAA8B;IACvC,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,2BAA2B;IAChC,GAAG,EAAE,+BAA+B;IACpC,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,IAAI;IACT,IAAI,EAAE,IAAI;IACV,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,eAAe;IACpB,IAAI,EAAE,YAAY;CACnB,CAAA;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACvC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC9B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;IACjD,OAAO,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;AAC/B,CAAC"}
+96
View File
@@ -0,0 +1,96 @@
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;
}
+2
View File
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=types.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
+26
View File
@@ -0,0 +1,26 @@
import type { Options } from './types';
export declare function resolveUrl(url: string, baseUrl: string | null): string;
export declare const uuid: () => string;
export declare function delay<T>(ms: number): (args: T) => Promise<T>;
export declare function toArray<T>(arrayLike: any): T[];
export declare function getStyleProperties(options?: Options): string[];
export declare function getImageSize(targetNode: HTMLElement, options?: Options): {
width: number;
height: number;
};
export declare function getPixelRatio(): number;
export declare function checkCanvasDimensions(canvas: HTMLCanvasElement): void;
export declare function canvasToBlob(canvas: HTMLCanvasElement, options?: Options): Promise<Blob | null>;
export declare function createImage(url: string): Promise<HTMLImageElement>;
export declare function svgToDataURL(svg: SVGElement): Promise<string>;
export declare function nodeToDataURL(node: HTMLElement, width: number, height: number): Promise<string>;
export declare const isInstanceOfElement: <T extends {
new (): Element;
prototype: Element;
} | {
new (): HTMLElement;
prototype: HTMLElement;
} | {
new (): SVGImageElement;
prototype: SVGImageElement;
}>(node: Element | HTMLElement | SVGImageElement, instance: T) => node is T["prototype"];
+193
View File
@@ -0,0 +1,193 @@
export function resolveUrl(url, baseUrl) {
// 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(ms) {
return (args) => new Promise((resolve) => {
setTimeout(() => resolve(args), ms);
});
}
export function toArray(arrayLike) {
const arr = [];
for (let i = 0, l = arrayLike.length; i < l; i++) {
arr.push(arrayLike[i]);
}
return arr;
}
let styleProps = null;
export function getStyleProperties(options = {}) {
if (styleProps) {
return styleProps;
}
if (options.includeStyleProperties) {
styleProps = options.includeStyleProperties;
return styleProps;
}
styleProps = toArray(window.getComputedStyle(document.documentElement));
return styleProps;
}
function px(node, styleProperty) {
const win = node.ownerDocument.defaultView || window;
const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
return val ? parseFloat(val.replace('px', '')) : 0;
}
function getNodeWidth(node) {
const leftBorder = px(node, 'border-left-width');
const rightBorder = px(node, 'border-right-width');
return node.clientWidth + leftBorder + rightBorder;
}
function getNodeHeight(node) {
const topBorder = px(node, 'border-top-width');
const bottomBorder = px(node, 'border-bottom-width');
return node.clientHeight + topBorder + bottomBorder;
}
export function getImageSize(targetNode, 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) {
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, options = {}) {
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) {
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) {
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, width, height) {
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 = (node, instance) => {
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));
};
//# sourceMappingURL=util.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
import type { Options } from './types';
export declare function applyStyle<T extends HTMLElement>(node: T, options: Options): T;
+24
View File
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyStyle = void 0;
function applyStyle(node, options) {
var style = node.style;
if (options.backgroundColor) {
style.backgroundColor = options.backgroundColor;
}
if (options.width) {
style.width = "".concat(options.width, "px");
}
if (options.height) {
style.height = "".concat(options.height, "px");
}
var manual = options.style;
if (manual != null) {
Object.keys(manual).forEach(function (key) {
style[key] = manual[key];
});
}
return node;
}
exports.applyStyle = applyStyle;
//# sourceMappingURL=apply-style.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"apply-style.js","sourceRoot":"","sources":["../src/apply-style.ts"],"names":[],"mappings":";;;AAEA,SAAgB,UAAU,CACxB,IAAO,EACP,OAAgB;IAER,IAAA,KAAK,GAAK,IAAI,MAAT,CAAS;IAEtB,IAAI,OAAO,CAAC,eAAe,EAAE;QAC3B,KAAK,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAA;KAChD;IAED,IAAI,OAAO,CAAC,KAAK,EAAE;QACjB,KAAK,CAAC,KAAK,GAAG,UAAG,OAAO,CAAC,KAAK,OAAI,CAAA;KACnC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE;QAClB,KAAK,CAAC,MAAM,GAAG,UAAG,OAAO,CAAC,MAAM,OAAI,CAAA;KACrC;IAED,IAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAA;IAC5B,IAAI,MAAM,IAAI,IAAI,EAAE;QAClB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAC,GAAQ;YACnC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAW,CAAA;QACpC,CAAC,CAAC,CAAA;KACH;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AA1BD,gCA0BC"}
+2
View File
@@ -0,0 +1,2 @@
import type { Options } from './types';
export declare function cloneNode<T extends HTMLElement>(node: T, options: Options, isRoot?: boolean): Promise<T | null>;
+289
View File
@@ -0,0 +1,289 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cloneNode = void 0;
var clone_pseudos_1 = require("./clone-pseudos");
var util_1 = require("./util");
var mimes_1 = require("./mimes");
var dataurl_1 = require("./dataurl");
function cloneCanvasElement(canvas) {
return __awaiter(this, void 0, void 0, function () {
var dataURL;
return __generator(this, function (_a) {
dataURL = canvas.toDataURL();
if (dataURL === 'data:,') {
return [2 /*return*/, canvas.cloneNode(false)];
}
return [2 /*return*/, (0, util_1.createImage)(dataURL)];
});
});
}
function cloneVideoElement(video, options) {
return __awaiter(this, void 0, void 0, function () {
var canvas, ctx, dataURL_1, poster, contentType, dataURL;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (video.currentSrc) {
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
canvas.width = video.clientWidth;
canvas.height = video.clientHeight;
ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
dataURL_1 = canvas.toDataURL();
return [2 /*return*/, (0, util_1.createImage)(dataURL_1)];
}
poster = video.poster;
contentType = (0, mimes_1.getMimeType)(poster);
return [4 /*yield*/, (0, dataurl_1.resourceToDataURL)(poster, contentType, options)];
case 1:
dataURL = _a.sent();
return [2 /*return*/, (0, util_1.createImage)(dataURL)];
}
});
});
}
function cloneIFrameElement(iframe, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
_c.trys.push([0, 3, , 4]);
if (!((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) return [3 /*break*/, 2];
return [4 /*yield*/, cloneNode(iframe.contentDocument.body, options, true)];
case 1: return [2 /*return*/, (_c.sent())];
case 2: return [3 /*break*/, 4];
case 3:
_b = _c.sent();
return [3 /*break*/, 4];
case 4: return [2 /*return*/, iframe.cloneNode(false)];
}
});
});
}
function cloneSingleNode(node, options) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if ((0, util_1.isInstanceOfElement)(node, HTMLCanvasElement)) {
return [2 /*return*/, cloneCanvasElement(node)];
}
if ((0, util_1.isInstanceOfElement)(node, HTMLVideoElement)) {
return [2 /*return*/, cloneVideoElement(node, options)];
}
if ((0, util_1.isInstanceOfElement)(node, HTMLIFrameElement)) {
return [2 /*return*/, cloneIFrameElement(node, options)];
}
return [2 /*return*/, node.cloneNode(isSVGElement(node))];
});
});
}
var isSlotElement = function (node) {
return node.tagName != null && node.tagName.toUpperCase() === 'SLOT';
};
var isSVGElement = function (node) {
return node.tagName != null && node.tagName.toUpperCase() === 'SVG';
};
function cloneChildren(nativeNode, clonedNode, options) {
var _a, _b;
return __awaiter(this, void 0, void 0, function () {
var children;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (isSVGElement(clonedNode)) {
return [2 /*return*/, clonedNode];
}
children = [];
if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
children = (0, util_1.toArray)(nativeNode.assignedNodes());
}
else if ((0, util_1.isInstanceOfElement)(nativeNode, HTMLIFrameElement) &&
((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
children = (0, util_1.toArray)(nativeNode.contentDocument.body.childNodes);
}
else {
children = (0, util_1.toArray)(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
}
if (children.length === 0 ||
(0, util_1.isInstanceOfElement)(nativeNode, HTMLVideoElement)) {
return [2 /*return*/, clonedNode];
}
return [4 /*yield*/, children.reduce(function (deferred, child) {
return deferred
.then(function () { return cloneNode(child, options); })
.then(function (clonedChild) {
if (clonedChild) {
clonedNode.appendChild(clonedChild);
}
});
}, Promise.resolve())];
case 1:
_c.sent();
return [2 /*return*/, clonedNode];
}
});
});
}
function cloneCSSStyle(nativeNode, clonedNode, options) {
var targetStyle = clonedNode.style;
if (!targetStyle) {
return;
}
var sourceStyle = window.getComputedStyle(nativeNode);
if (sourceStyle.cssText) {
targetStyle.cssText = sourceStyle.cssText;
targetStyle.transformOrigin = sourceStyle.transformOrigin;
}
else {
(0, util_1.getStyleProperties)(options).forEach(function (name) {
var value = sourceStyle.getPropertyValue(name);
if (name === 'font-size' && value.endsWith('px')) {
var reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
value = "".concat(reducedFont, "px");
}
if ((0, util_1.isInstanceOfElement)(nativeNode, HTMLIFrameElement) &&
name === 'display' &&
value === 'inline') {
value = 'block';
}
if (name === 'd' && clonedNode.getAttribute('d')) {
value = "path(".concat(clonedNode.getAttribute('d'), ")");
}
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
});
}
}
function cloneInputValue(nativeNode, clonedNode) {
if ((0, util_1.isInstanceOfElement)(nativeNode, HTMLTextAreaElement)) {
clonedNode.innerHTML = nativeNode.value;
}
if ((0, util_1.isInstanceOfElement)(nativeNode, HTMLInputElement)) {
clonedNode.setAttribute('value', nativeNode.value);
}
}
function cloneSelectValue(nativeNode, clonedNode) {
if ((0, util_1.isInstanceOfElement)(nativeNode, HTMLSelectElement)) {
var clonedSelect = clonedNode;
var selectedOption = Array.from(clonedSelect.children).find(function (child) { return nativeNode.value === child.getAttribute('value'); });
if (selectedOption) {
selectedOption.setAttribute('selected', '');
}
}
}
function decorate(nativeNode, clonedNode, options) {
if ((0, util_1.isInstanceOfElement)(clonedNode, Element)) {
cloneCSSStyle(nativeNode, clonedNode, options);
(0, clone_pseudos_1.clonePseudoElements)(nativeNode, clonedNode, options);
cloneInputValue(nativeNode, clonedNode);
cloneSelectValue(nativeNode, clonedNode);
}
return clonedNode;
}
function ensureSVGSymbols(clone, options) {
return __awaiter(this, void 0, void 0, function () {
var uses, processedDefs, i, use, id, exist, definition, _a, _b, nodes, ns, svg, defs, i;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
uses = clone.querySelectorAll ? clone.querySelectorAll('use') : [];
if (uses.length === 0) {
return [2 /*return*/, clone];
}
processedDefs = {};
i = 0;
_c.label = 1;
case 1:
if (!(i < uses.length)) return [3 /*break*/, 4];
use = uses[i];
id = use.getAttribute('xlink:href');
if (!id) return [3 /*break*/, 3];
exist = clone.querySelector(id);
definition = document.querySelector(id);
if (!(!exist && definition && !processedDefs[id])) return [3 /*break*/, 3];
// eslint-disable-next-line no-await-in-loop
_a = processedDefs;
_b = id;
return [4 /*yield*/, cloneNode(definition, options, true)];
case 2:
// eslint-disable-next-line no-await-in-loop
_a[_b] = (_c.sent());
_c.label = 3;
case 3:
i++;
return [3 /*break*/, 1];
case 4:
nodes = Object.values(processedDefs);
if (nodes.length) {
ns = 'http://www.w3.org/1999/xhtml';
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';
defs = document.createElementNS(ns, 'defs');
svg.appendChild(defs);
for (i = 0; i < nodes.length; i++) {
defs.appendChild(nodes[i]);
}
clone.appendChild(svg);
}
return [2 /*return*/, clone];
}
});
});
}
function cloneNode(node, options, isRoot) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (!isRoot && options.filter && !options.filter(node)) {
return [2 /*return*/, null];
}
return [2 /*return*/, Promise.resolve(node)
.then(function (clonedNode) { return cloneSingleNode(clonedNode, options); })
.then(function (clonedNode) { return cloneChildren(node, clonedNode, options); })
.then(function (clonedNode) { return decorate(node, clonedNode, options); })
.then(function (clonedNode) { return ensureSVGSymbols(clonedNode, options); })];
});
});
}
exports.cloneNode = cloneNode;
//# sourceMappingURL=clone-node.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
import type { Options } from './types';
export declare function clonePseudoElements<T extends HTMLElement>(nativeNode: T, clonedNode: T, options: Options): void;
+47
View File
@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.clonePseudoElements = void 0;
var util_1 = require("./util");
function formatCSSText(style) {
var content = style.getPropertyValue('content');
return "".concat(style.cssText, " content: '").concat(content.replace(/'|"/g, ''), "';");
}
function formatCSSProperties(style, options) {
return (0, util_1.getStyleProperties)(options)
.map(function (name) {
var value = style.getPropertyValue(name);
var priority = style.getPropertyPriority(name);
return "".concat(name, ": ").concat(value).concat(priority ? ' !important' : '', ";");
})
.join(' ');
}
function getPseudoElementStyle(className, pseudo, style, options) {
var selector = ".".concat(className, ":").concat(pseudo);
var cssText = style.cssText
? formatCSSText(style)
: formatCSSProperties(style, options);
return document.createTextNode("".concat(selector, "{").concat(cssText, "}"));
}
function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
var style = window.getComputedStyle(nativeNode, pseudo);
var content = style.getPropertyValue('content');
if (content === '' || content === 'none') {
return;
}
var className = (0, util_1.uuid)();
try {
clonedNode.className = "".concat(clonedNode.className, " ").concat(className);
}
catch (err) {
return;
}
var styleElement = document.createElement('style');
styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
clonedNode.appendChild(styleElement);
}
function clonePseudoElements(nativeNode, clonedNode, options) {
clonePseudoElement(nativeNode, clonedNode, ':before', options);
clonePseudoElement(nativeNode, clonedNode, ':after', options);
}
exports.clonePseudoElements = clonePseudoElements;
//# sourceMappingURL=clone-pseudos.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"clone-pseudos.js","sourceRoot":"","sources":["../src/clone-pseudos.ts"],"names":[],"mappings":";;;AACA,+BAAiD;AAIjD,SAAS,aAAa,CAAC,KAA0B;IAC/C,IAAM,OAAO,GAAG,KAAK,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAA;IACjD,OAAO,UAAG,KAAK,CAAC,OAAO,wBAAc,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,OAAI,CAAA;AACtE,CAAC;AAED,SAAS,mBAAmB,CAAC,KAA0B,EAAE,OAAgB;IACvE,OAAO,IAAA,yBAAkB,EAAC,OAAO,CAAC;SAC/B,GAAG,CAAC,UAAC,IAAI;QACR,IAAM,KAAK,GAAG,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAA;QAC1C,IAAM,QAAQ,GAAG,KAAK,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAEhD,OAAO,UAAG,IAAI,eAAK,KAAK,SAAG,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,MAAG,CAAA;IAC7D,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC;AAED,SAAS,qBAAqB,CAC5B,SAAiB,EACjB,MAAc,EACd,KAA0B,EAC1B,OAAgB;IAEhB,IAAM,QAAQ,GAAG,WAAI,SAAS,cAAI,MAAM,CAAE,CAAA;IAC1C,IAAM,OAAO,GAAG,KAAK,CAAC,OAAO;QAC3B,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC;QACtB,CAAC,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAEvC,OAAO,QAAQ,CAAC,cAAc,CAAC,UAAG,QAAQ,cAAI,OAAO,MAAG,CAAC,CAAA;AAC3D,CAAC;AAED,SAAS,kBAAkB,CACzB,UAAa,EACb,UAAa,EACb,MAAc,EACd,OAAgB;IAEhB,IAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;IACzD,IAAM,OAAO,GAAG,KAAK,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAA;IACjD,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,MAAM,EAAE;QACxC,OAAM;KACP;IAED,IAAM,SAAS,GAAG,IAAA,WAAI,GAAE,CAAA;IACxB,IAAI;QACF,UAAU,CAAC,SAAS,GAAG,UAAG,UAAU,CAAC,SAAS,cAAI,SAAS,CAAE,CAAA;KAC9D;IAAC,OAAO,GAAG,EAAE;QACZ,OAAM;KACP;IAED,IAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;IACpD,YAAY,CAAC,WAAW,CACtB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CACzD,CAAA;IACD,UAAU,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;AACtC,CAAC;AAED,SAAgB,mBAAmB,CACjC,UAAa,EACb,UAAa,EACb,OAAgB;IAEhB,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;IAC9D,kBAAkB,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;AAC/D,CAAC;AAPD,kDAOC"}
+8
View File
@@ -0,0 +1,8 @@
import { Options } from './types';
export declare function isDataUrl(url: string): boolean;
export declare function makeDataUrl(content: string, mimeType: string): string;
export declare function fetchAsDataURL<T>(url: string, init: RequestInit | undefined, process: (data: {
result: string;
res: Response;
}) => T): Promise<T>;
export declare function resourceToDataURL(resourceUrl: string, contentType: string | undefined, options: Options): Promise<string>;
+144
View File
@@ -0,0 +1,144 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resourceToDataURL = exports.fetchAsDataURL = exports.makeDataUrl = exports.isDataUrl = void 0;
function getContentFromDataUrl(dataURL) {
return dataURL.split(/,/)[1];
}
function isDataUrl(url) {
return url.search(/^(data:)/) !== -1;
}
exports.isDataUrl = isDataUrl;
function makeDataUrl(content, mimeType) {
return "data:".concat(mimeType, ";base64,").concat(content);
}
exports.makeDataUrl = makeDataUrl;
function fetchAsDataURL(url, init, process) {
return __awaiter(this, void 0, void 0, function () {
var res, blob;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, fetch(url, init)];
case 1:
res = _a.sent();
if (res.status === 404) {
throw new Error("Resource \"".concat(res.url, "\" not found"));
}
return [4 /*yield*/, res.blob()];
case 2:
blob = _a.sent();
return [2 /*return*/, new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onerror = reject;
reader.onloadend = function () {
try {
resolve(process({ res: res, result: reader.result }));
}
catch (error) {
reject(error);
}
};
reader.readAsDataURL(blob);
})];
}
});
});
}
exports.fetchAsDataURL = fetchAsDataURL;
var cache = {};
function getCacheKey(url, contentType, includeQueryParams) {
var key = url.replace(/\?.*/, '');
if (includeQueryParams) {
key = url;
}
// font resource
if (/ttf|otf|eot|woff2?/i.test(key)) {
key = key.replace(/.*\//, '');
}
return contentType ? "[".concat(contentType, "]").concat(key) : key;
}
function resourceToDataURL(resourceUrl, contentType, options) {
return __awaiter(this, void 0, void 0, function () {
var cacheKey, dataURL, content, error_1, msg;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
if (cache[cacheKey] != null) {
return [2 /*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();
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, fetchAsDataURL(resourceUrl, options.fetchRequestInit, function (_a) {
var res = _a.res, result = _a.result;
if (!contentType) {
// eslint-disable-next-line no-param-reassign
contentType = res.headers.get('Content-Type') || '';
}
return getContentFromDataUrl(result);
})];
case 2:
content = _a.sent();
dataURL = makeDataUrl(content, contentType);
return [3 /*break*/, 4];
case 3:
error_1 = _a.sent();
dataURL = options.imagePlaceholder || '';
msg = "Failed to fetch resource: ".concat(resourceUrl);
if (error_1) {
msg = typeof error_1 === 'string' ? error_1 : error_1.message;
}
if (msg) {
console.warn(msg);
}
return [3 /*break*/, 4];
case 4:
cache[cacheKey] = dataURL;
return [2 /*return*/, dataURL];
}
});
});
}
exports.resourceToDataURL = resourceToDataURL;
//# sourceMappingURL=dataurl.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"dataurl.js","sourceRoot":"","sources":["../src/dataurl.ts"],"names":[],"mappings":"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"}
+2
View File
@@ -0,0 +1,2 @@
import { Options } from './types';
export declare function embedImages<T extends HTMLElement>(clonedNode: T, options: Options): Promise<void>;
+201
View File
@@ -0,0 +1,201 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.embedImages = void 0;
var embed_resources_1 = require("./embed-resources");
var util_1 = require("./util");
var dataurl_1 = require("./dataurl");
var mimes_1 = require("./mimes");
function embedProp(propName, node, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var propValue, cssString;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
if (!propValue) return [3 /*break*/, 2];
return [4 /*yield*/, (0, embed_resources_1.embedResources)(propValue, null, options)];
case 1:
cssString = _b.sent();
node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
return [2 /*return*/, true];
case 2: return [2 /*return*/, false];
}
});
});
}
function embedBackground(clonedNode, options) {
return __awaiter(this, void 0, void 0, function () {
var _a, _b, _c, _d;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
;
return [4 /*yield*/, embedProp('background', clonedNode, options)];
case 1:
_a = (_e.sent());
if (_a) return [3 /*break*/, 3];
return [4 /*yield*/, embedProp('background-image', clonedNode, options)];
case 2:
_a = (_e.sent());
_e.label = 3;
case 3:
_a;
return [4 /*yield*/, embedProp('mask', clonedNode, options)];
case 4:
_d = (_e.sent());
if (_d) return [3 /*break*/, 6];
return [4 /*yield*/, embedProp('-webkit-mask', clonedNode, options)];
case 5:
_d = (_e.sent());
_e.label = 6;
case 6:
_c = _d;
if (_c) return [3 /*break*/, 8];
return [4 /*yield*/, embedProp('mask-image', clonedNode, options)];
case 7:
_c = (_e.sent());
_e.label = 8;
case 8:
_b = _c;
if (_b) return [3 /*break*/, 10];
return [4 /*yield*/, embedProp('-webkit-mask-image', clonedNode, options)];
case 9:
_b = (_e.sent());
_e.label = 10;
case 10:
_b;
return [2 /*return*/];
}
});
});
}
function embedImageNode(clonedNode, options) {
return __awaiter(this, void 0, void 0, function () {
var isImageElement, url, dataURL;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
isImageElement = (0, util_1.isInstanceOfElement)(clonedNode, HTMLImageElement);
if (!(isImageElement && !(0, dataurl_1.isDataUrl)(clonedNode.src)) &&
!((0, util_1.isInstanceOfElement)(clonedNode, SVGImageElement) &&
!(0, dataurl_1.isDataUrl)(clonedNode.href.baseVal))) {
return [2 /*return*/];
}
url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
return [4 /*yield*/, (0, dataurl_1.resourceToDataURL)(url, (0, mimes_1.getMimeType)(url), options)];
case 1:
dataURL = _a.sent();
return [4 /*yield*/, new Promise(function (resolve, reject) {
clonedNode.onload = resolve;
clonedNode.onerror = options.onImageErrorHandler
? function () {
var attributes = [];
for (var _i = 0; _i < arguments.length; _i++) {
attributes[_i] = arguments[_i];
}
try {
resolve(options.onImageErrorHandler.apply(options, attributes));
}
catch (error) {
reject(error);
}
}
: reject;
var image = clonedNode;
if (image.decode) {
image.decode = resolve;
}
if (image.loading === 'lazy') {
image.loading = 'eager';
}
if (isImageElement) {
clonedNode.srcset = '';
clonedNode.src = dataURL;
}
else {
clonedNode.href.baseVal = dataURL;
}
})];
case 2:
_a.sent();
return [2 /*return*/];
}
});
});
}
function embedChildren(clonedNode, options) {
return __awaiter(this, void 0, void 0, function () {
var children, deferreds;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
children = (0, util_1.toArray)(clonedNode.childNodes);
deferreds = children.map(function (child) { return embedImages(child, options); });
return [4 /*yield*/, Promise.all(deferreds).then(function () { return clonedNode; })];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
}
function embedImages(clonedNode, options) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(0, util_1.isInstanceOfElement)(clonedNode, Element)) return [3 /*break*/, 4];
return [4 /*yield*/, embedBackground(clonedNode, options)];
case 1:
_a.sent();
return [4 /*yield*/, embedImageNode(clonedNode, options)];
case 2:
_a.sent();
return [4 /*yield*/, embedChildren(clonedNode, options)];
case 3:
_a.sent();
_a.label = 4;
case 4: return [2 /*return*/];
}
});
});
}
exports.embedImages = embedImages;
//# sourceMappingURL=embed-images.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"embed-images.js","sourceRoot":"","sources":["../src/embed-images.ts"],"names":[],"mappings":"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"}
+5
View File
@@ -0,0 +1,5 @@
import { Options } from './types';
export declare function parseURLs(cssText: string): string[];
export declare function embed(cssText: string, resourceURL: string, baseURL: string | null, options: Options, getContentFromUrl?: (url: string) => Promise<string>): Promise<string>;
export declare function shouldEmbed(url: string): boolean;
export declare function embedResources(cssText: string, baseUrl: string | null, options: Options): Promise<string>;
+127
View File
@@ -0,0 +1,127 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.embedResources = exports.shouldEmbed = exports.embed = exports.parseURLs = void 0;
var util_1 = require("./util");
var mimes_1 = require("./mimes");
var dataurl_1 = require("./dataurl");
var URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
var URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
var FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
function toRegex(url) {
// eslint-disable-next-line no-useless-escape
var escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1');
return new RegExp("(url\\(['\"]?)(".concat(escaped, ")(['\"]?\\))"), 'g');
}
function parseURLs(cssText) {
var urls = [];
cssText.replace(URL_REGEX, function (raw, quotation, url) {
urls.push(url);
return raw;
});
return urls.filter(function (url) { return !(0, dataurl_1.isDataUrl)(url); });
}
exports.parseURLs = parseURLs;
function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
return __awaiter(this, void 0, void 0, function () {
var resolvedURL, contentType, dataURL, content, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 5, , 6]);
resolvedURL = baseURL ? (0, util_1.resolveUrl)(resourceURL, baseURL) : resourceURL;
contentType = (0, mimes_1.getMimeType)(resourceURL);
dataURL = void 0;
if (!getContentFromUrl) return [3 /*break*/, 2];
return [4 /*yield*/, getContentFromUrl(resolvedURL)];
case 1:
content = _a.sent();
dataURL = (0, dataurl_1.makeDataUrl)(content, contentType);
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, (0, dataurl_1.resourceToDataURL)(resolvedURL, contentType, options)];
case 3:
dataURL = _a.sent();
_a.label = 4;
case 4: return [2 /*return*/, cssText.replace(toRegex(resourceURL), "$1".concat(dataURL, "$3"))];
case 5:
error_1 = _a.sent();
return [3 /*break*/, 6];
case 6: return [2 /*return*/, cssText];
}
});
});
}
exports.embed = embed;
function filterPreferredFontFormat(str, _a) {
var preferredFontFormat = _a.preferredFontFormat;
return !preferredFontFormat
? str
: str.replace(FONT_SRC_REGEX, function (match) {
// eslint-disable-next-line no-constant-condition
while (true) {
var _a = URL_WITH_FORMAT_REGEX.exec(match) || [], src = _a[0], format = _a[2];
if (!format) {
return '';
}
if (format === preferredFontFormat) {
return "src: ".concat(src, ";");
}
}
});
}
function shouldEmbed(url) {
return url.search(URL_REGEX) !== -1;
}
exports.shouldEmbed = shouldEmbed;
function embedResources(cssText, baseUrl, options) {
return __awaiter(this, void 0, void 0, function () {
var filteredCSSText, urls;
return __generator(this, function (_a) {
if (!shouldEmbed(cssText)) {
return [2 /*return*/, cssText];
}
filteredCSSText = filterPreferredFontFormat(cssText, options);
urls = parseURLs(filteredCSSText);
return [2 /*return*/, urls.reduce(function (deferred, url) {
return deferred.then(function (css) { return embed(css, url, baseUrl, options); });
}, Promise.resolve(filteredCSSText))];
});
});
}
exports.embedResources = embedResources;
//# sourceMappingURL=embed-resources.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"embed-resources.js","sourceRoot":"","sources":["../src/embed-resources.ts"],"names":[],"mappings":"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"}
+3
View File
@@ -0,0 +1,3 @@
import type { Options } from './types';
export declare function getWebFontCSS<T extends HTMLElement>(node: T, options: Options): Promise<string>;
export declare function embedWebFonts<T extends HTMLElement>(clonedNode: T, options: Options): Promise<void>;
+320
View File
@@ -0,0 +1,320 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.embedWebFonts = exports.getWebFontCSS = void 0;
var util_1 = require("./util");
var dataurl_1 = require("./dataurl");
var embed_resources_1 = require("./embed-resources");
var cssFetchCache = {};
function fetchCSS(url) {
return __awaiter(this, void 0, void 0, function () {
var cache, res, cssText;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
cache = cssFetchCache[url];
if (cache != null) {
return [2 /*return*/, cache];
}
return [4 /*yield*/, fetch(url)];
case 1:
res = _a.sent();
return [4 /*yield*/, res.text()];
case 2:
cssText = _a.sent();
cache = { url: url, cssText: cssText };
cssFetchCache[url] = cache;
return [2 /*return*/, cache];
}
});
});
}
function embedFonts(data, options) {
return __awaiter(this, void 0, void 0, function () {
var cssText, regexUrl, fontLocs, loadFonts;
var _this = this;
return __generator(this, function (_a) {
cssText = data.cssText;
regexUrl = /url\(["']?([^"')]+)["']?\)/g;
fontLocs = cssText.match(/url\([^)]+\)/g) || [];
loadFonts = fontLocs.map(function (loc) { return __awaiter(_this, void 0, void 0, function () {
var url;
return __generator(this, function (_a) {
url = loc.replace(regexUrl, '$1');
if (!url.startsWith('https://')) {
url = new URL(url, data.url).href;
}
return [2 /*return*/, (0, dataurl_1.fetchAsDataURL)(url, options.fetchRequestInit, function (_a) {
var result = _a.result;
cssText = cssText.replace(loc, "url(".concat(result, ")"));
return [loc, result];
})];
});
}); });
return [2 /*return*/, Promise.all(loadFonts).then(function () { return cssText; })];
});
});
}
function parseCSS(source) {
if (source == null) {
return [];
}
var result = [];
var commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
// strip out comments
var cssText = source.replace(commentsRegex, '');
// eslint-disable-next-line prefer-regex-literals
var keyframesRegex = new RegExp('((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})', 'gi');
// eslint-disable-next-line no-constant-condition
while (true) {
var matches = keyframesRegex.exec(cssText);
if (matches === null) {
break;
}
result.push(matches[0]);
}
cssText = cssText.replace(keyframesRegex, '');
var importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
// to match css & media queries together
var combinedCSSRegex = '((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]' +
'*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})';
// unified regex
var unifiedRegex = new RegExp(combinedCSSRegex, 'gi');
// eslint-disable-next-line no-constant-condition
while (true) {
var 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;
}
function getCSSRules(styleSheets, options) {
return __awaiter(this, void 0, void 0, function () {
var ret, deferreds;
return __generator(this, function (_a) {
ret = [];
deferreds = [];
// First loop inlines imports
styleSheets.forEach(function (sheet) {
if ('cssRules' in sheet) {
try {
(0, util_1.toArray)(sheet.cssRules || []).forEach(function (item, index) {
if (item.type === CSSRule.IMPORT_RULE) {
var importIndex_1 = index + 1;
var url = item.href;
var deferred = fetchCSS(url)
.then(function (metadata) { return embedFonts(metadata, options); })
.then(function (cssText) {
return parseCSS(cssText).forEach(function (rule) {
try {
sheet.insertRule(rule, rule.startsWith('@import')
? (importIndex_1 += 1)
: sheet.cssRules.length);
}
catch (error) {
console.error('Error inserting rule from remote css', {
rule: rule,
error: error,
});
}
});
})
.catch(function (e) {
console.error('Error loading remote css', e.toString());
});
deferreds.push(deferred);
}
});
}
catch (e) {
var inline_1 = styleSheets.find(function (a) { return a.href == null; }) || document.styleSheets[0];
if (sheet.href != null) {
deferreds.push(fetchCSS(sheet.href)
.then(function (metadata) { return embedFonts(metadata, options); })
.then(function (cssText) {
return parseCSS(cssText).forEach(function (rule) {
inline_1.insertRule(rule, inline_1.cssRules.length);
});
})
.catch(function (err) {
console.error('Error loading remote stylesheet', err);
}));
}
console.error('Error inlining remote css file', e);
}
}
});
return [2 /*return*/, Promise.all(deferreds).then(function () {
// Second loop parses rules
styleSheets.forEach(function (sheet) {
if ('cssRules' in sheet) {
try {
(0, util_1.toArray)(sheet.cssRules || []).forEach(function (item) {
ret.push(item);
});
}
catch (e) {
console.error("Error while reading CSS rules from ".concat(sheet.href), e);
}
}
});
return ret;
})];
});
});
}
function getWebFontRules(cssRules) {
return cssRules
.filter(function (rule) { return rule.type === CSSRule.FONT_FACE_RULE; })
.filter(function (rule) { return (0, embed_resources_1.shouldEmbed)(rule.style.getPropertyValue('src')); });
}
function parseWebFontRules(node, options) {
return __awaiter(this, void 0, void 0, function () {
var styleSheets, cssRules;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (node.ownerDocument == null) {
throw new Error('Provided element is not within a Document');
}
styleSheets = (0, util_1.toArray)(node.ownerDocument.styleSheets);
return [4 /*yield*/, getCSSRules(styleSheets, options)];
case 1:
cssRules = _a.sent();
return [2 /*return*/, getWebFontRules(cssRules)];
}
});
});
}
function normalizeFontFamily(font) {
return font.trim().replace(/["']/g, '');
}
function getUsedFonts(node) {
var fonts = new Set();
function traverse(node) {
var fontFamily = node.style.fontFamily || getComputedStyle(node).fontFamily;
fontFamily.split(',').forEach(function (font) {
fonts.add(normalizeFontFamily(font));
});
Array.from(node.children).forEach(function (child) {
if (child instanceof HTMLElement) {
traverse(child);
}
});
}
traverse(node);
return fonts;
}
function getWebFontCSS(node, options) {
return __awaiter(this, void 0, void 0, function () {
var rules, usedFonts, cssTexts;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, parseWebFontRules(node, options)];
case 1:
rules = _a.sent();
usedFonts = getUsedFonts(node);
return [4 /*yield*/, Promise.all(rules
.filter(function (rule) {
return usedFonts.has(normalizeFontFamily(rule.style.fontFamily));
})
.map(function (rule) {
var baseUrl = rule.parentStyleSheet
? rule.parentStyleSheet.href
: null;
return (0, embed_resources_1.embedResources)(rule.cssText, baseUrl, options);
}))];
case 2:
cssTexts = _a.sent();
return [2 /*return*/, cssTexts.join('\n')];
}
});
});
}
exports.getWebFontCSS = getWebFontCSS;
function embedWebFonts(clonedNode, options) {
return __awaiter(this, void 0, void 0, function () {
var cssText, _a, _b, styleNode, sytleContent;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!(options.fontEmbedCSS != null)) return [3 /*break*/, 1];
_a = options.fontEmbedCSS;
return [3 /*break*/, 5];
case 1:
if (!options.skipFonts) return [3 /*break*/, 2];
_b = null;
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, getWebFontCSS(clonedNode, options)];
case 3:
_b = _c.sent();
_c.label = 4;
case 4:
_a = _b;
_c.label = 5;
case 5:
cssText = _a;
if (cssText) {
styleNode = document.createElement('style');
sytleContent = document.createTextNode(cssText);
styleNode.appendChild(sytleContent);
if (clonedNode.firstChild) {
clonedNode.insertBefore(styleNode, clonedNode.firstChild);
}
else {
clonedNode.appendChild(styleNode);
}
}
return [2 /*return*/];
}
});
});
}
exports.embedWebFonts = embedWebFonts;
//# sourceMappingURL=embed-webfonts.js.map
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
import { Options } from './types';
export declare function toSvg<T extends HTMLElement>(node: T, options?: Options): Promise<string>;
export declare function toCanvas<T extends HTMLElement>(node: T, options?: Options): Promise<HTMLCanvasElement>;
export declare function toPixelData<T extends HTMLElement>(node: T, options?: Options): Promise<Uint8ClampedArray>;
export declare function toPng<T extends HTMLElement>(node: T, options?: Options): Promise<string>;
export declare function toJpeg<T extends HTMLElement>(node: T, options?: Options): Promise<string>;
export declare function toBlob<T extends HTMLElement>(node: T, options?: Options): Promise<Blob | null>;
export declare function getFontEmbedCSS<T extends HTMLElement>(node: T, options?: Options): Promise<string>;
+184
View File
@@ -0,0 +1,184 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFontEmbedCSS = exports.toBlob = exports.toJpeg = exports.toPng = exports.toPixelData = exports.toCanvas = exports.toSvg = void 0;
var clone_node_1 = require("./clone-node");
var embed_images_1 = require("./embed-images");
var apply_style_1 = require("./apply-style");
var embed_webfonts_1 = require("./embed-webfonts");
var util_1 = require("./util");
function toSvg(node, options) {
if (options === void 0) { options = {}; }
return __awaiter(this, void 0, void 0, function () {
var _a, width, height, clonedNode, datauri;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = (0, util_1.getImageSize)(node, options), width = _a.width, height = _a.height;
return [4 /*yield*/, (0, clone_node_1.cloneNode)(node, options, true)];
case 1:
clonedNode = (_b.sent());
return [4 /*yield*/, (0, embed_webfonts_1.embedWebFonts)(clonedNode, options)];
case 2:
_b.sent();
return [4 /*yield*/, (0, embed_images_1.embedImages)(clonedNode, options)];
case 3:
_b.sent();
(0, apply_style_1.applyStyle)(clonedNode, options);
return [4 /*yield*/, (0, util_1.nodeToDataURL)(clonedNode, width, height)];
case 4:
datauri = _b.sent();
return [2 /*return*/, datauri];
}
});
});
}
exports.toSvg = toSvg;
function toCanvas(node, options) {
if (options === void 0) { options = {}; }
return __awaiter(this, void 0, void 0, function () {
var _a, width, height, svg, img, canvas, context, ratio, canvasWidth, canvasHeight;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = (0, util_1.getImageSize)(node, options), width = _a.width, height = _a.height;
return [4 /*yield*/, toSvg(node, options)];
case 1:
svg = _b.sent();
return [4 /*yield*/, (0, util_1.createImage)(svg)];
case 2:
img = _b.sent();
canvas = document.createElement('canvas');
context = canvas.getContext('2d');
ratio = options.pixelRatio || (0, util_1.getPixelRatio)();
canvasWidth = options.canvasWidth || width;
canvasHeight = options.canvasHeight || height;
canvas.width = canvasWidth * ratio;
canvas.height = canvasHeight * ratio;
if (!options.skipAutoScale) {
(0, util_1.checkCanvasDimensions)(canvas);
}
canvas.style.width = "".concat(canvasWidth);
canvas.style.height = "".concat(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 [2 /*return*/, canvas];
}
});
});
}
exports.toCanvas = toCanvas;
function toPixelData(node, options) {
if (options === void 0) { options = {}; }
return __awaiter(this, void 0, void 0, function () {
var _a, width, height, canvas, ctx;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = (0, util_1.getImageSize)(node, options), width = _a.width, height = _a.height;
return [4 /*yield*/, toCanvas(node, options)];
case 1:
canvas = _b.sent();
ctx = canvas.getContext('2d');
return [2 /*return*/, ctx.getImageData(0, 0, width, height).data];
}
});
});
}
exports.toPixelData = toPixelData;
function toPng(node, options) {
if (options === void 0) { options = {}; }
return __awaiter(this, void 0, void 0, function () {
var canvas;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, toCanvas(node, options)];
case 1:
canvas = _a.sent();
return [2 /*return*/, canvas.toDataURL()];
}
});
});
}
exports.toPng = toPng;
function toJpeg(node, options) {
if (options === void 0) { options = {}; }
return __awaiter(this, void 0, void 0, function () {
var canvas;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, toCanvas(node, options)];
case 1:
canvas = _a.sent();
return [2 /*return*/, canvas.toDataURL('image/jpeg', options.quality || 1)];
}
});
});
}
exports.toJpeg = toJpeg;
function toBlob(node, options) {
if (options === void 0) { options = {}; }
return __awaiter(this, void 0, void 0, function () {
var canvas, blob;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, toCanvas(node, options)];
case 1:
canvas = _a.sent();
return [4 /*yield*/, (0, util_1.canvasToBlob)(canvas)];
case 2:
blob = _a.sent();
return [2 /*return*/, blob];
}
});
});
}
exports.toBlob = toBlob;
function getFontEmbedCSS(node, options) {
if (options === void 0) { options = {}; }
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, (0, embed_webfonts_1.getWebFontCSS)(node, options)];
});
});
}
exports.getFontEmbedCSS = getFontEmbedCSS;
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
+1
View File
@@ -0,0 +1 @@
export declare function getMimeType(url: string): string;
+28
View File
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMimeType = void 0;
var WOFF = 'application/font-woff';
var JPEG = 'image/jpeg';
var mimes = {
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) {
var match = /\.([^./]*?)$/g.exec(url);
return match ? match[1] : '';
}
function getMimeType(url) {
var extension = getExtension(url).toLowerCase();
return mimes[extension] || '';
}
exports.getMimeType = getMimeType;
//# sourceMappingURL=mimes.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"mimes.js","sourceRoot":"","sources":["../src/mimes.ts"],"names":[],"mappings":";;;AAAA,IAAM,IAAI,GAAG,uBAAuB,CAAA;AACpC,IAAM,IAAI,GAAG,YAAY,CAAA;AACzB,IAAM,KAAK,GAA8B;IACvC,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,2BAA2B;IAChC,GAAG,EAAE,+BAA+B;IACpC,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,IAAI;IACT,IAAI,EAAE,IAAI;IACV,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,eAAe;IACpB,IAAI,EAAE,YAAY;CACnB,CAAA;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACvC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC9B,CAAC;AAED,SAAgB,WAAW,CAAC,GAAW;IACrC,IAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;IACjD,OAAO,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;AAC/B,CAAC;AAHD,kCAGC"}
+96
View File
@@ -0,0 +1,96 @@
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;
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
+26
View File
@@ -0,0 +1,26 @@
import type { Options } from './types';
export declare function resolveUrl(url: string, baseUrl: string | null): string;
export declare const uuid: () => string;
export declare function delay<T>(ms: number): (args: T) => Promise<T>;
export declare function toArray<T>(arrayLike: any): T[];
export declare function getStyleProperties(options?: Options): string[];
export declare function getImageSize(targetNode: HTMLElement, options?: Options): {
width: number;
height: number;
};
export declare function getPixelRatio(): number;
export declare function checkCanvasDimensions(canvas: HTMLCanvasElement): void;
export declare function canvasToBlob(canvas: HTMLCanvasElement, options?: Options): Promise<Blob | null>;
export declare function createImage(url: string): Promise<HTMLImageElement>;
export declare function svgToDataURL(svg: SVGElement): Promise<string>;
export declare function nodeToDataURL(node: HTMLElement, width: number, height: number): Promise<string>;
export declare const isInstanceOfElement: <T extends {
new (): Element;
prototype: Element;
} | {
new (): HTMLElement;
prototype: HTMLElement;
} | {
new (): SVGImageElement;
prototype: SVGImageElement;
}>(node: Element | HTMLElement | SVGImageElement, instance: T) => node is T["prototype"];
+259
View File
@@ -0,0 +1,259 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isInstanceOfElement = exports.nodeToDataURL = exports.svgToDataURL = exports.createImage = exports.canvasToBlob = exports.checkCanvasDimensions = exports.getPixelRatio = exports.getImageSize = exports.getStyleProperties = exports.toArray = exports.delay = exports.uuid = exports.resolveUrl = void 0;
function resolveUrl(url, baseUrl) {
// 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;
}
var doc = document.implementation.createHTMLDocument();
var base = doc.createElement('base');
var a = doc.createElement('a');
doc.head.appendChild(base);
doc.body.appendChild(a);
if (baseUrl) {
base.href = baseUrl;
}
a.href = url;
return a.href;
}
exports.resolveUrl = resolveUrl;
exports.uuid = (function () {
// generate uuid for className of pseudo elements.
// We should not use GUIDs, otherwise pseudo elements sometimes cannot be captured.
var counter = 0;
// ref: http://stackoverflow.com/a/6248722/2519373
var random = function () {
// eslint-disable-next-line no-bitwise
return "0000".concat(((Math.random() * Math.pow(36, 4)) << 0).toString(36)).slice(-4);
};
return function () {
counter += 1;
return "u".concat(random()).concat(counter);
};
})();
function delay(ms) {
return function (args) {
return new Promise(function (resolve) {
setTimeout(function () { return resolve(args); }, ms);
});
};
}
exports.delay = delay;
function toArray(arrayLike) {
var arr = [];
for (var i = 0, l = arrayLike.length; i < l; i++) {
arr.push(arrayLike[i]);
}
return arr;
}
exports.toArray = toArray;
var styleProps = null;
function getStyleProperties(options) {
if (options === void 0) { options = {}; }
if (styleProps) {
return styleProps;
}
if (options.includeStyleProperties) {
styleProps = options.includeStyleProperties;
return styleProps;
}
styleProps = toArray(window.getComputedStyle(document.documentElement));
return styleProps;
}
exports.getStyleProperties = getStyleProperties;
function px(node, styleProperty) {
var win = node.ownerDocument.defaultView || window;
var val = win.getComputedStyle(node).getPropertyValue(styleProperty);
return val ? parseFloat(val.replace('px', '')) : 0;
}
function getNodeWidth(node) {
var leftBorder = px(node, 'border-left-width');
var rightBorder = px(node, 'border-right-width');
return node.clientWidth + leftBorder + rightBorder;
}
function getNodeHeight(node) {
var topBorder = px(node, 'border-top-width');
var bottomBorder = px(node, 'border-bottom-width');
return node.clientHeight + topBorder + bottomBorder;
}
function getImageSize(targetNode, options) {
if (options === void 0) { options = {}; }
var width = options.width || getNodeWidth(targetNode);
var height = options.height || getNodeHeight(targetNode);
return { width: width, height: height };
}
exports.getImageSize = getImageSize;
function getPixelRatio() {
var ratio;
var FINAL_PROCESS;
try {
FINAL_PROCESS = process;
}
catch (e) {
// pass
}
var 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;
}
exports.getPixelRatio = getPixelRatio;
// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size
var canvasDimensionLimit = 16384;
function checkCanvasDimensions(canvas) {
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;
}
}
}
exports.checkCanvasDimensions = checkCanvasDimensions;
function canvasToBlob(canvas, options) {
if (options === void 0) { options = {}; }
if (canvas.toBlob) {
return new Promise(function (resolve) {
canvas.toBlob(resolve, options.type ? options.type : 'image/png', options.quality ? options.quality : 1);
});
}
return new Promise(function (resolve) {
var binaryString = window.atob(canvas
.toDataURL(options.type ? options.type : undefined, options.quality ? options.quality : undefined)
.split(',')[1]);
var len = binaryString.length;
var binaryArray = new Uint8Array(len);
for (var i = 0; i < len; i += 1) {
binaryArray[i] = binaryString.charCodeAt(i);
}
resolve(new Blob([binaryArray], {
type: options.type ? options.type : 'image/png',
}));
});
}
exports.canvasToBlob = canvasToBlob;
function createImage(url) {
return new Promise(function (resolve, reject) {
var img = new Image();
img.onload = function () {
img.decode().then(function () {
requestAnimationFrame(function () { return resolve(img); });
});
};
img.onerror = reject;
img.crossOrigin = 'anonymous';
img.decoding = 'async';
img.src = url;
});
}
exports.createImage = createImage;
function svgToDataURL(svg) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, Promise.resolve()
.then(function () { return new XMLSerializer().serializeToString(svg); })
.then(encodeURIComponent)
.then(function (html) { return "data:image/svg+xml;charset=utf-8,".concat(html); })];
});
});
}
exports.svgToDataURL = svgToDataURL;
function nodeToDataURL(node, width, height) {
return __awaiter(this, void 0, void 0, function () {
var xmlns, svg, foreignObject;
return __generator(this, function (_a) {
xmlns = 'http://www.w3.org/2000/svg';
svg = document.createElementNS(xmlns, 'svg');
foreignObject = document.createElementNS(xmlns, 'foreignObject');
svg.setAttribute('width', "".concat(width));
svg.setAttribute('height', "".concat(height));
svg.setAttribute('viewBox', "0 0 ".concat(width, " ").concat(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 [2 /*return*/, svgToDataURL(svg)];
});
});
}
exports.nodeToDataURL = nodeToDataURL;
var isInstanceOfElement = function (node, instance) {
if (node instanceof instance)
return true;
var nodePrototype = Object.getPrototypeOf(node);
if (nodePrototype === null)
return false;
return (nodePrototype.constructor.name === instance.name ||
(0, exports.isInstanceOfElement)(nodePrototype, instance));
};
exports.isInstanceOfElement = isInstanceOfElement;
//# sourceMappingURL=util.js.map
File diff suppressed because one or more lines are too long
+93
View File
@@ -0,0 +1,93 @@
{
"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"
}
}
+29
View File
@@ -0,0 +1,29 @@
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
}
+265
View File
@@ -0,0 +1,265 @@
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))
}
+69
View File
@@ -0,0 +1,69 @@
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)
}
+111
View File
@@ -0,0 +1,111 @@
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
}
+104
View File
@@ -0,0 +1,104 @@
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)
}
}
+92
View File
@@ -0,0 +1,92 @@
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),
)
}
+273
View File
@@ -0,0 +1,273 @@
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)
}
}
}
+101
View File
@@ -0,0 +1,101 @@
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)
}
+25
View File
@@ -0,0 +1,25 @@
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] || ''
}
+104
View File
@@ -0,0 +1,104 @@
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
}
+261
View File
@@ -0,0 +1,261 @@
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)
)
}