mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Inline fonts for ForeignObjectRenderer
This commit is contained in:
parent
f16d581f04
commit
9445b0b598
@ -30,14 +30,14 @@
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"body-parser": "1.17.2",
|
||||
"chai": "4.1.1",
|
||||
"chromeless": "^1.2.0",
|
||||
"chromeless": "1.2.0",
|
||||
"cors": "2.8.4",
|
||||
"eslint": "4.2.0",
|
||||
"eslint-plugin-flowtype": "2.35.0",
|
||||
"eslint-plugin-prettier": "2.1.2",
|
||||
"express": "4.15.4",
|
||||
"filenamify-url": "1.0.0",
|
||||
"flow-bin": "0.50.0",
|
||||
"flow-bin": "0.56.0",
|
||||
"glob": "7.1.2",
|
||||
"html2canvas-proxy": "1.0.0",
|
||||
"jquery": "3.2.1",
|
||||
|
135
src/Clone.js
135
src/Clone.js
@ -3,11 +3,10 @@
|
||||
import type {Bounds} from './Bounds';
|
||||
import type {Options} from './index';
|
||||
import type Logger from './Logger';
|
||||
import type {ImageElement} from './ImageLoader';
|
||||
|
||||
import {parseBounds} from './Bounds';
|
||||
import {Proxy} from './Proxy';
|
||||
import ImageLoader from './ImageLoader';
|
||||
import ResourceLoader from './ResourceLoader';
|
||||
import {copyCSSStyles} from './Util';
|
||||
import {parseBackgroundImage} from './parsing/background';
|
||||
import CanvasRenderer from './renderer/CanvasRenderer';
|
||||
@ -17,7 +16,7 @@ export class DocumentCloner {
|
||||
referenceElement: HTMLElement;
|
||||
clonedReferenceElement: HTMLElement;
|
||||
documentElement: HTMLElement;
|
||||
imageLoader: ImageLoader<*>;
|
||||
resourceLoader: ResourceLoader;
|
||||
logger: Logger;
|
||||
options: Options;
|
||||
inlineImages: boolean;
|
||||
@ -38,7 +37,7 @@ export class DocumentCloner {
|
||||
this.logger = logger;
|
||||
this.options = options;
|
||||
this.renderer = renderer;
|
||||
this.imageLoader = new ImageLoader(options, logger, window);
|
||||
this.resourceLoader = new ResourceLoader(options, logger, window);
|
||||
// $FlowFixMe
|
||||
this.documentElement = this.cloneNode(element.ownerDocument.documentElement);
|
||||
}
|
||||
@ -49,9 +48,14 @@ export class DocumentCloner {
|
||||
Promise.all(
|
||||
parseBackgroundImage(style.backgroundImage).map(backgroundImage => {
|
||||
if (backgroundImage.method === 'url') {
|
||||
return this.imageLoader
|
||||
return this.resourceLoader
|
||||
.inlineImage(backgroundImage.args[0])
|
||||
.then(img => (img ? `url("${img.src}")` : 'none'))
|
||||
.then(
|
||||
img =>
|
||||
img && typeof img.src === 'string'
|
||||
? `url("${img.src}")`
|
||||
: 'none'
|
||||
)
|
||||
.catch(e => {
|
||||
if (__DEV__) {
|
||||
this.logger.log(`Unable to load image`, e);
|
||||
@ -73,7 +77,7 @@ export class DocumentCloner {
|
||||
});
|
||||
|
||||
if (node instanceof HTMLImageElement) {
|
||||
this.imageLoader
|
||||
this.resourceLoader
|
||||
.inlineImage(node.src)
|
||||
.then(img => {
|
||||
if (img && node instanceof HTMLImageElement && node.parentNode) {
|
||||
@ -91,6 +95,56 @@ export class DocumentCloner {
|
||||
}
|
||||
}
|
||||
|
||||
inlineFonts(document: Document): Promise<void> {
|
||||
return Promise.all(
|
||||
Array.from(document.styleSheets).map(sheet => {
|
||||
if (sheet.href) {
|
||||
return fetch(sheet.href)
|
||||
.then(res => res.text())
|
||||
.then(text => createStyleSheetFontsFromText(text, sheet.href))
|
||||
.catch(e => {
|
||||
if (__DEV__) {
|
||||
this.logger.log(`Unable to load stylesheet`, e);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
return getSheetFonts(sheet, document);
|
||||
})
|
||||
)
|
||||
.then(fonts => fonts.reduce((acc, font) => acc.concat(font), []))
|
||||
.then(fonts =>
|
||||
Promise.all(
|
||||
fonts.map(font =>
|
||||
fetch(font.formats[0].src)
|
||||
.then(response => response.blob())
|
||||
.then(
|
||||
blob =>
|
||||
new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = reject;
|
||||
reader.onload = () => {
|
||||
// $FlowFixMe
|
||||
const result: string = reader.result;
|
||||
resolve(result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
})
|
||||
)
|
||||
.then(dataUri => {
|
||||
font.fontFace.setProperty('src', `url("${dataUri}")`);
|
||||
return `@font-face {${font.fontFace.cssText} `;
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(fontCss => {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = fontCss.join('\n');
|
||||
this.documentElement.appendChild(style);
|
||||
});
|
||||
}
|
||||
|
||||
createElementClone(node: Node) {
|
||||
if (this.copyStyles && node instanceof HTMLCanvasElement) {
|
||||
const img = node.ownerDocument.createElement('img');
|
||||
@ -111,7 +165,7 @@ export class DocumentCloner {
|
||||
|
||||
const {width, height} = parseBounds(node, 0, 0);
|
||||
|
||||
this.imageLoader.cache[iframeKey] = getIframeDocumentElement(node, this.options)
|
||||
this.resourceLoader.cache[iframeKey] = getIframeDocumentElement(node, this.options)
|
||||
.then(documentElement => {
|
||||
return this.renderer(
|
||||
documentElement,
|
||||
@ -211,6 +265,67 @@ export class DocumentCloner {
|
||||
}
|
||||
}
|
||||
|
||||
type Font = {
|
||||
src: string,
|
||||
format: string
|
||||
};
|
||||
|
||||
type FontFamily = {
|
||||
formats: Array<Font>,
|
||||
fontFace: CSSStyleDeclaration
|
||||
};
|
||||
|
||||
const getSheetFonts = (sheet: StyleSheet, document: Document): Array<FontFamily> => {
|
||||
// $FlowFixMe
|
||||
return (sheet.cssRules ? Array.from(sheet.cssRules) : [])
|
||||
.filter(rule => rule.type === CSSRule.FONT_FACE_RULE)
|
||||
.map(rule => {
|
||||
const src = parseBackgroundImage(rule.style.getPropertyValue('src'));
|
||||
const formats = [];
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
if (src[i].method === 'url' && src[i + 1] && src[i + 1].method === 'format') {
|
||||
const a = document.createElement('a');
|
||||
a.href = src[i].args[0];
|
||||
if (document.body) {
|
||||
document.body.appendChild(a);
|
||||
}
|
||||
|
||||
const font = {
|
||||
src: a.href,
|
||||
format: src[i + 1].args[0]
|
||||
};
|
||||
formats.push(font);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// TODO select correct format for browser),
|
||||
|
||||
formats: formats.filter(font => /^woff/i.test(font.format)),
|
||||
fontFace: rule.style
|
||||
};
|
||||
})
|
||||
.filter(font => font.formats.length);
|
||||
};
|
||||
|
||||
const createStyleSheetFontsFromText = (text: string, baseHref: string): Array<FontFamily> => {
|
||||
const doc = document.implementation.createHTMLDocument('');
|
||||
const base = document.createElement('base');
|
||||
// $FlowFixMe
|
||||
base.href = baseHref;
|
||||
const style = document.createElement('style');
|
||||
|
||||
style.textContent = text;
|
||||
if (doc.head) {
|
||||
doc.head.appendChild(base);
|
||||
}
|
||||
if (doc.body) {
|
||||
doc.body.appendChild(style);
|
||||
}
|
||||
|
||||
return style.sheet ? getSheetFonts(style.sheet, doc) : [];
|
||||
};
|
||||
|
||||
const restoreOwnerScroll = (ownerDocument: Document, x: number, y: number) => {
|
||||
if (
|
||||
ownerDocument.defaultView &&
|
||||
@ -415,7 +530,7 @@ export const cloneWindow = (
|
||||
options: Options,
|
||||
logger: Logger,
|
||||
renderer: (element: HTMLElement, options: Options, logger: Logger) => Promise<*>
|
||||
): Promise<[HTMLIFrameElement, HTMLElement, ImageLoader<ImageElement>]> => {
|
||||
): Promise<[HTMLIFrameElement, HTMLElement, ResourceLoader]> => {
|
||||
const cloner = new DocumentCloner(referenceElement, options, logger, false, renderer);
|
||||
const scrollX = ownerDocument.defaultView.pageXOffset;
|
||||
const scrollY = ownerDocument.defaultView.pageYOffset;
|
||||
@ -445,7 +560,7 @@ export const cloneWindow = (
|
||||
? Promise.resolve([
|
||||
cloneIframeContainer,
|
||||
cloner.clonedReferenceElement,
|
||||
cloner.imageLoader
|
||||
cloner.resourceLoader
|
||||
])
|
||||
: Promise.reject(
|
||||
__DEV__
|
||||
|
@ -146,7 +146,10 @@ const FEATURES = {
|
||||
// $FlowFixMe - get/set properties not yet supported
|
||||
get SUPPORT_FOREIGNOBJECT_DRAWING() {
|
||||
'use strict';
|
||||
const value = testForeignObject(document);
|
||||
const value =
|
||||
typeof Array.from === 'function' && typeof window.fetch === 'function'
|
||||
? testForeignObject(document)
|
||||
: Promise.resolve(false);
|
||||
Object.defineProperty(FEATURES, 'SUPPORT_FOREIGNOBJECT_DRAWING', {value});
|
||||
return value;
|
||||
},
|
||||
|
@ -18,7 +18,7 @@ import type {Visibility} from './parsing/visibility';
|
||||
import type {zIndex} from './parsing/zIndex';
|
||||
|
||||
import type {Bounds, BoundCurves} from './Bounds';
|
||||
import type ImageLoader, {ImageElement} from './ImageLoader';
|
||||
import type ResourceLoader, {ImageElement} from './ResourceLoader';
|
||||
import type {Path} from './drawing/Path';
|
||||
import type TextContainer from './TextContainer';
|
||||
|
||||
@ -87,7 +87,7 @@ export default class NodeContainer {
|
||||
constructor(
|
||||
node: HTMLElement | SVGSVGElement,
|
||||
parent: ?NodeContainer,
|
||||
imageLoader: ImageLoader<ImageElement>,
|
||||
resourceLoader: ResourceLoader,
|
||||
index: number
|
||||
) {
|
||||
this.parent = parent;
|
||||
@ -104,7 +104,7 @@ export default class NodeContainer {
|
||||
const position = parsePosition(style.position);
|
||||
|
||||
this.style = {
|
||||
background: IS_INPUT ? INPUT_BACKGROUND : parseBackground(style, imageLoader),
|
||||
background: IS_INPUT ? INPUT_BACKGROUND : parseBackground(style, resourceLoader),
|
||||
border: IS_INPUT ? INPUT_BORDERS : parseBorder(style),
|
||||
borderRadius:
|
||||
(node instanceof defaultView.HTMLInputElement ||
|
||||
@ -148,7 +148,7 @@ export default class NodeContainer {
|
||||
);
|
||||
});
|
||||
}
|
||||
this.image = getImage(node, imageLoader);
|
||||
this.image = getImage(node, resourceLoader);
|
||||
this.bounds = IS_INPUT
|
||||
? reformatInputBounds(parseBounds(node, scrollX, scrollY))
|
||||
: parseBounds(node, scrollX, scrollY);
|
||||
@ -223,26 +223,25 @@ export default class NodeContainer {
|
||||
}
|
||||
}
|
||||
|
||||
const getImage = (
|
||||
node: HTMLElement | SVGSVGElement,
|
||||
imageLoader: ImageLoader<ImageElement>
|
||||
): ?string => {
|
||||
const getImage = (node: HTMLElement | SVGSVGElement, resourceLoader: ResourceLoader): ?string => {
|
||||
if (
|
||||
node instanceof node.ownerDocument.defaultView.SVGSVGElement ||
|
||||
node instanceof SVGSVGElement
|
||||
) {
|
||||
const s = new XMLSerializer();
|
||||
return imageLoader.loadImage(
|
||||
return resourceLoader.loadImage(
|
||||
`data:image/svg+xml,${encodeURIComponent(s.serializeToString(node))}`
|
||||
);
|
||||
}
|
||||
switch (node.tagName) {
|
||||
case 'IMG':
|
||||
// $FlowFixMe
|
||||
return imageLoader.loadImage(node.currentSrc || node.src);
|
||||
const img: HTMLImageElement = node;
|
||||
return resourceLoader.loadImage(img.currentSrc || img.src);
|
||||
case 'CANVAS':
|
||||
// $FlowFixMe
|
||||
return imageLoader.loadCanvas(node);
|
||||
const canvas: HTMLCanvasElement = node;
|
||||
return resourceLoader.loadCanvas(canvas);
|
||||
case 'IFRAME':
|
||||
const iframeKey = node.getAttribute('data-html2canvas-internal-iframe-key');
|
||||
if (iframeKey) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
import type ImageLoader, {ImageElement} from './ImageLoader';
|
||||
import type ResourceLoader, {ImageElement} from './ResourceLoader';
|
||||
import type Logger from './Logger';
|
||||
import StackingContext from './StackingContext';
|
||||
import NodeContainer from './NodeContainer';
|
||||
@ -9,7 +9,7 @@ import {inlineInputElement, inlineTextAreaElement, inlineSelectElement} from './
|
||||
|
||||
export const NodeParser = (
|
||||
node: HTMLElement,
|
||||
imageLoader: ImageLoader<ImageElement>,
|
||||
resourceLoader: ResourceLoader,
|
||||
logger: Logger
|
||||
): StackingContext => {
|
||||
if (__DEV__) {
|
||||
@ -18,10 +18,10 @@ export const NodeParser = (
|
||||
|
||||
let index = 0;
|
||||
|
||||
const container = new NodeContainer(node, null, imageLoader, index++);
|
||||
const container = new NodeContainer(node, null, resourceLoader, index++);
|
||||
const stack = new StackingContext(container, null, true);
|
||||
|
||||
parseNodeTree(node, container, stack, imageLoader, index);
|
||||
parseNodeTree(node, container, stack, resourceLoader, index);
|
||||
|
||||
if (__DEV__) {
|
||||
logger.log(`Finished parsing node tree`);
|
||||
@ -36,7 +36,7 @@ const parseNodeTree = (
|
||||
node: HTMLElement,
|
||||
parent: NodeContainer,
|
||||
stack: StackingContext,
|
||||
imageLoader: ImageLoader<ImageElement>,
|
||||
resourceLoader: ResourceLoader,
|
||||
index: number
|
||||
): void => {
|
||||
if (__DEV__ && index > 50000) {
|
||||
@ -60,7 +60,7 @@ const parseNodeTree = (
|
||||
(defaultView.parent && childNode instanceof defaultView.parent.HTMLElement)
|
||||
) {
|
||||
if (IGNORED_NODE_NAMES.indexOf(childNode.nodeName) === -1) {
|
||||
const container = new NodeContainer(childNode, parent, imageLoader, index++);
|
||||
const container = new NodeContainer(childNode, parent, resourceLoader, index++);
|
||||
if (container.isVisible()) {
|
||||
if (childNode.tagName === 'INPUT') {
|
||||
// $FlowFixMe
|
||||
@ -92,12 +92,12 @@ const parseNodeTree = (
|
||||
);
|
||||
parentStack.contexts.push(childStack);
|
||||
if (SHOULD_TRAVERSE_CHILDREN) {
|
||||
parseNodeTree(childNode, container, childStack, imageLoader, index);
|
||||
parseNodeTree(childNode, container, childStack, resourceLoader, index);
|
||||
}
|
||||
} else {
|
||||
stack.children.push(container);
|
||||
if (SHOULD_TRAVERSE_CHILDREN) {
|
||||
parseNodeTree(childNode, container, stack, imageLoader, index);
|
||||
parseNodeTree(childNode, container, stack, resourceLoader, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,7 @@ const parseNodeTree = (
|
||||
childNode instanceof SVGSVGElement ||
|
||||
(defaultView.parent && childNode instanceof defaultView.parent.SVGSVGElement)
|
||||
) {
|
||||
const container = new NodeContainer(childNode, parent, imageLoader, index++);
|
||||
const container = new NodeContainer(childNode, parent, resourceLoader, index++);
|
||||
const treatAsRealStackingContext = createsRealStackingContext(container, childNode);
|
||||
if (treatAsRealStackingContext || createsStackingContext(container)) {
|
||||
// for treatAsRealStackingContext:false, any positioned descendants and descendants
|
||||
|
@ -15,7 +15,7 @@ import type {Matrix} from './parsing/transform';
|
||||
|
||||
import type {BoundCurves} from './Bounds';
|
||||
import type {Gradient} from './Gradient';
|
||||
import type {ImageStore, ImageElement} from './ImageLoader';
|
||||
import type {ResourceStore, ImageElement} from './ResourceLoader';
|
||||
import type NodeContainer from './NodeContainer';
|
||||
import type StackingContext from './StackingContext';
|
||||
import type {TextBounds} from './TextBounds';
|
||||
@ -43,7 +43,7 @@ import {BORDER_STYLE} from './parsing/border';
|
||||
export type RenderOptions = {
|
||||
scale: number,
|
||||
backgroundColor: ?Color,
|
||||
imageStore: ImageStore<ImageElement>,
|
||||
imageStore: ResourceStore,
|
||||
fontMetrics: FontMetrics,
|
||||
logger: Logger,
|
||||
x: number,
|
||||
|
@ -5,17 +5,17 @@ import type Options from './index';
|
||||
import type Logger from './Logger';
|
||||
|
||||
export type ImageElement = Image | HTMLCanvasElement;
|
||||
type ImageCache<T> = {[string]: Promise<T>};
|
||||
export type Resource = ImageElement;
|
||||
type ResourceCache = {[string]: Promise<Resource>};
|
||||
|
||||
import FEATURES from './Feature';
|
||||
import {Proxy} from './Proxy';
|
||||
|
||||
// $FlowFixMe
|
||||
export default class ImageLoader<T> {
|
||||
export default class ResourceLoader {
|
||||
origin: string;
|
||||
options: Options;
|
||||
_link: HTMLAnchorElement;
|
||||
cache: ImageCache<T>;
|
||||
cache: ResourceCache;
|
||||
logger: Logger;
|
||||
_index: number;
|
||||
_window: WindowProxy;
|
||||
@ -30,7 +30,7 @@ export default class ImageLoader<T> {
|
||||
}
|
||||
|
||||
loadImage(src: string): ?string {
|
||||
if (this.hasImageInCache(src)) {
|
||||
if (this.hasResourceInCache(src)) {
|
||||
return src;
|
||||
}
|
||||
|
||||
@ -58,11 +58,11 @@ export default class ImageLoader<T> {
|
||||
}
|
||||
}
|
||||
|
||||
inlineImage(src: string): Promise<Image> {
|
||||
inlineImage(src: string): Promise<Resource> {
|
||||
if (isInlineImage(src)) {
|
||||
return loadImage(src, this.options.imageTimeout || 0);
|
||||
}
|
||||
if (this.hasImageInCache(src)) {
|
||||
if (this.hasResourceInCache(src)) {
|
||||
return this.cache[src];
|
||||
}
|
||||
if (!this.isSameOrigin(src) && typeof this.options.proxy === 'string') {
|
||||
@ -74,7 +74,7 @@ export default class ImageLoader<T> {
|
||||
return this.xhrImage(src);
|
||||
}
|
||||
|
||||
xhrImage(src: string): Promise<Image> {
|
||||
xhrImage(src: string): Promise<Resource> {
|
||||
this.cache[src] = new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = () => {
|
||||
@ -88,10 +88,16 @@ export default class ImageLoader<T> {
|
||||
);
|
||||
} else {
|
||||
const reader = new FileReader();
|
||||
// $FlowFixMe
|
||||
reader.addEventListener('load', () => resolve(reader.result), false);
|
||||
// $FlowFixMe
|
||||
reader.addEventListener('error', e => reject(e), false);
|
||||
reader.addEventListener(
|
||||
'load',
|
||||
() => {
|
||||
// $FlowFixMe
|
||||
const result: string = reader.result;
|
||||
resolve(result);
|
||||
},
|
||||
false
|
||||
);
|
||||
reader.addEventListener('error', (e: Event) => reject(e), false);
|
||||
reader.readAsDataURL(xhr.response);
|
||||
}
|
||||
}
|
||||
@ -118,7 +124,7 @@ export default class ImageLoader<T> {
|
||||
return key;
|
||||
}
|
||||
|
||||
hasImageInCache(key: string): boolean {
|
||||
hasResourceInCache(key: string): boolean {
|
||||
return typeof this.cache[key] !== 'undefined';
|
||||
}
|
||||
|
||||
@ -177,38 +183,37 @@ export default class ImageLoader<T> {
|
||||
return link.protocol + link.hostname + link.port;
|
||||
}
|
||||
|
||||
ready(): Promise<ImageStore<T>> {
|
||||
const keys = Object.keys(this.cache);
|
||||
return Promise.all(
|
||||
keys.map(str =>
|
||||
this.cache[str].catch(e => {
|
||||
if (__DEV__) {
|
||||
this.logger.log(`Unable to load image`, e);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
)
|
||||
).then(images => {
|
||||
ready(): Promise<ResourceStore> {
|
||||
const keys: Array<string> = Object.keys(this.cache);
|
||||
const values: Array<Promise<?Resource>> = keys.map(str =>
|
||||
this.cache[str].catch(e => {
|
||||
if (__DEV__) {
|
||||
this.logger.log(`Unable to load image`, e);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
return Promise.all(values).then((images: Array<?Resource>) => {
|
||||
if (__DEV__) {
|
||||
this.logger.log(`Finished loading ${images.length} images`, images);
|
||||
}
|
||||
return new ImageStore(keys, images);
|
||||
return new ResourceStore(keys, images);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageStore<T> {
|
||||
export class ResourceStore {
|
||||
_keys: Array<string>;
|
||||
_images: Array<?T>;
|
||||
_resources: Array<?Resource>;
|
||||
|
||||
constructor(keys: Array<string>, images: Array<?T>) {
|
||||
constructor(keys: Array<string>, resources: Array<?Resource>) {
|
||||
this._keys = keys;
|
||||
this._images = images;
|
||||
this._resources = resources;
|
||||
}
|
||||
|
||||
get(key: string): ?T {
|
||||
get(key: string): ?Resource {
|
||||
const index = this._keys.indexOf(key);
|
||||
return index === -1 ? null : this._images[index];
|
||||
return index === -1 ? null : this._resources[index];
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,7 +227,7 @@ const isInlineBase64Image = (src: string): boolean => INLINE_BASE64.test(src);
|
||||
const isSVG = (src: string): boolean =>
|
||||
src.substr(-3).toLowerCase() === 'svg' || INLINE_SVG.test(src);
|
||||
|
||||
const loadImage = (src: string, timeout: number) => {
|
||||
const loadImage = (src: string, timeout: number): Promise<Image> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
@ -57,22 +57,25 @@ export const renderElement = (
|
||||
logger.log(`Document cloned, using foreignObject rendering`);
|
||||
}
|
||||
|
||||
return cloner.imageLoader.ready().then(() => {
|
||||
const renderer = new ForeignObjectRenderer(cloner.clonedReferenceElement);
|
||||
return renderer.render({
|
||||
backgroundColor,
|
||||
logger,
|
||||
scale: options.scale,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
windowWidth: options.windowWidth,
|
||||
windowHeight: options.windowHeight,
|
||||
scrollX: options.scrollX,
|
||||
scrollY: options.scrollY
|
||||
return cloner
|
||||
.inlineFonts(ownerDocument)
|
||||
.then(() => cloner.resourceLoader.ready())
|
||||
.then(() => {
|
||||
const renderer = new ForeignObjectRenderer(cloner.documentElement);
|
||||
return renderer.render({
|
||||
backgroundColor,
|
||||
logger,
|
||||
scale: options.scale,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
windowWidth: options.windowWidth,
|
||||
windowHeight: options.windowHeight,
|
||||
scrollX: options.scrollX,
|
||||
scrollY: options.scrollY
|
||||
});
|
||||
});
|
||||
});
|
||||
})(new DocumentCloner(element, options, logger, true, renderElement))
|
||||
: cloneWindow(
|
||||
ownerDocument,
|
||||
@ -81,19 +84,19 @@ export const renderElement = (
|
||||
options,
|
||||
logger,
|
||||
renderElement
|
||||
).then(([container, clonedElement, imageLoader]) => {
|
||||
).then(([container, clonedElement, resourceLoader]) => {
|
||||
if (__DEV__) {
|
||||
logger.log(`Document cloned, using computed rendering`);
|
||||
}
|
||||
|
||||
const stack = NodeParser(clonedElement, imageLoader, logger);
|
||||
const stack = NodeParser(clonedElement, resourceLoader, logger);
|
||||
const clonedDocument = clonedElement.ownerDocument;
|
||||
|
||||
if (backgroundColor === stack.container.style.background.backgroundColor) {
|
||||
stack.container.style.background.backgroundColor = TRANSPARENT;
|
||||
}
|
||||
|
||||
return imageLoader.ready().then(imageStore => {
|
||||
return resourceLoader.ready().then(imageStore => {
|
||||
if (options.removeContainer === true) {
|
||||
if (container.parentNode) {
|
||||
container.parentNode.removeChild(container);
|
||||
|
@ -2,7 +2,7 @@
|
||||
'use strict';
|
||||
import type {Path} from '../drawing/Path';
|
||||
import type {Bounds, BoundCurves} from '../Bounds';
|
||||
import type ImageLoader, {ImageElement} from '../ImageLoader';
|
||||
import type ResourceLoader, {ImageElement} from '../ResourceLoader';
|
||||
|
||||
import Color from '../Color';
|
||||
import Length from '../Length';
|
||||
@ -223,11 +223,11 @@ export const calculateBackgroundRepeatPath = (
|
||||
|
||||
export const parseBackground = (
|
||||
style: CSSStyleDeclaration,
|
||||
imageLoader: ImageLoader<ImageElement>
|
||||
resourceLoader: ResourceLoader
|
||||
): Background => {
|
||||
return {
|
||||
backgroundColor: new Color(style.backgroundColor),
|
||||
backgroundImage: parseBackgroundImages(style, imageLoader),
|
||||
backgroundImage: parseBackgroundImages(style, resourceLoader),
|
||||
backgroundClip: parseBackgroundClip(style.backgroundClip),
|
||||
backgroundOrigin: parseBackgroundOrigin(style.backgroundOrigin)
|
||||
};
|
||||
@ -276,13 +276,13 @@ const parseBackgroundRepeat = (backgroundRepeat: string): BackgroundRepeat => {
|
||||
|
||||
const parseBackgroundImages = (
|
||||
style: CSSStyleDeclaration,
|
||||
imageLoader: ImageLoader<ImageElement>
|
||||
resourceLoader: ResourceLoader
|
||||
): Array<BackgroundImage> => {
|
||||
const sources: Array<BackgroundSource> = parseBackgroundImage(
|
||||
style.backgroundImage
|
||||
).map(backgroundImage => {
|
||||
if (backgroundImage.method === 'url') {
|
||||
const key = imageLoader.loadImage(backgroundImage.args[0]);
|
||||
const key = resourceLoader.loadImage(backgroundImage.args[0]);
|
||||
backgroundImage.args = key ? [key] : [];
|
||||
}
|
||||
return backgroundImage;
|
||||
|
@ -12,7 +12,7 @@ import type {TextShadow} from '../parsing/textShadow';
|
||||
import type {Matrix} from '../parsing/transform';
|
||||
|
||||
import type {Bounds} from '../Bounds';
|
||||
import type {ImageElement} from '../ImageLoader';
|
||||
import type {ImageElement} from '../ResourceLoader';
|
||||
import type {Gradient} from '../Gradient';
|
||||
import type {TextBounds} from '../TextBounds';
|
||||
|
||||
|
@ -12,29 +12,34 @@ export default class ForeignObjectRenderer {
|
||||
this.options = options;
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.canvas.width = Math.floor(options.width * options.scale);
|
||||
this.canvas.height = Math.floor(options.height * options.scale);
|
||||
this.canvas.width = Math.floor(options.width) * options.scale;
|
||||
this.canvas.height = Math.floor(options.height) * options.scale;
|
||||
this.canvas.style.width = `${options.width}px`;
|
||||
this.canvas.style.height = `${options.height}px`;
|
||||
this.ctx.scale(this.options.scale, this.options.scale);
|
||||
|
||||
options.logger.log(
|
||||
`ForeignObject renderer initialized (${options.width}x${options.height} at ${options.x},${options.y}) with scale ${this
|
||||
.options.scale}`
|
||||
`ForeignObject renderer initialized (${options.width}x${options.height} at ${options.x},${options.y}) with scale ${options.scale}`
|
||||
);
|
||||
const svg = createForeignObjectSVG(
|
||||
Math.max(options.windowWidth, options.width),
|
||||
Math.max(options.windowHeight, options.height),
|
||||
options.scrollX,
|
||||
options.scrollY,
|
||||
Math.max(options.windowWidth, options.width) * options.scale,
|
||||
Math.max(options.windowHeight, options.height) * options.scale,
|
||||
options.scrollX * options.scale,
|
||||
options.scrollY * options.scale,
|
||||
this.element
|
||||
);
|
||||
|
||||
return loadSerializedSVG(svg).then(img => {
|
||||
if (options.backgroundColor) {
|
||||
this.ctx.fillStyle = options.backgroundColor.toString();
|
||||
this.ctx.fillRect(0, 0, options.width, options.height);
|
||||
this.ctx.fillRect(
|
||||
0,
|
||||
0,
|
||||
options.width * options.scale,
|
||||
options.height * options.scale
|
||||
);
|
||||
}
|
||||
this.ctx.drawImage(img, -options.x, -options.y);
|
||||
|
||||
this.ctx.drawImage(img, -options.x * options.scale, -options.y * options.scale);
|
||||
return this.canvas;
|
||||
});
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import type {TextShadow} from '../parsing/textShadow';
|
||||
import type {Matrix} from '../parsing/transform';
|
||||
|
||||
import type {Bounds} from '../Bounds';
|
||||
import type {ImageElement} from '../ImageLoader';
|
||||
import type {ImageElement} from '../ResourceLoader';
|
||||
import type {Gradient} from '../Gradient';
|
||||
import type {TextBounds} from '../TextBounds';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user