Fixed handling of svg images with no width/height attributes

Such images were not appearing in resulting canvas when using firefox.
The details of the problem are explained in https://bugzilla.mozilla.org/show_bug.cgi?id=700533 and https://webcompat.com/issues/64352
but a rough summary is that firefox won't render svg with no width/height attribute via drawImage.
This fix thus recreates the missing attributes from the viewport one when they are missing
This commit is contained in:
Sebastien Ponce 2023-01-12 11:40:28 +01:00
parent 007a73293a
commit 9bc852f9f5

View File

@ -265,19 +265,53 @@ export class CanvasRenderer extends Renderer {
}); });
} }
renderReplacedElement( async fixSVGImage(
container: ReplacedElementContainer,
image: HTMLImageElement | HTMLCanvasElement
): Promise<HTMLImageElement | HTMLCanvasElement> {
if (image instanceof HTMLImageElement && container.intrinsicWidth == 0 && container.intrinsicHeight == 0) {
// in such case we will to use the viewport attribute
// and we preprocess the svg image and add the width/height svg attributes to it so that drawImage works on firefox
// See https://bugzilla.mozilla.org/show_bug.cgi?id=700533 and https://webcompat.com/issues/64352
var img: HTMLImageElement = image;
var response = await fetch(img.src);
var str = await response.text();
var doc = new window.DOMParser().parseFromString(str, 'text/xml');
var svgElem = doc.documentElement;
if (svgElem) {
var viewBox = svgElem.getAttribute('viewBox');
var match = viewBox?.match(/\w+ \w+ (\w+) (\w+)/);
if (match) {
var viewBoxWidth = match[1];
var viewBoxHeight = match[2];
container.intrinsicWidth = +viewBoxWidth;
container.intrinsicHeight = +viewBoxHeight;
svgElem.setAttribute('width', viewBoxWidth);
svgElem.setAttribute('height', viewBoxHeight);
var svgData = new XMLSerializer().serializeToString(svgElem);
img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
}
}
}
return image;
}
async renderReplacedElement(
container: ReplacedElementContainer, container: ReplacedElementContainer,
curves: BoundCurves, curves: BoundCurves,
image: HTMLImageElement | HTMLCanvasElement image: HTMLImageElement | HTMLCanvasElement
): void { ) {
if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) { if (image) {
// Special fix for displaying svg images with no width/height attributes
var img = await this.fixSVGImage(container, image);
if (container.intrinsicWidth > 0 && container.intrinsicHeight > 0) {
const box = contentBox(container); const box = contentBox(container);
const path = calculatePaddingBoxPath(curves); const path = calculatePaddingBoxPath(curves);
this.path(path); this.path(path);
this.ctx.save(); this.ctx.save();
this.ctx.clip(); this.ctx.clip();
this.ctx.drawImage( this.ctx.drawImage(
image, img,
0, 0,
0, 0,
container.intrinsicWidth, container.intrinsicWidth,
@ -290,6 +324,7 @@ export class CanvasRenderer extends Renderer {
this.ctx.restore(); this.ctx.restore();
} }
} }
}
async renderNodeContent(paint: ElementPaint): Promise<void> { async renderNodeContent(paint: ElementPaint): Promise<void> {
this.applyEffects(paint.getEffects(EffectTarget.CONTENT)); this.applyEffects(paint.getEffects(EffectTarget.CONTENT));
@ -303,20 +338,20 @@ 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);
this.renderReplacedElement(container, curves, image); await 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}`);
} }
} }
if (container instanceof CanvasElementContainer) { if (container instanceof CanvasElementContainer) {
this.renderReplacedElement(container, curves, container.canvas); await this.renderReplacedElement(container, curves, container.canvas);
} }
if (container instanceof SVGElementContainer) { if (container instanceof SVGElementContainer) {
try { try {
const image = await this.context.cache.match(container.svg); const image = await this.context.cache.match(container.svg);
this.renderReplacedElement(container, curves, image); await this.renderReplacedElement(container, curves, image);
} catch (e) { } catch (e) {
this.context.logger.error(`Error loading svg ${container.svg.substring(0, 255)}`); this.context.logger.error(`Error loading svg ${container.svg.substring(0, 255)}`);
} }
@ -903,7 +938,6 @@ export class CanvasRenderer extends Renderer {
} }
const stack = parseStackingContexts(element); const stack = parseStackingContexts(element);
await this.renderStack(stack); await this.renderStack(stack);
this.applyEffects([]); this.applyEffects([]);
return this.canvas; return this.canvas;