#!/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'{body}' 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-helvetica-{name}.svg'; p=OUT/'layers'/f'calling-disciples-SOL-fonts-helvetica-{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-helvetica-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-helvetica-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-helvetica-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-helvetica-{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 [('Nimbus Sans', 'NimbusSans-Regular.otf',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-helvetica-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-helvetica-{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-helvetica-{printing}-finish-2000.png' Image.fromarray(coverage).save(mask) checks=[] for size in LAYOUT['runtimeSizes']: cardpath=OUT/'cards'/f'calling-disciples-SOL-fonts-helvetica-{printing}-{size}.png' maskpath=OUT/'masks'/f'calling-disciples-SOL-fonts-helvetica-{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-helvetica-{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-helvetica-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-helvetica-printings.png' tiles=[] for printing in components: tile=OUT/'review'/f'calling-disciples-SOL-fonts-helvetica-{printing}-tile.png' run('magick',OUT/'cards'/f'calling-disciples-SOL-fonts-helvetica-{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-helvetica-finish-masks.png' masktiles=[] for printing in components: tile=OUT/'review'/f'calling-disciples-SOL-fonts-helvetica-{printing}-mask-tile.png' run('magick',OUT/'masks'/f'calling-disciples-SOL-fonts-helvetica-{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-helvetica-{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-helvetica-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-helvetica-{printing}-{size}.png')) newmask=np.asarray(Image.open(OUT/'masks'/f'calling-disciples-SOL-fonts-helvetica-{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-helvetica-validation.json').write_text(json.dumps(report,indent=2)+'\n') print(f'SOL v2 proof built and structurally validated: {ROOT}')