From 85163e4cbb48bc2921c4a136afcdd0216c797624 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 17 Jun 2026 22:50:28 +0200 Subject: [PATCH] fix: detect opaque SVG backgrounds without a viewBox Opaque background detection only read canvas dimensions from the viewBox, so an SVG that declares width and height on the root element but omits the viewBox slipped through and was tinted into a solid block. Fall back to the root svg width and height when no viewBox is present, and match attribute names exactly so stroke-width is not mistaken for the canvas width. Add tests for the no-viewBox cases. --- client/src/utils/__tests__/svg.test.ts | 18 ++++++++++++++++++ client/src/utils/svg.ts | 24 ++++++++++++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/client/src/utils/__tests__/svg.test.ts b/client/src/utils/__tests__/svg.test.ts index 6d9b05f6bf..9b21fd1bdd 100644 --- a/client/src/utils/__tests__/svg.test.ts +++ b/client/src/utils/__tests__/svg.test.ts @@ -118,6 +118,24 @@ describe('isMonochromeSvg', () => { ''; expect(isMonochromeSvg(svg)).toBe(true); }); + + it('rejects a background rect using root width/height when no viewBox is present', () => { + const svg = + ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); + + it('rejects a background rect when root dimensions carry units', () => { + const svg = + ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); + + it('does not confuse stroke-width with the canvas width', () => { + const svg = + ''; + expect(isMonochromeSvg(svg)).toBe(false); + }); }); }); diff --git a/client/src/utils/svg.ts b/client/src/utils/svg.ts index 454c02da9b..01aad53a9e 100644 --- a/client/src/utils/svg.ts +++ b/client/src/utils/svg.ts @@ -104,13 +104,27 @@ function extractColors(svg: string): string[] { } const VIEWBOX_REGEX = /viewBox\s*=\s*["']\s*[-\d.]+\s+[-\d.]+\s+([\d.]+)\s+([\d.]+)/i; +const SVG_TAG_REGEX = /]*>/i; const RECT_REGEX = /]*>/gi; function getAttr(tag: string, name: string): string | null { - const match = tag.match(new RegExp(`\\b${name}\\s*=\\s*["']([^"']*)["']`, 'i')); + const match = tag.match(new RegExp(`(?` at the origin spanning the - * viewBox). Such SVGs cannot be tinted via a CSS mask, since the opaque + * canvas). Such SVGs cannot be tinted via a CSS mask, since the opaque * background fills the whole area with the tint color instead of the glyph. + * Canvas dimensions come from the viewBox, falling back to the root `` + * width and height when no viewBox is present. */ function hasOpaqueBackground(svg: string): boolean { const viewBox = svg.match(VIEWBOX_REGEX); - const width = viewBox ? parseFloat(viewBox[1]) : null; - const height = viewBox ? parseFloat(viewBox[2]) : null; + const width = viewBox ? parseFloat(viewBox[1]) : rootDimension(svg, 'width'); + const height = viewBox ? parseFloat(viewBox[2]) : rootDimension(svg, 'height'); const rects = svg.match(RECT_REGEX) ?? []; for (const rect of rects) {