#!/usr/bin/env python3
"""Independent SOL v2 proof: deterministic SVG/Inkscape and ImageMagick composition."""
from pathlib import Path
import base64, hashlib, json, os, subprocess, xml.sax.saxutils as xml
import numpy as np
from PIL import Image
ROOT = Path(__file__).resolve().parent
OUT = ROOT / 'output'
for sub in ['layers', 'cards', 'masks', 'review', 'svg']:
(OUT / sub).mkdir(parents=True, exist_ok=True)
CARD = json.loads((ROOT/'card.json').read_text())
LAYOUT = json.loads((ROOT/'layout.json').read_text())
W,H = LAYOUT['canvas']
def run(*args, env=None):
return subprocess.check_output([str(x) for x in args], text=True, env=env).strip()
def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest()
font_config = OUT/'fonts.conf'
font_config.write_text(f'{ROOT / "fonts"}{OUT / "font-cache"}')
ENV = dict(os.environ, FONTCONFIG_FILE=str(font_config))
def ink(*args): return run('inkscape', *args, env=ENV)
def uri(path): return 'data:image/png;base64,'+base64.b64encode(path.read_bytes()).decode()
def svg(body): return f''
def rects(items, rounded=False):
# Opaque gradient foundations maintain the original overlay seams and finish occlusion.
body = ''
for index,(x,y,w,h) in enumerate(items):
radius = 28 if rounded else 0
body += f''
body += f''
body += f''
# Small, static geometry suggests book illumination without adding a finish effect.
for y in [299,2231,2694]:
body += f''
return body
def render(name, body):
s=OUT/'svg'/f'calling-disciples-SOL-fonts-noto-serif-{name}.svg'; p=OUT/'layers'/f'calling-disciples-SOL-fonts-noto-serif-{name}.png'
s.write_text(svg(body)); ink(s,'--export-type=png',f'--export-filename={p}','--export-width=2000','--export-height=2800')
return p
source = ROOT/CARD['sourceArtwork']; frame_source=ROOT/CARD['frameInput']
assert Image.open(source).size == tuple(LAYOUT['sourceTransform']['native'])
assert Image.open(frame_source).size == (W,H)
art=OUT/'layers'/'calling-disciples-SOL-fonts-noto-serif-art-master.png'
run('magick',source,'-filter','Lanczos','-resize','2000x2800', '-alpha','on', '-channel','A','-evaluate','set','100%','+channel',art)
frame=OUT/'layers'/'calling-disciples-SOL-fonts-noto-serif-frame-runtime.png'
frame.write_bytes(frame_source.read_bytes())
normal_body=rects(LAYOUT['normalBacking'])
borderless_body=rects(LAYOUT['borderlessBacking'],True)
normal_back=render('backing-normal',normal_body)
borderless_back=render('backing-borderless',borderless_body)
reference_display=f"{CARD['reference']} • {CARD['translation']}"
assert reference_display == CARD['referenceDisplay'] == 'Matthew 4:19 • NKJV'
name=xml.escape(CARD['name']); ref=xml.escape(reference_display)
text_body=f'{name}'
for i,line in enumerate(LAYOUT['verseLines']):
text_body += f'{xml.escape(line)}'
text_body+=f'{ref}'
text=render('text',text_body)
combined=OUT/'layers'/'calling-disciples-SOL-fonts-noto-serif-normal-runtime-overlay.png'
run('magick',normal_back,frame,'-compose','over','-composite',combined)
components={'normal':[combined,text], 'textless':[frame], 'borderless':[borderless_back,text], 'boundless':[]}
art_body=f''
frame_body=f''
for printing in components:
body=art_body
if printing in ['normal','borderless']: body+=f'{normal_body if printing=="normal" else borderless_body}'
if printing in ['normal','textless']: body+=frame_body
if printing in ['normal','borderless']: body+=f'{text_body}'
(OUT/f'calling-disciples-SOL-fonts-noto-serif-{printing}-editable.svg').write_text(svg(body))
report={'status':'pending','proof':'Controlled font comparison; exact SOL v2 art, frame and backing reused', 'inputs':{}, 'tools':{}, 'sourceTransform':LAYOUT['sourceTransform'], 'checks':{}, 'limitations':['Native artwork is 1060 × 1484 and uniformly enlarged, not native 2000 × 2800 generation.','No moving-light harness validation or human visual approval performed.','Cream vellum backing and title-only typography are text-treatment proof choices requiring visual review.','Finish recipe is a starting recipe from general documentation, not tuned or approved for this illustration.','Rebuild uses retained generated artwork; image generation itself is not deterministic.','Supplied scripture was reproduced verbatim without independent online verification.']}
for p in [source,frame_source,ROOT/'source'/'calling-disciples-SOL-prompt.txt',ROOT/'fonts'/'NotoSerif-Regular.ttf',ROOT/'fonts'/'NotoSans-Regular.ttf']:
report['inputs'][str(p.relative_to(ROOT))]={'sha256':sha(p)}
chosen_font=ROOT/'fonts'/CARD['fontChoice']['filename']
report['inputs'][str(chosen_font.relative_to(ROOT))]={'sha256':sha(chosen_font)}
report['tools']={tool:run(*cmd) for tool,cmd in {'inkscape':['inkscape','--version'],'imagemagick':['magick','--version'],'python':['python3','--version']}.items()}
report['tools']['numpy']=np.__version__; report['tools']['pillow']=Image.__version__
font_checks=[]
for family,filename,content in [('Noto Serif', 'NotoSerif-Regular.ttf',CARD['name']+CARD['excerpt']+reference_display),('Noto Sans','NotoSans-Regular.ttf',CARD['subjectType']+CARD['reference'])]:
font_path=ROOT/'fonts'/filename
selected=Path(run('fc-match','--format=%{file}',family,env=ENV)).resolve()
assert selected==font_path.resolve(), f'Unpinned font selected for {family}: {selected}'
charset=run('fc-query','--format=%{charset}',font_path)
ranges=[]
for item in charset.split():
ends=item.split('-'); ranges.append((int(ends[0],16),int(ends[-1],16)))
missing=[c for c in set(content) if not any(lo<=ord(c)<=hi for lo,hi in ranges)]
assert not missing, f'Missing glyphs in {filename}: {missing}'
font_checks.append({'family':family,'selected':str(font_path.relative_to(ROOT)),'missingGlyphs':missing})
expected_excerpt=' '.join(LAYOUT['verseLines'])
assert expected_excerpt == CARD['excerpt'], 'Verse altered in layout'
report['checks']['exactContent']={'pass':True,'excerpt':expected_excerpt,'reference':CARD['reference'],'referenceDisplay':reference_display,'referenceFormat':'Book chapter:verse • Translation','translation':CARD['translation'],'subjectType':CARD['subjectType'],'visibleCategory':False,'descriptor':None}
# Query actual, unclipped glyph bounds from the same Inkscape renderer.
bounds={}
for row in ink(OUT/'svg'/'calling-disciples-SOL-fonts-noto-serif-text.svg','--query-all').splitlines():
vals=row.split(',')
if len(vals)==5: bounds[vals[0]]=list(map(float,vals[1:]))
text_checks=[]
for key in ['title','verse-0','verse-1','reference']:
region_key='verse' if key.startswith('verse') else key
x,y,w,h=bounds[key]; rx,ry,rw,rh=LAYOUT['textRegions'][region_key]
passed=x>=rx and y>=ry and x+w<=rx+rw and y+h<=ry+rh
assert passed, f'Text overflow: {key} {bounds[key]}'
text_checks.append({'id':key,'glyphBounds':bounds[key],'region':LAYOUT['textRegions'][region_key],'pass':passed})
assert all(LAYOUT['fontSizes'][k]>=v for k,v in LAYOUT['fontMinimums'].items())
report['checks']['typography']={'pass':True,'glyphs':text_checks,'minimumSizes':True,'verseLineCount':2,'fontIsolation':'Pinned local font files via FONTCONFIG_FILE','fontChoice':CARD['fontChoice'],'fonts':font_checks}
rgb=np.asarray(Image.open(art).convert('RGB'),dtype=np.float32)/255
v=rgb.max(axis=2); mn=rgb.min(axis=2); sat=(v-mn)/np.maximum(v,1e-8)
def smooth(a,b,x):
t=np.clip((x-a)/(b-a),0,1); return t*t*(3-2*t)
base=smooth(.08,.58,sat)*smooth(.015,.16,v)
report['checks']['printings']={}
def alpha(p): return np.asarray(Image.open(p).convert('RGBA'))[:,:,3]/255.
for printing,layers in components.items():
master=OUT/'cards'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-2000.png'
args=['magick',art]
for layer in layers: args += [layer,'-compose','over','-composite']
args += ['-alpha','on','-channel','A','-evaluate','set','100%','+channel',master]
run(*args)
protection=np.zeros((H,W),dtype=np.float64)
if printing=='normal': protection=alpha(combined)
elif printing=='textless': protection=alpha(frame)
elif printing=='borderless': protection=alpha(borderless_back)
coverage=np.rint(base*(1-protection)*255).astype(np.uint8)
mask=OUT/'masks'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-finish-2000.png'
Image.fromarray(coverage).save(mask)
checks=[]
for size in LAYOUT['runtimeSizes']:
cardpath=OUT/'cards'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-{size}.png'
maskpath=OUT/'masks'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-finish-{size}.png'
if size!=2000:
run('magick',master,'-filter','Lanczos','-resize',f'{size}x{size*7//5}',cardpath)
# Data-map triangle filtering avoids ringing outside protected regions.
run('magick',mask,'-filter','Triangle','-resize',f'{size}x{size*7//5}',maskpath)
image=np.asarray(Image.open(cardpath).convert('RGBA')); m=np.asarray(Image.open(maskpath).convert('L'))
assert image.shape[:2]==(size*7//5,size)
assert np.all(image[:,:,3]==255)
corners=[(0,0),(0,size-1),(size*7//5-1,0),(size*7//5-1,size-1)]
corner_colors=[image[y,x,:3].tolist() for y,x in corners]
corner_masks=[int(m[y,x]) for y,x in corners]
if printing in ['normal','textless']:
assert all(c==[38,63,80] for c in corner_colors)
assert all(c==0 for c in corner_masks)
checks.append({'size':[size,size*7//5],'opaque':True,'cornerRGB':corner_colors,'cornerFinish':corner_masks,'decodedSHA256':hashlib.sha256(image.tobytes()).hexdigest()})
assert np.all(coverage[protection==1]==0)
report['checks']['printings'][printing]={'components':['art']+({'normal':['frame+backing','text'],'textless':['frame'],'borderless':['backing','text'],'boundless':[]}[printing]),'finishProtection':True,'exports':checks,'finishMean':float(coverage.mean()/255)}
# Composition repeatability at decoded pixel level.
second=OUT/'layers'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-repeat.png'
run(*(args[:-1]+[second]))
assert np.array_equal(np.asarray(Image.open(master)),np.asarray(Image.open(second)))
second.unlink()
report['checks']['repeatComposition']={'pass':True,'printings':list(components),'note':'All four ImageMagick compositions rerun and decoded pixels compared.'}
# Validate the overlay separately: opaque artwork cannot hide overlay holes.
overlay_alpha=np.asarray(Image.open(combined).convert('RGBA'))[:,:,3]
required=np.zeros((H,W),bool)
for x,y,w,h in LAYOUT['normalBacking']: required[y:y+h,x:x+w]=True
required |= np.asarray(Image.open(frame).convert('RGBA'))[:,:,3]==255
assert np.all(overlay_alpha[required]==255)
assert np.all(overlay_alpha[430:2100,160:1840]==0)
report['checks']['seams']={'pass':True,'requiredOpaquePixels':int(required.sum()),'clearWindowPixels':1670*1680,'fixture':'Injected transparent pixel inside a required join is rejected.'}
bad=overlay_alpha.copy(); bad[100,65]=0
assert not np.all(bad[required]==255)
for size in [1000,500]:
sampled=OUT/'layers'/f'calling-disciples-SOL-fonts-noto-serif-overlay-{size}.png'
run('magick',combined,'-filter','Lanczos','-resize',f'{size}x{size*7//5}',sampled)
a=np.asarray(Image.open(sampled).convert('RGBA'))[:,:,3]
factor=size/2000
# Perimeter plus deeply interior backing pixels: avoid intentional art-window AA band.
assert np.all(a[:int(48*factor),:]==255)
assert np.all(a[-int(48*factor):,:]==255)
for x,y,w,h in LAYOUT['normalBacking']:
xx,yy,ww,hh=[int(v*factor) for v in [x+12,y+12,w-24,h-24]]
assert np.all(a[yy:yy+hh,xx:xx+ww]==255)
report['checks']['downsampledOverlay']={'pass':True,'sizes':[1000,500],'note':'Opaque panel interiors/perimeter checked apart from intentional window antialiasing.'}
font=ROOT/'fonts'/'NotoSans-Regular.ttf'
review=OUT/'review'/'calling-disciples-SOL-fonts-noto-serif-printings.png'
tiles=[]
for printing in components:
tile=OUT/'review'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-tile.png'
run('magick',OUT/'cards'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-500.png','-background','#ede9df','-gravity','north','-splice','0x62','-font',font,'-fill','#263f50','-pointsize','25','-gravity','north','-annotate','+0+15',printing.title(),tile)
tiles.append(tile)
run('magick','montage',*tiles,'-tile','4x1','-geometry','500x762+18+18','-background','#ede9df',review)
maskreview=OUT/'review'/'calling-disciples-SOL-fonts-noto-serif-finish-masks.png'
masktiles=[]
for printing in components:
tile=OUT/'review'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-mask-tile.png'
run('magick',OUT/'masks'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-finish-500.png','-background','#ede9df','-gravity','north','-splice','0x62','-font',font,'-fill','#263f50','-pointsize','25','-gravity','north','-annotate','+0+15',printing.title(),tile)
masktiles.append(tile)
run('magick','montage',*masktiles,'-tile','4x1','-geometry','500x762+18+18','-background','#ede9df',maskreview)
# An actual 1000px sheet and 2000px sheet support inspection without pretending thumbnail review is enough.
for size in [1000,2000]:
run('magick','montage',*[OUT/'cards'/f'calling-disciples-SOL-fonts-noto-serif-{p}-{size}.png' for p in components],'-tile','4x1','-geometry',f'{size}x{size*7//5}+16+16','-background','#ede9df',OUT/'review'/f'calling-disciples-SOL-fonts-noto-serif-printings-{size}.png')
baseline=json.loads((ROOT/'source/reuse-metadata.json').read_text())
reuse={}
for relative,expected in baseline['sourceAssets'].items():
assert sha(ROOT/relative)==expected['originalSHA256']
reuse[relative]={'byteIdentical':True,'originalSHA256':expected['originalSHA256']}
assert hashlib.sha256(np.asarray(Image.open(art)).tobytes()).hexdigest()==baseline['artMasterDecodedSHA256']
for printing,sizes in baseline['unchangedPrintings'].items():
for size,expected in sizes.items():
new=np.asarray(Image.open(OUT/'cards'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-{size}.png'))
newmask=np.asarray(Image.open(OUT/'masks'/f'calling-disciples-SOL-fonts-noto-serif-{printing}-finish-{size}.png'))
assert hashlib.sha256(new.tobytes()).hexdigest()==expected['cardDecodedSHA256']
assert hashlib.sha256(newmask.tobytes()).hexdigest()==expected['maskDecodedSHA256']
report['checks']['unchangedArtworkFrame']={'pass':True,'sourceAssets':reuse,'artMasterPixelIdentical':True,'textlessBoundlessCardsAndMasksPixelIdenticalAtAllSizes':True,'baseline':'source/reuse-metadata.json; hashes captured from original SOL outputs'}
report['textTreatment']=LAYOUT['backingTreatment']
report['inputs']['source/font-comparison-brief.txt']={'sha256':sha(ROOT/CARD['revisionPrompt'])}
report['inputs']['source/reuse-metadata.json']={'sha256':sha(ROOT/'source/reuse-metadata.json')}
report['inputs']['layout.json']={'sha256':sha(ROOT/'layout.json')}
report['inputs']['card.json']={'sha256':sha(ROOT/'card.json')}
report['fontChoice']=CARD['fontChoice']
report['status']='passed'
(OUT/'calling-disciples-SOL-fonts-noto-serif-validation.json').write_text(json.dumps(report,indent=2)+'\n')
print(f'SOL v2 proof built and structurally validated: {ROOT}')