Yuri Papouski 2022-09-06 14:12:21 +00:00
parent af62018424
commit 7c7aee3698
5 changed files with 59 additions and 18 deletions

View File

@ -1,4 +1,5 @@
import {fromCodePoint, toCodePoints} from 'css-line-break';
import {isSVGForeignObjectElement} from '../dom/node-parser';
const testRangeBounds = (document: Document) => {
const TEST_HEIGHT = 123;
@ -156,15 +157,37 @@ export const createForeignObjectSVG = (
return svg;
};
export const loadSerializedSVG = (svg: Node): Promise<HTMLImageElement> => {
return new Promise((resolve, reject) => {
export const serializeSvg = (svg: SVGSVGElement | SVGForeignObjectElement, encoding: string = ''): string => {
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) {
// @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();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(new XMLSerializer().serializeToString(svg))}`;
img.src = serializeSvg(svg, 'charset=utf-8');
});
};
export const FEATURES = {
get SUPPORT_RANGE_BOUNDS(): boolean {

View File

@ -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 isHTMLElement = (node: Element): node is HTMLHtmlElement => node.tagName === 'HTML';
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 isCanvasElement = (node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS';
export const isVideoElement = (node: Element): node is HTMLVideoElement => node.tagName === 'VIDEO';

View File

@ -1,16 +1,42 @@
import {ElementContainer} from '../element-container';
import {Context} from '../../core/context';
import {serializeSvg, deserializeSvg} from '../../core/features';
export class ImageElementContainer extends ElementContainer {
src: string;
intrinsicWidth: number;
intrinsicHeight: number;
private static INLINE_SVG = /^data:image\/svg\+xml/i;
private static IS_FIRE_FOX = /firefox/i.test(navigator?.userAgent);
constructor(context: Context, img: HTMLImageElement) {
super(context, img);
this.src = img.currentSrc || img.src;
this.intrinsicWidth = img.naturalWidth;
this.intrinsicHeight = img.naturalHeight;
this.update();
this.context.cache.addImage(this.src);
}
private update() {
if (!this.intrinsicWidth || !this.intrinsicHeight || ImageElementContainer.IS_FIRE_FOX) {
if (ImageElementContainer.INLINE_SVG.test(this.src)) {
const [, inlinedSvg] = this.src.split(',');
const svgElement = deserializeSvg(inlinedSvg);
const {
width: {baseVal: widthBaseVal},
height: {baseVal: heightBaseVal}
} = svgElement;
widthBaseVal.valueAsString = widthBaseVal.value.toString();
heightBaseVal.valueAsString = heightBaseVal.value.toString();
this.src = serializeSvg(svgElement, 'base64');
this.intrinsicWidth = widthBaseVal.value;
this.intrinsicHeight = heightBaseVal.value;
return;
}
}
}
}

View File

@ -1,6 +1,7 @@
import {ElementContainer} from '../element-container';
import {parseBounds} from '../../css/layout/bounds';
import {Context} from '../../core/context';
import {serializeSvg} from '../../core/features';
export class SVGElementContainer extends ElementContainer {
svg: string;
@ -9,12 +10,12 @@ export class SVGElementContainer extends ElementContainer {
constructor(context: Context, img: SVGSVGElement) {
super(context, img);
const s = new XMLSerializer();
const bounds = parseBounds(context, img);
img.setAttribute('width', `${bounds.width}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.intrinsicHeight = img.height.baseVal.value;

View File

@ -1,5 +1,5 @@
import {RenderConfigurations} from './canvas-renderer';
import {createForeignObjectSVG} from '../../core/features';
import {createForeignObjectSVG, loadSerializedSVG} from '../../core/features';
import {asString} from '../../css/types/color';
import {Renderer} from '../renderer';
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))}`;
});