#!/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 ENTIRE connected drape, in native 1060 x 1484 coordinates.
# Keep whole cloth pixels and internal lead seams, with no color-based alpha or hole repair.
head='')
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':'Whole connected red drape, including the plane right of the curled front fold and its true lower edge against the stone ledge'},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))
for ident,text,x,y,size,family,weight in rows:
font=R/'fonts'/('P052-Bold.otf' if weight==700 else 'SanctificationP052-Medium.otf')
charset=subprocess.check_output(['fc-query','--format','%{charset}',str(font)],text=True)
coverage=set()
for span in charset.split():
bounds=[int(v,16) for v in span.split('-')];coverage.update(range(bounds[0],bounds[-1]+1))
assert all(ord(c) in coverage for c in text),ident
texts=''.join(f'{escape(text)}' for ident,text,x,y,size,family,weight in rows)
head='');render(P/'text-preview.svg',P/'text-preview.png')
# Simple SVG geometry is intentionally undecorated until layout review.
base=''
frame=''
panels=''
(P/'backing-review.svg').write_text(head+panels+'');render(P/'backing-review.svg',P/'backing-review.png')
backing_alpha=np.asarray(Image.open(P/'backing-review.png').convert('RGBA').getchannel('A'))
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
assert np.all(backing_alpha[inkalpha>0]==255),ident
checks.append({'id':ident,'text':text,'font':family,'weight':weight,'size':size,'inkBounds':list(bounds),'safeBox':list(box),'mantleIntersectionPixels':0,'glyphPixelsOutsideOpaqueBacking':0,'glyphCoverageVerified':True})
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':[500,2200,1440,540],'palette':{'backing':'#f4ead4','text':'#263f50','rule':'#806438'},'fontManifestSHA256':sha(R/'fonts/manifest.json'),'textChecks':checks,'overlap':{'method':'Hand-authored continuous vector contour of the entire connected red mantle drape; same-canvas source fragment.','contour':'mantle-contour.json','note':'Entire red cloth plane included; natural right silhouette against blue garment and lower ledge retained. Provisional contour pending aesthetic review.','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))
# The full cloth plane, not merely a corridor, must remain visible.
art_array=np.asarray(Image.open(ART).convert('RGB'))
fragment=np.asarray(Image.open(P/'mantle-overlap-preview.png').convert('RGBA'))
assert np.array_equal(fragment[:,:,:3][alpha==255],art_array[alpha==255])
assert (P/'text-preview.png').read_bytes()==(P.parent/'layout-preview-v2/text-preview.png').read_bytes()
board=Image.new('RGB',(1500,750),'#11161c');d=ImageDraw.Draw(board)
for i,(label,f) in enumerate([('Previous Astra: incomplete cloth',P.parent/'layout-preview-astra/composed-preview-1000.png'),('Full drape over original backing',P/'composed-preview-1000.png'),('Source art',ART)]):
d.text((i*500+12,12),label,fill='#f4ead4');im=Image.open(f);im.thumbnail((480,672));board.paste(im,(i*500+10,45))
board.save(P/'before-after.png')
board=Image.new('RGB',(1500,710),'#11161c');d=ImageDraw.Draw(board)
for i,(label,f) in enumerate([('Previous Astra',P.parent/'layout-preview-astra/composed-preview-2000.png'),('Full connected cloth',P/'composed-preview-2000.png'),('Source artwork',ART)]):
d.text((i*500+12,12),label,fill='#f4ead4');im=Image.open(f).crop((100,1970,980,2800));im.thumbnail((480,645));board.paste(im,(i*500+10,45))
board.save(P/'junction-comparison.png')
composed=np.asarray(Image.open(P/'composed-preview-2000.png').convert('RGB'))
points=[(335,1150),(350,1200),(370,1260),(375,1310)]
for x,y in points:
xx,yy=round(x*scale),round(y*scale)
assert np.array_equal(composed[yy,xx],art_array[yy,xx]),(x,y)
# A broad interior swath explicitly covers the formerly missing right-hand cloth plane.
plane=Image.new('L',(2000,2800));ImageDraw.Draw(plane).polygon([(round(x*scale),round(y*scale)) for x,y in [(320,1160),(370,1160),(382,1220),(390,1300),(383,1330),(295,1330),(320,1290)]],fill=255)
plane_pixels=np.asarray(plane)>0
assert np.array_equal(composed[plane_pixels],art_array[plane_pixels])
report.update({'sourceFragmentOpaquePixelsMatch':True,'letteringPNGMatchesV2':True,'glyphCoverageVerified':True,'glyphPixelsOutsideOpaqueBacking':0,'formerlyMissingClothPlane':{'nativeInteriorPolygon':[(320,1160),(370,1160),(382,1220),(390,1300),(383,1330),(295,1330),(320,1290)],'preservedSourcePixels':int(plane_pixels.sum()),'spotChecksNative':points,'assessmentBasis':'Full enlarged silhouette compared visually with source; broad interior pixel check only supports that review.'},'staticReview':{'status':'provisional','assessment':'The entire red drape is retained from above the backing, down its true right boundary beside the blue garment, across its dark lower boundary at the stone ledge, and continuously into the front hanging fold. No cream wedge cuts the internal red cloth plane. The prior front-fold-only contour and swept backing are replaced.','limitation':'The source itself has a near-horizontal lower drape edge at the stone ledge. This natural source edge remains visible over the backing; this is a provisional silhouette, not an approved final layer.'}})
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps({'preservedClothPlanePixels':int(plane_pixels.sum()),'glyphPixelsOutsideOpaqueBacking':0,'letteringPNGMatchesV2':True},indent=2))