From a5011b1ea26803fec42349a10296afbec6989cfe Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:34:38 +0200 Subject: [PATCH] fix(pdf): version the logo rasterisation cache so the font fix takes effect The SVG->PNG cache was keyed only by source path + mtime + size, so an override logo rasterised once WITHOUT fonts (text -> tofu) stayed cached after the font fix - the source SVG was unchanged, so the stale tofu PNG kept being served. Add a RASTER_VERSION component to the cache key; bumping it (v2-fonts) invalidates every prior rasterisation without clearing the cache dir by hand. --- backend/src/utils/resolveLogoFile.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/src/utils/resolveLogoFile.js b/backend/src/utils/resolveLogoFile.js index 92b48acb..1c2114e7 100644 --- a/backend/src/utils/resolveLogoFile.js +++ b/backend/src/utils/resolveLogoFile.js @@ -41,6 +41,12 @@ const { getStoragePath } = require('../config/storage'); const { getAppSetting } = require('./appSettings'); const logger = require('./logger'); +// Bump this whenever the SVG→PNG rendering environment changes in a way that +// changes output, to invalidate previously-cached rasterisations. +// v2 (2026-06): backend image now ships fonts (fontconfig + brand fonts), +// so SVG logos with live render their text instead of tofu boxes. +const RASTER_VERSION = 'v2-fonts'; + const SUPPORTED_EXT = /\.(png|jpe?g)$/i; // Formats PDFKit can't embed directly but `sharp` can rasterise into // PNG for us. We transparently convert + cache. @@ -96,11 +102,14 @@ async function rasteriseToPng(sourcePath, storageRoot) { const stat = fs.statSync(sourcePath); const cacheDir = path.join(storageRoot, 'cache', 'logo-png'); fs.mkdirSync(cacheDir, { recursive: true }); - // Content-addressed cache: sha1(src path + mtime ns + size). - // Including mtime means re-uploading the source invalidates the - // cache entry naturally. + // Content-addressed cache: sha1(renderer version + src path + mtime ns + // + size). Including mtime means re-uploading the source invalidates the + // cache entry naturally. RASTER_VERSION is bumped whenever the rendering + // environment changes in a way that affects output (e.g. installing fonts + // so SVG stops rendering as tofu) — bumping it invalidates every + // previously-cached PNG without having to clear the cache dir by hand. const key = crypto.createHash('sha1') - .update(`${sourcePath}|${stat.mtimeMs}|${stat.size}`) + .update(`${RASTER_VERSION}|${sourcePath}|${stat.mtimeMs}|${stat.size}`) .digest('hex'); const cachedPath = path.join(cacheDir, `${key}.png`); if (fs.existsSync(cachedPath)) {