Good base to start generation

This commit is contained in:
2026-09-11 18:38:01 -07:00
parent 8355743ada
commit 8fcda5f2b7
50 changed files with 616 additions and 114 deletions

View File

@@ -0,0 +1,138 @@
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import assert from 'node:assert/strict';
const root = dirname(fileURLToPath(import.meta.url));
const layout = JSON.parse(readFileSync(resolve(root, 'layout.json')));
const card = JSON.parse(readFileSync(resolve(root, 'card.json')));
const { width: W, height: H, radius, frameInset: inset, fields } = layout;
const out = resolve(root, 'output');
for (const dir of ['layers', 'cards', 'masks', 'review']) mkdirSync(resolve(out, dir), { recursive: true });
const run = (bin, args, input) => execFileSync(bin, args, { input, maxBuffer: 128 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
const magick = process.env.MAGICK_BIN || 'magick';
const inkscape = process.env.INKSCAPE_BIN || 'inkscape';
const path = name => resolve(out, name);
const hash = data => createHash('sha256').update(data).digest('hex');
const escape = value => String(value).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('"', '&quot;');
const rect = ([x, y, w, h], attrs = String()) => `<rect x="${x}" y="${y}" width="${w}" height="${h}" ${attrs}/>`;
const svg = (body, w = W, h = H) => `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">${body}</svg>`;
const round = (x, y, w, h, r) => `M${x+r},${y}H${x+w-r}A${r},${r} 0 0 1 ${x+w},${y+r}V${y+h-r}A${r},${r} 0 0 1 ${x+w-r},${y+h}H${x+r}A${r},${r} 0 0 1 ${x},${y+h-r}V${y+r}A${r},${r} 0 0 1 ${x+r},${y}Z`;
const runtimeFrame = `<path fill="${layout.inkColor}" fill-rule="evenodd" d="M0,0H${W}V${H}H0Z ${round(inset,inset,W-2*inset,H-2*inset,radius-inset)}"/>`;
const frame = `<path fill="${layout.inkColor}" fill-rule="evenodd" d="${round(0,0,W,H,radius)} ${round(inset,inset,W-2*inset,H-2*inset,radius-inset)}"/>`;
const backing = layout.panels.map(box => rect(box, `fill="${layout.panelColor}"`)).join('') +
`<path d="M48 357H1952 M48 2183H1952" stroke="${layout.inkColor}" stroke-width="6"/>`;
const textValues = { title: card.name.toUpperCase(), subtitle: card.subtitle, category: card.category.toUpperCase(), reference: `${card.reference} · ${card.translation}` };
assert.equal(card.verseLines.join(' '), card.verse, 'Verse line breaks must preserve exact source text');
const textElement = (id, f, value) => `<text id="${id}" x="${f.x}" y="${f.y}" font-family="${f.font}" font-size="${f.size}" text-anchor="${f.anchor}" fill="${layout.inkColor}">${escape(value)}</text>`;
const textMarkup = Object.entries(fields).map(([id, f]) => id === 'verse'
? card.verseLines.map((line, i) => textElement(`verse-${i}`, { ...f, y: f.y + i*f.lineHeight }, `${i === 0 ? '“' : ''}${line}${i === card.verseLines.length-1 ? '”' : ''}`)).join('')
: textElement(id, f, textValues[id])).join('');
const exportSvg = (name, body, w = W, h = H) => {
writeFileSync(path(`${name}.svg`), svg(body, w, h));
run(inkscape, [path(`${name}.svg`), '--export-area-page', '--export-png-color-mode=RGBA_8', `--export-filename=${path(`${name}.png`)}`]);
};
const rgba = file => run(magick, [file, '-alpha', 'on', '-depth', '8', 'rgba:-']);
const rawPng = (bytes, file, channels = 'rgba') => run(magick, ['-size', `${W}x${H}`, '-depth', '8', `${channels}:-`, file], bytes);
const source = resolve(root, 'source/timothy-art.png');
const dimensions = run(magick, ['identify', '-format', '%w %h', source]).toString().split(' ').map(Number);
assert.equal(dimensions[0]/dimensions[1], W/H, 'Source requires an explicitly reviewed crop');
run(magick, [source, '-resize', `${W}x${H}`, '-colorspace', 'sRGB', path('layers/art.png')]);
const fonts = {};
for (const family of new Set(Object.values(fields).map(f => f.font))) {
const [matched, file] = run('fc-match', ['-f', '%{family}\n%{file}', family]).toString().split('\n');
assert.equal(matched, family, `Missing font: ${family}`);
fonts[family] = { file, sha256: hash(readFileSync(file)) };
}
exportSvg('layers/frame', frame);
exportSvg('layers/frame-runtime', runtimeFrame);
exportSvg('layers/backing', backing);
exportSvg('layers/text', textMarkup);
exportSvg('layers/normal-overlay', backing + frame);
exportSvg('layers/normal-runtime-overlay', backing + runtimeFrame);
exportSvg('layers/card-silhouette', `<rect width="${W}" height="${H}" rx="${radius}" fill="white"/>`);
const queries = run(inkscape, [path('layers/text.svg'), '--query-all']).toString();
writeFileSync(path('review/text-bounds.csv'), queries);
const glyphBounds = {};
for (const row of queries.trim().split('\n')) { const [id, ...numbers] = row.split(','); glyphBounds[id] = numbers.map(Number); }
function fits(box, bounds) {
return bounds?.length === 4 && bounds[0] >= box[0] && bounds[1] >= box[1] && bounds[0]+bounds[2] <= box[0]+box[2] && bounds[1]+bounds[3] <= box[1]+box[3];
}
for (const [id, f] of Object.entries(fields)) {
const ids = id === 'verse' ? card.verseLines.map((_, i) => `verse-${i}`) : [id];
for (const textId of ids) assert.ok(fits(f.box, glyphBounds[textId]), `${textId} overflows: ${glyphBounds[textId]}`);
}
// Measure an actual overlong text object to prove the check rejects overflow.
writeFileSync(path('review/overflow-fixture.svg'), svg(textElement('too-long', fields.title, 'TIMOTHY '.repeat(12))));
const overflow = run(inkscape, [path('review/overflow-fixture.svg'), '--query-id=too-long', '--query-all']).toString().trim().split('\n').find(row => row.startsWith('too-long,'));
assert.ok(!fits(fields.title.box, overflow.split(',').slice(1).map(Number)), 'Overflow fixture was not rejected');
const art = rgba(path('layers/art.png'));
const normalOverlay = rgba(path('layers/normal-runtime-overlay.png'));
const backingPixels = rgba(path('layers/backing.png'));
const framePixels = rgba(path('layers/frame.png'));
const runtimeFramePixels = rgba(path('layers/frame-runtime.png'));
const alphaAt = (buf, x, y) => buf[(y*W+x)*4+3];
let inspectedJoinPixels = 0;
// Interior joins, deliberately away from the rounded exterior and antialias bands.
for (const [x0,y0,w,h] of [[50,110,20,230],[1930,110,20,230],[50,2200,20,470],[1930,2200,20,470],[110,50,1780,20],[110,2730,1780,20]]) {
for (let y=y0;y<y0+h;y++) for (let x=x0;x<x0+w;x++) {
assert.equal(alphaAt(normalOverlay,x,y),255, `Gap at ${x},${y}`); inspectedJoinPixels++;
}
}
assert.equal(alphaAt(normalOverlay,1000,1200),0, 'Art window must be transparent');
assert.equal(alphaAt(framePixels,1000,2400),0, 'Textless must expose the lower art');
const seamFixture = Buffer.from(normalOverlay);
seamFixture[(150*W+59)*4+3] = 0;
assert.notEqual(alphaAt(seamFixture,59,150),255, 'One-pixel seam fixture was not detected');
const smooth = (a,b,v) => { const t=Math.max(0,Math.min(1,(v-a)/(b-a))); return t*t*(3-2*t); };
const variants = { normal: ['normal-runtime-overlay','text'], textless: ['frame-runtime'], borderless: ['backing','text'], boundless: [] };
const decodedHashes = {};
for (const [name, layers] of Object.entries(variants)) {
const args = [path('layers/art.png')];
for (const layer of layers) args.push(path(`layers/${layer}.png`), '-compose', 'Over', '-composite');
// Runtime textures stay opaque through their square corners; harness geometry owns the rounded silhouette.
args.push('-alpha', 'off', path(`cards/timothy-${name}.png`));
run(magick,args);
const pixels = rgba(path(`cards/timothy-${name}.png`));
for (let i = 3; i < pixels.length; i += 4) assert.equal(pixels[i], 255, `${name} runtime texture has a transparent pixel at index ${Math.floor(i / 4)}`);
decodedHashes[name] = hash(pixels);
const mask = Buffer.alloc(W*H);
for(let i=0;i<W*H;i++) {
const p=i*4, r=art[p]/255,g=art[p+1]/255,b=art[p+2]/255;
const hi=Math.max(r,g,b),lo=Math.min(r,g,b),sat=hi===0?0:(hi-lo)/hi;
let coverage=smooth(.08,.58,sat)*smooth(.015,.16,hi);
// This proof treats the frame and text panels as printed ink; finish is on visible art.
if (name==='normal'||name==='textless') coverage*=1-runtimeFramePixels[p+3]/255;
if (name==='normal'||name==='borderless') coverage*=1-backingPixels[p+3]/255;
mask[i]=Math.round(255*coverage);
}
rawPng(mask,path(`masks/timothy-${name}-finish.png`),'gray');
for(const width of [1000,500]) run(magick,[path(`cards/timothy-${name}.png`),'-resize',String(width),path(`cards/timothy-${name}-${width}.png`)]);
for(const [id,f] of Object.entries(fields)) if(layers.includes('text')) {
const [x,y,w,h]=f.box;
for(let yy=y;yy<y+h;yy++) for(let xx=x;xx<x+w;xx++) assert.equal(mask[yy*W+xx],0,`Finish under ${id}`);
}
}
// The textless variants retain the original art's finish coverage in former panel regions.
const boundlessMask=run(magick,[path('masks/timothy-boundless-finish.png'),'-depth','8','gray:-']);
assert.ok(boundlessMask.subarray(2250*W,2530*W).some(v=>v>0),'Stale lower text protection in Boundless');
// Repeat an independent composition and compare decoded pixels, ignoring PNG metadata.
const repeated=run(magick,[path('layers/art.png'),path('layers/normal-runtime-overlay.png'),'-compose','Over','-composite',path('layers/text.png'),'-compose','Over','-composite','-alpha','off','-depth','8','rgba:-']);
assert.equal(hash(repeated),decodedHashes.normal,'Composition is not repeatable');
const embeddedArt=`<image width="${W}" height="${H}" xlink:href="data:image/png;base64,${readFileSync(source).toString('base64')}"/>`;
// Inkscape's actual namespace is required for editable layer groups.
const group=(label,body)=>`<g inkscape:groupmode="layer" inkscape:label="${label}" id="${label}">${body}</g>`;
writeFileSync(path('timothy-editable.svg'),svg(group('Art',embeddedArt)+group('Backing',backing)+group('Frame',frame)+group('Text',textMarkup)).replace('http://www.inkscape.org/namespaces/inkscape','http://www.inkscape.org/namespaces/inkscape'));
const guides=Object.entries(fields).map(([id,f])=>rect(f.box,'fill="none" stroke="#db4652" stroke-width="4"')+`<text x="${f.box[0]}" y="${f.box[1]-12}" font-family="Noto Sans" font-size="30" fill="#db4652">${id}</text>`).join('')+rect(layout.focalRegion,'fill="none" stroke="#4289bd" stroke-width="5" stroke-dasharray="20 12"');
exportSvg('review/layout-guide',embeddedArt+backing+frame+textMarkup+guides);
let sheet=`<rect width="2240" height="920" fill="#f6f3eb"/><text x="60" y="62" font-family="Noto Serif" font-size="32" fill="#304747">Timothy / Common — four printings, one illustration</text>`;
for(const [i,name] of Object.keys(variants).entries()) {
const x=60+i*550;
sheet+=`<text x="${x}" y="120" font-family="Noto Sans" font-size="22" fill="#304747">${name[0].toUpperCase()+name.slice(1)}</text><image x="${x}" y="150" width="500" height="700" xlink:href="data:image/png;base64,${readFileSync(path(`cards/timothy-${name}-500.png`)).toString('base64')}"/>`;
}
exportSvg('review/printings',sheet,2240,920);
const report={status:'passed',template:layout.revision,source:{dimensions,sha256:hash(readFileSync(source)),transform:`Uniform scale ${W/dimensions[0]}; no crop. Master is upscaled from generated source.`},tools:{inkscape:run(inkscape,['--version']).toString().trim(),imagemagick:run(magick,['-version']).toString().split('\n')[0]},fonts,glyphBounds,inspectedJoinPixels,runtimeTextureAlpha:'Every card pixel is fully opaque; harness geometry owns the rounded silhouette',negativeChecks:['Rendered overlong title rejected','One-pixel alpha seam detected'],decodedHashes,finishRecipe:'Timothy saturation smoothstep(0.08,0.58) × value smoothstep(0.015,0.16), occluded by active printed-ink frame/backing',limitations:['Visual approval pending','No automated semantic or optical-centering judgment','No runtime lighting review performed by this script','Two-line alternate title layout not implemented in this proof']};
writeFileSync(path('validation.json'),JSON.stringify(report,null,2)+'\n');
console.log(`Built four printings and aligned finish masks. Validation passed (${inspectedJoinPixels} join pixels). See ${path('review/printings.png')}`);

View File

@@ -0,0 +1,13 @@
{
"id": "timothy-common-proof-v1",
"name": "Timothy",
"rarity": "Common",
"subtitle": "Faithful disciple",
"category": "Person",
"verse": "I have fought a good fight, I have finished my course, I have kept the faith:",
"verseLines": ["I have fought a good fight,", "I have finished my course,", "I have kept the faith:"],
"reference": "2 Timothy 4:7",
"translation": "KJV",
"source": "https://www.biblegateway.com/passage/?search=2+Timothy+4%3A7&version=KJV",
"context": "Paul's words to Timothy; not a quotation spoken by Timothy. The scene is an illustrative interpretation, not a claim to depict a documented encounter."
}

View File

@@ -0,0 +1,18 @@
{
"revision": "common-proof-v1",
"width": 2000,
"height": 2800,
"radius": 88.888889,
"frameInset": 60,
"panelColor": "#eee8d5",
"inkColor": "#304747",
"panels": [[48, 48, 1904, 312], [48, 2180, 1904, 572]],
"focalRegion": [160, 430, 1680, 1680],
"fields": {
"title": {"box": [160, 105, 1680, 105], "x": 1000, "y": 197, "size": 110, "font": "Noto Serif", "anchor": "middle"},
"subtitle": {"box": [160, 240, 1100, 64], "x": 160, "y": 288, "size": 44, "font": "Noto Sans", "anchor": "start"},
"category": {"box": [1400, 240, 440, 64], "x": 1840, "y": 288, "size": 38, "font": "Noto Sans", "anchor": "end"},
"verse": {"box": [160, 2250, 1680, 280], "x": 1000, "y": 2320, "size": 72, "lineHeight": 95, "font": "Noto Serif", "anchor": "middle"},
"reference": {"box": [160, 2580, 1680, 80], "x": 1000, "y": 2635, "size": 44, "font": "Noto Sans", "anchor": "middle"}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><rect x="48" y="48" width="1904" height="312" fill="#eee8d5"/><rect x="48" y="2180" width="1904" height="572" fill="#eee8d5"/><path d="M48 357H1952 M48 2183H1952" stroke="#304747" stroke-width="6"/></svg>

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><rect width="2000" height="2800" rx="88.888889" fill="white"/></svg>

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><path fill="#304747" fill-rule="evenodd" d="M0,0H2000V2800H0Z M88.888889,60H1911.111111A28.888889000000006,28.888889000000006 0 0 1 1940,88.888889V2711.111111A28.888889000000006,28.888889000000006 0 0 1 1911.111111,2740H88.888889A28.888889000000006,28.888889000000006 0 0 1 60,2711.111111V88.888889A28.888889000000006,28.888889000000006 0 0 1 88.888889,60Z"/></svg>

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><path fill="#304747" fill-rule="evenodd" d="M88.888889,0H1911.111111A88.888889,88.888889 0 0 1 2000,88.888889V2711.111111A88.888889,88.888889 0 0 1 1911.111111,2800H88.888889A88.888889,88.888889 0 0 1 0,2711.111111V88.888889A88.888889,88.888889 0 0 1 88.888889,0Z M88.888889,60H1911.111111A28.888889000000006,28.888889000000006 0 0 1 1940,88.888889V2711.111111A28.888889000000006,28.888889000000006 0 0 1 1911.111111,2740H88.888889A28.888889000000006,28.888889000000006 0 0 1 60,2711.111111V88.888889A28.888889000000006,28.888889000000006 0 0 1 88.888889,60Z"/></svg>

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><rect x="48" y="48" width="1904" height="312" fill="#eee8d5"/><rect x="48" y="2180" width="1904" height="572" fill="#eee8d5"/><path d="M48 357H1952 M48 2183H1952" stroke="#304747" stroke-width="6"/><path fill="#304747" fill-rule="evenodd" d="M88.888889,0H1911.111111A88.888889,88.888889 0 0 1 2000,88.888889V2711.111111A88.888889,88.888889 0 0 1 1911.111111,2800H88.888889A88.888889,88.888889 0 0 1 0,2711.111111V88.888889A88.888889,88.888889 0 0 1 88.888889,0Z M88.888889,60H1911.111111A28.888889000000006,28.888889000000006 0 0 1 1940,88.888889V2711.111111A28.888889000000006,28.888889000000006 0 0 1 1911.111111,2740H88.888889A28.888889000000006,28.888889000000006 0 0 1 60,2711.111111V88.888889A28.888889000000006,28.888889000000006 0 0 1 88.888889,60Z"/></svg>

After

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><rect x="48" y="48" width="1904" height="312" fill="#eee8d5"/><rect x="48" y="2180" width="1904" height="572" fill="#eee8d5"/><path d="M48 357H1952 M48 2183H1952" stroke="#304747" stroke-width="6"/><path fill="#304747" fill-rule="evenodd" d="M0,0H2000V2800H0Z M88.888889,60H1911.111111A28.888889000000006,28.888889000000006 0 0 1 1940,88.888889V2711.111111A28.888889000000006,28.888889000000006 0 0 1 1911.111111,2740H88.888889A28.888889000000006,28.888889000000006 0 0 1 60,2711.111111V88.888889A28.888889000000006,28.888889000000006 0 0 1 88.888889,60Z"/></svg>

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="title" x="1000" y="197" font-family="Noto Serif" font-size="110" text-anchor="middle" fill="#304747">TIMOTHY</text><text id="subtitle" x="160" y="288" font-family="Noto Sans" font-size="44" text-anchor="start" fill="#304747">Faithful disciple</text><text id="category" x="1840" y="288" font-family="Noto Sans" font-size="38" text-anchor="end" fill="#304747">PERSON</text><text id="verse-0" x="1000" y="2320" font-family="Noto Serif" font-size="72" text-anchor="middle" fill="#304747">“I have fought a good fight,</text><text id="verse-1" x="1000" y="2415" font-family="Noto Serif" font-size="72" text-anchor="middle" fill="#304747">I have finished my course,</text><text id="verse-2" x="1000" y="2510" font-family="Noto Serif" font-size="72" text-anchor="middle" fill="#304747">I have kept the faith:”</text><text id="reference" x="1000" y="2635" font-family="Noto Sans" font-size="44" text-anchor="middle" fill="#304747">2 Timothy 4:7 · KJV</text></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="2000" height="2800" viewBox="0 0 2000 2800"><text id="too-long" x="1000" y="197" font-family="Noto Serif" font-size="110" text-anchor="middle" fill="#304747">TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY TIMOTHY </text></svg>

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

@@ -0,0 +1,8 @@
svg9,164.268,117.25,1672.05,2528.31
title,745.295,117.25,511.94,80.85
subtitle,164.268,254.34,315.744,44.22
category,1696.51,260.45,139.802,27.93
verse-0,541.72,2264.2,916.56,73.08
verse-1,553.348,2359.2,892.008,73.08
verse-2,632.584,2454.56,733.536,72.72
reference,812.318,2601.56,377.476,44
1 svg9 164.268 117.25 1672.05 2528.31
2 title 745.295 117.25 511.94 80.85
3 subtitle 164.268 254.34 315.744 44.22
4 category 1696.51 260.45 139.802 27.93
5 verse-0 541.72 2264.2 916.56 73.08
6 verse-1 553.348 2359.2 892.008 73.08
7 verse-2 632.584 2454.56 733.536 72.72
8 reference 812.318 2601.56 377.476 44

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

@@ -0,0 +1,95 @@
{
"status": "passed",
"template": "common-proof-v1",
"source": {
"dimensions": [
1060,
1484
],
"sha256": "64431ddf894056d0936200117002ecce120fe553e2cce81bb4763e783e646393",
"transform": "Uniform scale 1.8867924528301887; no crop. Master is upscaled from generated source."
},
"tools": {
"inkscape": "Inkscape 1.2.2 (b0a8486541, 2022-12-01)",
"imagemagick": "Version: ImageMagick 7.1.2-31 Q16-HDRI x86_64 8309dc92a:20260903 https://imagemagick.org"
},
"fonts": {
"Noto Serif": {
"file": "/usr/share/fonts/truetype/noto/NotoSerif-Regular.ttf",
"sha256": "9d7583b7dc9e812afd32a14280c5cac3160012efe50c8d08938f4fea266ff67f"
},
"Noto Sans": {
"file": "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
"sha256": "89c3c497f618fdaa0b2d1e98fef93582f28c71debd2c4a8cdf41f190ced2909d"
}
},
"glyphBounds": {
"svg9": [
164.268,
117.25,
1672.05,
2528.31
],
"title": [
745.295,
117.25,
511.94,
80.85
],
"subtitle": [
164.268,
254.34,
315.744,
44.22
],
"category": [
1696.51,
260.45,
139.802,
27.93
],
"verse-0": [
541.72,
2264.2,
916.56,
73.08
],
"verse-1": [
553.348,
2359.2,
892.008,
73.08
],
"verse-2": [
632.584,
2454.56,
733.536,
72.72
],
"reference": [
812.318,
2601.56,
377.476,
44
]
},
"inspectedJoinPixels": 99200,
"runtimeTextureAlpha": "Every card pixel is fully opaque; harness geometry owns the rounded silhouette",
"negativeChecks": [
"Rendered overlong title rejected",
"One-pixel alpha seam detected"
],
"decodedHashes": {
"normal": "da8a7aa62f0bc4369527859f2f625adb656f201059a765efe6238198b36d6d64",
"textless": "3d64324a77eb31313c7123018580fd4ab834aeff6fe0b14b586814f1f8c9980c",
"borderless": "bffe0a4d7a58caeda72fdc990333e5466bd89a5ac7f19f86d241fbbb412cd6de",
"boundless": "d418f16ff392b1f2b3e237db0a67e1b218b7aeeb5f0d6927a4cde2b984ffb3a9"
},
"finishRecipe": "Timothy saturation smoothstep(0.08,0.58) × value smoothstep(0.015,0.16), occluded by active printed-ink frame/backing",
"limitations": [
"Visual approval pending",
"No automated semantic or optical-centering judgment",
"No runtime lighting review performed by this script",
"Two-line alternate title layout not implemented in this proof"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB