#!/usr/bin/env python3
"""Provisional bespoke Legendary decoration and gilded-pronoun preview for David."""
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
REV=P.parents[1]
R=next(p for p in P.parents if (p/'docs/card-workspace.md').is_file())
ART=REV/'source/art-master.png'
FOREGROUND=P.parent/'layout-preview-astra-v3/mantle-overlap-preview.png'
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
art_hash=sha(ART);foreground_hash=sha(FOREGROUND)
fm=json.loads((R/'fonts/manifest.json').read_text());font_files={'title':'P052-Bold.otf','verse':'SanctificationP052-Medium.otf','reference':'SanctificationP052-Medium.otf'}
for name in set(font_files.values()):assert sha(R/'fonts'/name)==fm['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).strip()
def render(src,dst):ink(src,'--export-type=png',f'--export-filename={dst}','--export-width=2000','--export-height=2800')
def fc_file(family):return Path(ink('dummy','--version')) if False else Path(subprocess.check_output(['fc-match','-f','%{file}',family],env=env,text=True).strip()).resolve()
assert fc_file('P052:style=Bold')==(R/'fonts/P052-Bold.otf').resolve()
assert fc_file('Sanctification P052:style=Medium')==(R/'fonts/SanctificationP052-Medium.otf').resolve()
head='');render(P/'text-preview.svg',P/'text-preview.png')
(P/'gilded-lettering-preview.svg').write_text(head+defs+gold_only+'');render(P/'gilded-lettering-preview.svg',P/'gilded-lettering-preview.png')
(P/'border-backing-preview.svg').write_text(head+defs+frame+panels+'');render(P/'border-backing-preview.svg',P/'border-backing-preview.png')
for name,gold in [('composed-preview',True),('plain-pronouns',False)]:
active=texts if gold else texts.replace('fill="url(#gold)" stroke="#5a3e17" stroke-width="4" paint-order="stroke fill"','fill="#263f50"')
src=P/(name+'.svg');src.write_text(head+defs+base+frame+panels+foreground+active+'');render(src,P/(name+'-2000.png'));Image.open(P/(name+'-2000.png')).convert('RGB').resize((1000,1400),Image.Resampling.LANCZOS).save(P/(name+'-1000.png'))
# Per-row actual ink, panel coverage and foreground-clearance checks.
backing=np.asarray(Image.open(P/'border-backing-preview.png').convert('RGBA').getchannel('A'));fg=np.asarray(Image.open(FOREGROUND).convert('RGBA').getchannel('A'));checks=[];verse_bounds=[]
for ident,parts,x,y,size,family,weight in rows:
row=next(t for t in texts.split('') if f'id="{ident}"' in t)+'';src=P/(ident+'-ink.svg');src.write_text(head+defs+row+'');dst=P/(ident+'-ink.png');render(src,dst);inkalpha=np.asarray(Image.open(dst).convert('RGBA').getchannel('A'));ys,xs=np.where(inkalpha>0);assert len(xs);bounds=[int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)];assert np.all(backing[inkalpha>0]==255),ident;assert not np.any((inkalpha>0)&(fg>0)),ident
role='title' if ident=='title' else ('reference' if ident=='reference' else 'verse');text=''.join(v for v,_ in parts);font=R/'fonts'/font_files[role];charset=ink('dummy','--version') if False else subprocess.check_output(['fc-query','--format','%{charset}',str(font)],text=True);coverage=set()
for span in charset.split():lohi=[int(v,16) for v in span.split('-')];coverage.update(range(lohi[0],lohi[-1]+1))
assert all(ord(c) in coverage for c in text),ident
checks.append({'id':ident,'text':text,'baseline':y,'font':family,'weight':weight,'size':size,'inkBounds':bounds,'glyphPixelsOutsideOpaqueBacking':0,'foregroundIntersectionPixels':0});verse_bounds.append(bounds) if role=='verse' else None
# Validate exact content and gilded tokens.
card=json.loads((REV/'card.json').read_text());assert ' '.join(c['text'] for c in checks if c['id'].startswith('verse-'))=='“'+card['excerpt']+'”';assert checks[-1]['text']==card['referenceDisplay']
gold_alpha=np.asarray(Image.open(P/'gilded-lettering-preview.png').convert('RGBA').getchannel('A'));assert np.count_nonzero(gold_alpha)>0;assert not np.any((gold_alpha>0)&(fg>0));assert np.all(backing[gold_alpha>0]==255)
# Center verse ink union between upper flourish and reference's fixed line-box top for this candidate.
union=[min(b[0] for b in verse_bounds),min(b[1] for b in verse_bounds),max(b[2] for b in verse_bounds),max(b[3] for b in verse_bounds)];upper_flourish_bottom=2163;reference_linebox_top=2613;above=union[1]-upper_flourish_bottom;below=reference_linebox_top-union[3]
# Comparison sheets.
def sheet(items,out):
board=Image.new('RGB',(1000,750),'#11161c');d=ImageDraw.Draw(board)
for i,(label,path) in enumerate(items):d.text((i*500+12,12),label,fill='#f4ead4');im=Image.open(path).convert('RGB');im.thumbnail((480,672));board.paste(im,(i*500+10,45))
board.save(P/out)
sheet([('Previous simple layout',P.parent/'layout-preview-astra-v3/composed-preview-1000.png'),('Legendary design pass',P/'composed-preview-1000.png')],'design-comparison.png')
sheet([('Shared teal pronouns',P/'plain-pronouns-1000.png'),('Gilded I / My / My',P/'composed-preview-1000.png')],'gilding-comparison.png')
# Enlarged bottom-panel typography comparison.
board=Image.new('RGB',(1520,430),'#11161c');d=ImageDraw.Draw(board)
for i,(label,path) in enumerate([('Shared teal pronouns',P/'plain-pronouns-1000.png'),('Gilded I / My / My',P/'composed-preview-1000.png')]):
d.text((i*760+12,12),label,fill='#f4ead4');crop=Image.open(path).convert('RGB').crop((230,1025,980,1390));crop.thumbnail((740,360));board.paste(crop,(i*760+10,52))
board.save(P/'gilding-detail.png')
assert sha(ART)==art_hash and sha(FOREGROUND)==foreground_hash
report={'status':'passed','scope':'Provisional Legendary frame/backing/typography and gilded-pronoun preview','canvas':[2000,2800],'sourceArtwork':{'path':str(ART.relative_to(R)),'sha256':art_hash,'unchanged':True},'foreground':{'path':str(FOREGROUND.relative_to(R)),'sha256':foreground_hash,'unchanged':True},'geometry':{'titleBackingPath':title_path,'verseBackingPath':verse_path,'verseUpperFlourishBottom':upper_flourish_bottom,'referenceLineBoxTop':reference_linebox_top},'palette':{'backingBase':'#f4ead4','standardText':'#263f50','ornament':'#98702a','frameEnamel':'#173e59','gildedTextGradient':['#f3d879','#c89732','#7b5317'],'gildedTextOutline':'#5a3e17'},'fontManifestSHA256':sha(R/'fonts/manifest.json'),'textChecks':checks,'spacing':{'verseInkUnion':union,'upperFlourishToVerseInk':above,'verseInkToReferenceLineBox':below,'centeringDifference':abs(above-below),'lineBaselineGap':94,'referenceToLowerFlourish':'review visually; candidate-specific'},'gilding':{'tokens':[{'text':'I','line':'verse-0'},{'text':'My','line':'verse-2'},{'text':'My','line':'verse-3'}],'separateEditableLayer':'gilded-lettering-preview.svg','separateAlphaPreview':'gilded-lettering-preview.png','glyphPixelsOutsideOpaqueBacking':0,'foregroundIntersectionPixels':0,'finishDecision':'Pending: static gilded ink is previewed; production must decide whether this alpha receives metallic finish or remains protected lettering.'},'notPerformed':['Final printing exports','Production finish/glyph/gilding masks','Harness fixture installation','GPU/moving-light validation'],'staticReview':'pending user review'}
(P/'preview-validation.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps({'status':'passed','spacing':report['spacing'],'gilding':report['gilding'],'preview':str((P/'composed-preview-1000.png').relative_to(R))},indent=2))