commit d03048ae1c310948faecd556507851569d49b69b
parent 7716753f67ea051610c6894826d75d04cfa85939
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 03:23:59 -0400
umtool: test that the preset's rsvg-convert children get FONTCONFIG_FILE; fc-match's escaped paths read back
IBM Plex Sans is not a system font, so a branded SVG rasterized without
the preset's FONTCONFIG_FILE falls back to some other sans, silently. A
stub RSVG_BIN records the variable each child is given and runs the real
rsvg-convert: an unbranded scroll card's child gets the system's config,
a branded one's the vendored fonts.conf. Bites: with rasterize() calling
rsvg-convert without childOpts, it fails.
fc-match backslash-escapes a comma in a path (`\,`), which a checkout path
with one would trip; the fonts.conf test unescapes before comparing, and
checks Plex Sans's variable file by its full path.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
1 file changed, 40 insertions(+), 3 deletions(-)
diff --git a/umtool/report-to-video/brand.test.mjs b/umtool/report-to-video/brand.test.mjs
@@ -9,10 +9,11 @@
import assert from "node:assert/strict";
import test from "node:test";
import { execFileSync, spawnSync } from "node:child_process";
-import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
+import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { mkdir } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
+import { fileURLToPath, pathToFileURL } from "node:url";
import {
BRAND_IDS, END_CARD_DEFAULT_SECONDS, END_CARD_DEFAULT_URL, END_CARD_ID, FACES, FONTCONFIG_FILE,
@@ -25,6 +26,7 @@ import { markPng, renderThumbnail, THUMB } from "./brand-cards.mjs";
import { IBM_PLEX_SANS } from "./svg-faces.mjs";
const mkdirP = (d) => mkdir(d, { recursive: true });
+const HERE = path.dirname(fileURLToPath(import.meta.url));
const PLAIN_RENDER = {
width: 1920, height: 1080, fps: 30, headerHeight: 56,
@@ -172,11 +174,14 @@ test("the kit: the lockup and the mark are outlines, not text", () => {
const hasFcMatch = spawnSync("fc-match", ["--version"], { stdio: "ignore" }).status === 0;
test("fonts.conf: the preset's children resolve the vendored faces, bold mono included", { skip: !hasFcMatch && "fc-match not available" }, () => {
+ // fc-match escapes its separators in a path (`\,`), so they are unescaped.
const match = (pattern) =>
- execFileSync("fc-match", [pattern, "file"], { encoding: "utf8", env: { ...process.env, FONTCONFIG_FILE } }).trim();
+ execFileSync("fc-match", [pattern, "file"], { encoding: "utf8", env: { ...process.env, FONTCONFIG_FILE } })
+ .trim()
+ .replace(/\\(.)/g, "$1");
assert.equal(match("IBM Plex Mono:bold"), `:file=${MONO_BOLD_FONT_FILE}`);
assert.equal(match("IBM Plex Mono"), `:file=${MONO_FONT_FILE}`);
- assert.match(match("IBM Plex Sans:bold"), /IBMPlexSans\[wdth\\?,wght\]\.ttf$/);
+ assert.equal(match("IBM Plex Sans:bold"), `:file=${path.join(FONTS_DIR, "IBMPlexSans[wdth,wght].ttf")}`);
});
// Rendering needs ImageMagick with Pango and rsvg-convert; a machine without
@@ -253,3 +258,35 @@ test("rendered: the scroll's SVG text takes the preset's face and its fit; unbra
rmSync(dir, { recursive: true, force: true });
}
});
+
+test("rendered: the preset's rsvg-convert children get the vendored fonts; unbranded ones the system's", { skip: !hasTools && "magick / rsvg-convert not available" }, () => {
+ // IBM Plex Sans is not a system font, so a branded SVG rasterized without
+ // FONTCONFIG_FILE would fall back to some other sans -- silently. A stub
+ // RSVG_BIN records the variable each child is given, then runs the real one.
+ const dir = mkdtempSync(path.join(tmpdir(), "rtv-brand-env-"));
+ try {
+ const log = path.join(dir, "env.log");
+ const stub = path.join(dir, "rsvg-stub.sh");
+ const real = process.env.RSVG_BIN ?? "rsvg-convert";
+ writeFileSync(stub, `#!/bin/sh\nprintf '%s\\n' "\${FONTCONFIG_FILE:-system}" >> '${log}'\nexec ${real} "$@"\n`, { mode: 0o755 });
+ const url = (f) => JSON.stringify(pathToFileURL(path.join(HERE, f)).href);
+ const script = `
+ import { mkdirSync } from "node:fs";
+ import { renderScrollCard } from ${url("render-cards.mjs")};
+ import { resolveBrandRender } from ${url("brand.mjs")};
+ const render = ${JSON.stringify({ ...PLAIN_RENDER, rail: { tracks: [{ key: "q", label: "Q", color: "#2bb38a" }] } })};
+ const ledger = [{ id: "a", date: "2023-06-30", scope: "q", value: 6, display: "6", label: "six", entryId: "c" }];
+ for (const [sub, r] of [["plain", render], ["brand", resolveBrandRender({ ...render, brand: "archilyzer-media" })]]) {
+ mkdirSync(${JSON.stringify(dir)} + "/" + sub + "/cards", { recursive: true });
+ await renderScrollCard({ type: "scroll", id: "z", hideRail: true }, r, ledger, ${JSON.stringify(dir)} + "/" + sub);
+ }
+ `;
+ const env = { ...process.env, RSVG_BIN: stub };
+ delete env.FONTCONFIG_FILE;
+ const r = spawnSync(process.execPath, ["--input-type=module", "-e", script], { env, encoding: "utf8" });
+ assert.equal(r.status, 0, r.stderr);
+ assert.deepEqual(readFileSync(log, "utf8").trim().split("\n"), ["system", FONTCONFIG_FILE]);
+ } finally {
+ rmSync(dir, { recursive: true, force: true });
+ }
+});