mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Merge 3d559c79dfafa51ca3352236d79e5f5fc5aa582e into 6020386bbeed60ad68e675fdcaa6220e292fd35a
This commit is contained in:
commit
721b36b7eb
24946
package-lock.json
generated
24946
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,10 +9,9 @@ export class Context {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cache = {
|
this.cache = {
|
||||||
addImage: jest.fn().mockImplementation((src: string): Promise<void> => {
|
addImage: jest.fn().mockImplementation((src: string): boolean => {
|
||||||
const result = Promise.resolve();
|
this._cache[src] = Promise.resolve();
|
||||||
this._cache[src] = result;
|
return true;
|
||||||
return result;
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -125,88 +125,88 @@ describe('cache-storage', () => {
|
|||||||
xhr.splice(0, xhr.length);
|
xhr.splice(0, xhr.length);
|
||||||
images.splice(0, images.length);
|
images.splice(0, images.length);
|
||||||
});
|
});
|
||||||
it('addImage adds images to cache', async () => {
|
it('addImage adds images to cache', () => {
|
||||||
const {cache} = createMockContext('http://example.com', {proxy: null});
|
const {cache} = createMockContext('http://example.com', {proxy: null});
|
||||||
await cache.addImage('http://example.com/test.jpg');
|
cache.addImage('http://example.com/test.jpg');
|
||||||
await cache.addImage('http://example.com/test2.jpg');
|
cache.addImage('http://example.com/test2.jpg');
|
||||||
|
|
||||||
deepStrictEqual(images.length, 2);
|
deepStrictEqual(images.length, 2);
|
||||||
deepStrictEqual(images[0].src, 'http://example.com/test.jpg');
|
deepStrictEqual(images[0].src, 'http://example.com/test.jpg');
|
||||||
deepStrictEqual(images[1].src, 'http://example.com/test2.jpg');
|
deepStrictEqual(images[1].src, 'http://example.com/test2.jpg');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('addImage should not add duplicate entries', async () => {
|
it('addImage should not add duplicate entries', () => {
|
||||||
const {cache} = createMockContext('http://example.com');
|
const {cache} = createMockContext('http://example.com');
|
||||||
await cache.addImage('http://example.com/test.jpg');
|
cache.addImage('http://example.com/test.jpg');
|
||||||
await cache.addImage('http://example.com/test.jpg');
|
cache.addImage('http://example.com/test.jpg');
|
||||||
|
|
||||||
deepStrictEqual(images.length, 1);
|
deepStrictEqual(images.length, 1);
|
||||||
deepStrictEqual(images[0].src, 'http://example.com/test.jpg');
|
deepStrictEqual(images[0].src, 'http://example.com/test.jpg');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('svg', () => {
|
describe('svg', () => {
|
||||||
it('should add svg images correctly', async () => {
|
it('should add svg images correctly', () => {
|
||||||
const {cache} = createMockContext('http://example.com');
|
const {cache} = createMockContext('http://example.com');
|
||||||
await cache.addImage('http://example.com/test.svg');
|
cache.addImage('http://example.com/test.svg');
|
||||||
await cache.addImage('http://example.com/test2.svg');
|
cache.addImage('http://example.com/test2.svg');
|
||||||
|
|
||||||
deepStrictEqual(images.length, 2);
|
deepStrictEqual(images.length, 2);
|
||||||
deepStrictEqual(images[0].src, 'http://example.com/test.svg');
|
deepStrictEqual(images[0].src, 'http://example.com/test.svg');
|
||||||
deepStrictEqual(images[1].src, 'http://example.com/test2.svg');
|
deepStrictEqual(images[1].src, 'http://example.com/test2.svg');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should omit svg images if not supported', async () => {
|
it('should omit svg images if not supported', () => {
|
||||||
setFeatures({SUPPORT_SVG_DRAWING: false});
|
setFeatures({SUPPORT_SVG_DRAWING: false});
|
||||||
const {cache} = createMockContext('http://example.com');
|
const {cache} = createMockContext('http://example.com');
|
||||||
await cache.addImage('http://example.com/test.svg');
|
cache.addImage('http://example.com/test.svg');
|
||||||
await cache.addImage('http://example.com/test2.svg');
|
cache.addImage('http://example.com/test2.svg');
|
||||||
|
|
||||||
deepStrictEqual(images.length, 0);
|
deepStrictEqual(images.length, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('cross-origin', () => {
|
describe('cross-origin', () => {
|
||||||
it('addImage should not add images it cannot load/render', async () => {
|
it('addImage should not add images it cannot load/render', () => {
|
||||||
const {cache} = createMockContext('http://example.com', {
|
const {cache} = createMockContext('http://example.com', {
|
||||||
proxy: undefined
|
proxy: undefined
|
||||||
});
|
});
|
||||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(images.length, 0);
|
deepStrictEqual(images.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('addImage should add images if tainting enabled', async () => {
|
it('addImage should add images if tainting enabled', () => {
|
||||||
const {cache} = createMockContext('http://example.com', {
|
const {cache} = createMockContext('http://example.com', {
|
||||||
allowTaint: true,
|
allowTaint: true,
|
||||||
proxy: undefined
|
proxy: undefined
|
||||||
});
|
});
|
||||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(images.length, 1);
|
deepStrictEqual(images.length, 1);
|
||||||
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(images[0].crossOrigin, undefined);
|
deepStrictEqual(images[0].crossOrigin, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('addImage should add images if cors enabled', async () => {
|
it('addImage should add images if cors enabled', () => {
|
||||||
const {cache} = createMockContext('http://example.com', {useCORS: true});
|
const {cache} = createMockContext('http://example.com', {useCORS: true});
|
||||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(images.length, 1);
|
deepStrictEqual(images.length, 1);
|
||||||
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(images[0].crossOrigin, 'anonymous');
|
deepStrictEqual(images[0].crossOrigin, 'anonymous');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('addImage should not add images if cors enabled but not supported', async () => {
|
it('addImage should not add images if cors enabled but not supported', () => {
|
||||||
setFeatures({SUPPORT_CORS_IMAGES: false});
|
setFeatures({SUPPORT_CORS_IMAGES: false});
|
||||||
|
|
||||||
const {cache} = createMockContext('http://example.com', {
|
const {cache} = createMockContext('http://example.com', {
|
||||||
useCORS: true,
|
useCORS: true,
|
||||||
proxy: undefined
|
proxy: undefined
|
||||||
});
|
});
|
||||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(images.length, 0);
|
deepStrictEqual(images.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('addImage should not add images to proxy if cors enabled', async () => {
|
it('addImage should not add images to proxy if cors enabled', () => {
|
||||||
const {cache} = createMockContext('http://example.com', {useCORS: true});
|
const {cache} = createMockContext('http://example.com', {useCORS: true});
|
||||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(images.length, 1);
|
deepStrictEqual(images.length, 1);
|
||||||
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(images[0].crossOrigin, 'anonymous');
|
deepStrictEqual(images[0].crossOrigin, 'anonymous');
|
||||||
@ -214,7 +214,7 @@ describe('cache-storage', () => {
|
|||||||
|
|
||||||
it('addImage should use proxy ', async () => {
|
it('addImage should use proxy ', async () => {
|
||||||
const {cache} = createMockContext('http://example.com');
|
const {cache} = createMockContext('http://example.com');
|
||||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||||
deepStrictEqual(xhr.length, 1);
|
deepStrictEqual(xhr.length, 1);
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
xhr[0].url,
|
xhr[0].url,
|
||||||
@ -230,7 +230,7 @@ describe('cache-storage', () => {
|
|||||||
const {cache} = createMockContext('http://example.com', {
|
const {cache} = createMockContext('http://example.com', {
|
||||||
imageTimeout: 10
|
imageTimeout: 10
|
||||||
});
|
});
|
||||||
await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
cache.addImage('http://html2canvas.hertzen.com/test.jpg');
|
||||||
|
|
||||||
deepStrictEqual(xhr.length, 1);
|
deepStrictEqual(xhr.length, 1);
|
||||||
deepStrictEqual(
|
deepStrictEqual(
|
||||||
@ -250,7 +250,7 @@ describe('cache-storage', () => {
|
|||||||
|
|
||||||
it('match should return cache entry', async () => {
|
it('match should return cache entry', async () => {
|
||||||
const {cache} = createMockContext('http://example.com');
|
const {cache} = createMockContext('http://example.com');
|
||||||
await cache.addImage('http://example.com/test.jpg');
|
cache.addImage('http://example.com/test.jpg');
|
||||||
|
|
||||||
if (images[0].onload) {
|
if (images[0].onload) {
|
||||||
images[0].onload();
|
images[0].onload();
|
||||||
@ -270,4 +270,14 @@ describe('cache-storage', () => {
|
|||||||
fail('Expected result to timeout');
|
fail('Expected result to timeout');
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('addImage should add an inlined image', async () => {
|
||||||
|
const {cache} = createMockContext('http://example.com', {imageTimeout: 10});
|
||||||
|
const inlinedImg = `data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub/
|
||||||
|
/ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcpp
|
||||||
|
V0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7`;
|
||||||
|
cache.addImage(inlinedImg);
|
||||||
|
|
||||||
|
await cache.match(inlinedImg);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -39,20 +39,15 @@ export class Cache {
|
|||||||
|
|
||||||
constructor(private readonly context: Context, private readonly _options: ResourceOptions) {}
|
constructor(private readonly context: Context, private readonly _options: ResourceOptions) {}
|
||||||
|
|
||||||
addImage(src: string): Promise<void> {
|
addImage(src: string): boolean {
|
||||||
const result = Promise.resolve();
|
if (this.has(src)) return true;
|
||||||
if (this.has(src)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBlobImage(src) || isRenderable(src)) {
|
if (isBlobImage(src) || isRenderable(src)) {
|
||||||
(this._cache[src] = this.loadImage(src)).catch(() => {
|
(this._cache[src] = this.loadImage(src)).catch(() => {
|
||||||
// prevent unhandled rejection
|
// prevent unhandled rejection
|
||||||
});
|
});
|
||||||
return result;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -98,7 +93,9 @@ export class Cache {
|
|||||||
img.crossOrigin = 'anonymous';
|
img.crossOrigin = 'anonymous';
|
||||||
}
|
}
|
||||||
img.src = src;
|
img.src = src;
|
||||||
if (img.complete === true) {
|
if (/^data:/.test(src)) {
|
||||||
|
resolve(img);
|
||||||
|
} else if (img.complete === true) {
|
||||||
// Inline XML images may fail to parse, throwing an Error later on
|
// Inline XML images may fail to parse, throwing an Error later on
|
||||||
setTimeout(() => resolve(img), 500);
|
setTimeout(() => resolve(img), 500);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {fromCodePoint, toCodePoints} from 'css-line-break';
|
import {fromCodePoint, toCodePoints} from 'css-line-break';
|
||||||
|
import {isSVGForeignObjectElement} from '../dom/node-parser';
|
||||||
|
|
||||||
const testRangeBounds = (document: Document) => {
|
const testRangeBounds = (document: Document) => {
|
||||||
const TEST_HEIGHT = 123;
|
const TEST_HEIGHT = 123;
|
||||||
@ -156,15 +157,38 @@ export const createForeignObjectSVG = (
|
|||||||
return svg;
|
return svg;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadSerializedSVG = (svg: Node): Promise<HTMLImageElement> => {
|
export const serializeSvg = (svg: SVGSVGElement | SVGForeignObjectElement, encoding = ''): string => {
|
||||||
return new Promise((resolve, reject) => {
|
const svgPrefix = 'data:image/svg+xml';
|
||||||
|
const selializedSvg = new XMLSerializer().serializeToString(svg);
|
||||||
|
const encodedSvg = encoding === 'base64' ? btoa(selializedSvg) : encodeURIComponent(selializedSvg);
|
||||||
|
return `${svgPrefix}${encoding && `;${encoding}`},${encodedSvg}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const INLINE_BASE64 = /^data:image\/.*;base64,/i;
|
||||||
|
export const deserializeSvg = (svg: string): SVGSVGElement | SVGForeignObjectElement => {
|
||||||
|
const encodedSvg = INLINE_BASE64.test(svg) ? atob(svg) : decodeURIComponent(svg);
|
||||||
|
const domParser = new DOMParser();
|
||||||
|
const document = domParser.parseFromString(encodedSvg, 'image/svg+xml');
|
||||||
|
const parserError = document.querySelector('parsererror');
|
||||||
|
if (parserError) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Expected 0-1 arguments, but got 2.
|
||||||
|
throw new Error('Deserialisation failed', {cause: parserError});
|
||||||
|
}
|
||||||
|
const {documentElement} = document;
|
||||||
|
const firstSvgChild = documentElement.firstElementChild;
|
||||||
|
return firstSvgChild && isSVGForeignObjectElement(firstSvgChild)
|
||||||
|
? (documentElement as unknown as SVGForeignObjectElement)
|
||||||
|
: (documentElement as unknown as SVGSVGElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadSerializedSVG = (svg: SVGSVGElement | SVGForeignObjectElement): Promise<HTMLImageElement> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => resolve(img);
|
img.onload = () => resolve(img);
|
||||||
img.onerror = reject;
|
img.onerror = reject;
|
||||||
|
img.src = serializeSvg(svg, 'charset=utf-8');
|
||||||
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(new XMLSerializer().serializeToString(svg))}`;
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const FEATURES = {
|
export const FEATURES = {
|
||||||
get SUPPORT_RANGE_BOUNDS(): boolean {
|
get SUPPORT_RANGE_BOUNDS(): boolean {
|
||||||
|
@ -122,6 +122,7 @@ export const isOLElement = (node: Element): node is HTMLOListElement => node.tag
|
|||||||
export const isInputElement = (node: Element): node is HTMLInputElement => node.tagName === 'INPUT';
|
export const isInputElement = (node: Element): node is HTMLInputElement => node.tagName === 'INPUT';
|
||||||
export const isHTMLElement = (node: Element): node is HTMLHtmlElement => node.tagName === 'HTML';
|
export const isHTMLElement = (node: Element): node is HTMLHtmlElement => node.tagName === 'HTML';
|
||||||
export const isSVGElement = (node: Element): node is SVGSVGElement => node.tagName === 'svg';
|
export const isSVGElement = (node: Element): node is SVGSVGElement => node.tagName === 'svg';
|
||||||
|
export const isSVGForeignObjectElement = (node: Element): node is SVGSVGElement => node.tagName === 'foreignObject';
|
||||||
export const isBodyElement = (node: Element): node is HTMLBodyElement => node.tagName === 'BODY';
|
export const isBodyElement = (node: Element): node is HTMLBodyElement => node.tagName === 'BODY';
|
||||||
export const isCanvasElement = (node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS';
|
export const isCanvasElement = (node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS';
|
||||||
export const isVideoElement = (node: Element): node is HTMLVideoElement => node.tagName === 'VIDEO';
|
export const isVideoElement = (node: Element): node is HTMLVideoElement => node.tagName === 'VIDEO';
|
||||||
|
@ -13,8 +13,8 @@ export class IFrameElementContainer extends ElementContainer {
|
|||||||
constructor(context: Context, iframe: HTMLIFrameElement) {
|
constructor(context: Context, iframe: HTMLIFrameElement) {
|
||||||
super(context, iframe);
|
super(context, iframe);
|
||||||
this.src = iframe.src;
|
this.src = iframe.src;
|
||||||
this.width = parseInt(iframe.width, 10) || 0;
|
this.width = parseInt(iframe.width, 10) || iframe.offsetWidth || 0;
|
||||||
this.height = parseInt(iframe.height, 10) || 0;
|
this.height = parseInt(iframe.height, 10) || iframe.offsetHeight || 0;
|
||||||
this.backgroundColor = this.styles.backgroundColor;
|
this.backgroundColor = this.styles.backgroundColor;
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
|
@ -1,16 +1,50 @@
|
|||||||
import {ElementContainer} from '../element-container';
|
import {ElementContainer} from '../element-container';
|
||||||
import {Context} from '../../core/context';
|
import {Context} from '../../core/context';
|
||||||
|
import {serializeSvg, deserializeSvg} from '../../core/features';
|
||||||
|
|
||||||
export class ImageElementContainer extends ElementContainer {
|
export class ImageElementContainer extends ElementContainer {
|
||||||
src: string;
|
src: string;
|
||||||
intrinsicWidth: number;
|
intrinsicWidth: number = 0;
|
||||||
intrinsicHeight: number;
|
intrinsicHeight: number = 0;
|
||||||
|
isSVG: boolean;
|
||||||
|
|
||||||
|
private static SVG = /\.svg(?:\?.*)?$/i;
|
||||||
|
private static INLINED_SVG = /^data:image\/svg\+xml/i;
|
||||||
|
private static IS_FIRE_FOX = /firefox/i.test(navigator?.userAgent);
|
||||||
|
|
||||||
constructor(context: Context, img: HTMLImageElement) {
|
constructor(context: Context, img: HTMLImageElement) {
|
||||||
super(context, img);
|
super(context, img);
|
||||||
this.src = img.currentSrc || img.src;
|
this.src = img.currentSrc || img.src;
|
||||||
this.intrinsicWidth = img.naturalWidth;
|
this.isSVG = this.isSvg() || this.isInlinedSvg();
|
||||||
this.intrinsicHeight = img.naturalHeight;
|
|
||||||
this.context.cache.addImage(this.src);
|
this.context.cache.addImage(this.src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isInlinedSvg = () => ImageElementContainer.INLINED_SVG.test(this.src);
|
||||||
|
private isSvg = () => ImageElementContainer.SVG.test(this.src);
|
||||||
|
|
||||||
|
public setup(img: HTMLImageElement) {
|
||||||
|
if (this.isSvg()) return;
|
||||||
|
|
||||||
|
if (this.isInlinedSvg()) {
|
||||||
|
const [, inlinedSvg] = this.src.split(',');
|
||||||
|
const svgElement = deserializeSvg(inlinedSvg);
|
||||||
|
const {
|
||||||
|
width: {baseVal: widthBaseVal},
|
||||||
|
height: {baseVal: heightBaseVal}
|
||||||
|
} = svgElement;
|
||||||
|
|
||||||
|
if (ImageElementContainer.IS_FIRE_FOX) {
|
||||||
|
widthBaseVal.valueAsString = widthBaseVal.value.toString();
|
||||||
|
heightBaseVal.valueAsString = heightBaseVal.value.toString();
|
||||||
|
img.src = serializeSvg(svgElement, 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.intrinsicWidth = widthBaseVal.value;
|
||||||
|
this.intrinsicHeight = heightBaseVal.value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.intrinsicWidth = img.naturalWidth;
|
||||||
|
this.intrinsicHeight = img.naturalHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {ElementContainer} from '../element-container';
|
import {ElementContainer} from '../element-container';
|
||||||
import {parseBounds} from '../../css/layout/bounds';
|
import {parseBounds} from '../../css/layout/bounds';
|
||||||
import {Context} from '../../core/context';
|
import {Context} from '../../core/context';
|
||||||
|
import {serializeSvg} from '../../core/features';
|
||||||
|
|
||||||
export class SVGElementContainer extends ElementContainer {
|
export class SVGElementContainer extends ElementContainer {
|
||||||
svg: string;
|
svg: string;
|
||||||
@ -9,12 +10,12 @@ export class SVGElementContainer extends ElementContainer {
|
|||||||
|
|
||||||
constructor(context: Context, img: SVGSVGElement) {
|
constructor(context: Context, img: SVGSVGElement) {
|
||||||
super(context, img);
|
super(context, img);
|
||||||
const s = new XMLSerializer();
|
|
||||||
const bounds = parseBounds(context, img);
|
const bounds = parseBounds(context, img);
|
||||||
img.setAttribute('width', `${bounds.width}px`);
|
img.setAttribute('width', `${bounds.width}px`);
|
||||||
img.setAttribute('height', `${bounds.height}px`);
|
img.setAttribute('height', `${bounds.height}px`);
|
||||||
|
|
||||||
this.svg = `data:image/svg+xml,${encodeURIComponent(s.serializeToString(img))}`;
|
this.svg = serializeSvg(img);
|
||||||
this.intrinsicWidth = img.width.baseVal.value;
|
this.intrinsicWidth = img.width.baseVal.value;
|
||||||
this.intrinsicHeight = img.height.baseVal.value;
|
this.intrinsicHeight = img.height.baseVal.value;
|
||||||
|
|
||||||
|
@ -270,24 +270,35 @@ export class CanvasRenderer extends Renderer {
|
|||||||
curves: BoundCurves,
|
curves: BoundCurves,
|
||||||
image: HTMLImageElement | HTMLCanvasElement
|
image: HTMLImageElement | HTMLCanvasElement
|
||||||
): void {
|
): void {
|
||||||
if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) {
|
if (image) {
|
||||||
const box = contentBox(container);
|
const isContainerWSizes = container.intrinsicWidth > 0 && container.intrinsicHeight > 0;
|
||||||
const path = calculatePaddingBoxPath(curves);
|
const isSVGContainer =
|
||||||
this.path(path);
|
container instanceof SVGElementContainer ||
|
||||||
this.ctx.save();
|
(container instanceof ImageElementContainer && container.isSVG);
|
||||||
this.ctx.clip();
|
if (isContainerWSizes || isSVGContainer) {
|
||||||
this.ctx.drawImage(
|
const box = contentBox(container);
|
||||||
image,
|
const path = calculatePaddingBoxPath(curves);
|
||||||
0,
|
this.path(path);
|
||||||
0,
|
this.ctx.save();
|
||||||
container.intrinsicWidth,
|
this.ctx.clip();
|
||||||
container.intrinsicHeight,
|
if (isContainerWSizes) {
|
||||||
box.left,
|
this.ctx.drawImage(
|
||||||
box.top,
|
image,
|
||||||
box.width,
|
0,
|
||||||
box.height
|
0,
|
||||||
);
|
container.intrinsicWidth,
|
||||||
this.ctx.restore();
|
container.intrinsicHeight,
|
||||||
|
box.left,
|
||||||
|
box.top,
|
||||||
|
box.width,
|
||||||
|
box.height
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// As usual it won't work in FF. https://bugzilla.mozilla.org/show_bug.cgi?id=700533
|
||||||
|
this.ctx.drawImage(image, box.left, box.top, box.width, box.height);
|
||||||
|
}
|
||||||
|
this.ctx.restore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,6 +314,7 @@ export class CanvasRenderer extends Renderer {
|
|||||||
if (container instanceof ImageElementContainer) {
|
if (container instanceof ImageElementContainer) {
|
||||||
try {
|
try {
|
||||||
const image = await this.context.cache.match(container.src);
|
const image = await this.context.cache.match(container.src);
|
||||||
|
container.setup(image);
|
||||||
this.renderReplacedElement(container, curves, image);
|
this.renderReplacedElement(container, curves, image);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.error(`Error loading image ${container.src}`);
|
this.context.logger.error(`Error loading image ${container.src}`);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {RenderConfigurations} from './canvas-renderer';
|
import {RenderConfigurations} from './canvas-renderer';
|
||||||
import {createForeignObjectSVG} from '../../core/features';
|
import {createForeignObjectSVG, loadSerializedSVG} from '../../core/features';
|
||||||
import {asString} from '../../css/types/color';
|
import {asString} from '../../css/types/color';
|
||||||
import {Renderer} from '../renderer';
|
import {Renderer} from '../renderer';
|
||||||
import {Context} from '../../core/context';
|
import {Context} from '../../core/context';
|
||||||
@ -48,13 +48,3 @@ export class ForeignObjectRenderer extends Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadSerializedSVG = (svg: Node): Promise<HTMLImageElement> =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
resolve(img);
|
|
||||||
};
|
|
||||||
img.onerror = reject;
|
|
||||||
|
|
||||||
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(new XMLSerializer().serializeToString(svg))}`;
|
|
||||||
});
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user