#!/usr/bin/env python3 """Requested provisional David layout only; no production printing or finish masks.""" from pathlib import Path import hashlib,json,os,subprocess from xml.sax.saxutils import escape import numpy as np from PIL import Image,ImageDraw P=Path(__file__).resolve().parent R=next(p for p in P.parents if (p/'docs/card-workspace.md').is_file()) ART=P.parents[1]/'source/art-master.png' sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest() original=sha(ART) fonts=json.loads((R/'fonts/manifest.json').read_text()) for name in ['P052-Bold.otf','SanctificationP052-Medium.otf']: assert sha(R/'fonts'/name)==fonts['files'][name]['sha256'] (P/'fonts.conf').write_text(f'{R/"fonts"}{P/"font-cache"}') env=dict(os.environ,FONTCONFIG_FILE=str(P/'fonts.conf')) def ink(*args):return subprocess.check_output(['inkscape',*map(str,args)],env=env,text=True,stderr=subprocess.PIPE) def render(src,dst):ink(src,'--export-type=png',f'--export-filename={dst}','--export-width=2000','--export-height=2800') # Hand-authored silhouette of the FRONT hanging fold, in native 1060 x 1484 coordinates. # Keep whole cloth pixels and internal lead seams, with no color-based alpha or hole repair. head='' path="M -5 1484 C 27 1398 52 1338 93 1302 C 134 1277 194 1254 239 1237 C 275 1220 299 1227 302 1284 C 291 1269 279 1257 262 1260 C 265 1304 272 1358 282 1402 L 292 1484 Z" scale=2000/1060 clip=f'' fragment_svg=P/'mantle-overlap-preview.svg' fragment_svg.write_text(head+clip+'') render(fragment_svg,P/'mantle-overlap-preview.png') alpha=np.asarray(Image.open(P/'mantle-overlap-preview.png').convert('RGBA').getchannel('A')) (P/'mantle-contour.json').write_text(json.dumps({'method':'Hand-authored continuous SVG silhouette; source cloth RGB unchanged','nativeCanvas':[1060,1484],'path':path,'uniformScale':scale,'component':'Front hanging fold only; upright drapery excluded'},indent=2)+'\n') rows=[('title','DAVID',1490,255,138,'P052',700)] verses=['“I have found David','the son of Jesse, a man','after My own heart,','who will do all My will.”'] for i,t in enumerate(verses):rows.append((f'verse-{i}',t,1320,2306+i*85,80,'Sanctification P052',500)) rows.append(('reference','Acts 13:22 • NKJV',1320,2660,57,'Sanctification P052',500)) texts=''.join(f'{escape(text)}' for ident,text,x,y,size,family,weight in rows) head='' (P/'text-preview.svg').write_text(head+texts+'');render(P/'text-preview.svg',P/'text-preview.png') # Simple SVG geometry is intentionally undecorated until layout review. base='' frame='' panels='' overlap='' for name,fg in [('composed-preview',overlap),('without-overlap', '')]: src=P/(name+'.svg');src.write_text(head+base+frame+panels+fg+texts+'');render(src,P/(name+'-2000.png')) image=Image.open(P/(name+'-2000.png')).convert('RGB');image.resize((1000,1400),Image.Resampling.LANCZOS).save(P/(name+'-1000.png')) # Measure each independently rendered line so no clipping can hide an overlap. checks=[] for ident,text,x,y,size,family,weight in rows: node=next(v for v in texts.split('') if f'id="{ident}"' in v)+'' src=P/(ident+'-ink.svg');src.write_text(head+node+'');dst=P/(ident+'-ink.png');render(src,dst);bounds=Image.open(dst).convert('RGBA').getchannel('A').getbbox();assert bounds box=(1098,108,1882,332) if ident=='title' else (410,2240,1910,2700) assert bounds[0]>=box[0] and bounds[1]>=box[1] and bounds[2]<=box[2] and bounds[3]<=box[3],(ident,bounds,box) inkalpha=np.asarray(Image.open(dst).convert('RGBA').getchannel('A'));assert not np.any((inkalpha>0)&(alpha>0)),ident checks.append({'id':ident,'text':text,'font':family,'weight':weight,'size':size,'inkBounds':list(bounds),'safeBox':list(box),'mantleIntersectionPixels':0}) board=Image.new('RGB',(1000,750),'#11161c');d=ImageDraw.Draw(board) for i,(label,path) in enumerate([('Simple backing, no overlap',P/'without-overlap-1000.png'),('Provisional mantle overlap',P/'composed-preview-1000.png')]): d.text((i*500+12,12),label,fill='#f4ead4');im=Image.open(path);im.thumbnail((480,672));board.paste(im,(i*500+10,45)) board.save(P/'overlap-comparison.png') assert sha(ART)==original report={'status':'passed','scope':'Provisional composed layout preview only','artSHA256':original,'sourceArtworkUnchanged':True,'canvas':[2000,2800],'titleBacking':[1080,90,820,260],'verseBacking':[360,2200,1580,540],'palette':{'backing':'#f4ead4','text':'#263f50','rule':'#806438'},'fontManifestSHA256':sha(R/'fonts/manifest.json'),'textChecks':checks,'overlap':{'method':'Hand-authored continuous vector contour of front hanging mantle fold; same-canvas source fragment.','contour':'mantle-contour.json','note':'Review alpha edges before production; not a locked foreground layer.','letteringIntersectionPixels':0},'notPerformed':['Decorative asset generation','Final printing exports','Finish or glyph-protection masks','Harness fixture installation','GPU/moving-light validation']} (P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps({'status':'passed','preview':str((P/'composed-preview-1000.png').relative_to(R)),'inkChecks':checks},indent=2))