#!/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,ImageFilter 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-v4/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='' defs=''' ''' base=f'' # Narrow layered perimeter with restrained illuminated-manuscript foliage. frame=''' ''' # Bespoke cream panels: double gold rules, restrained grain and low-profile flourishes. title_path='M1090 58 H1897 L1938 99 V326 L1899 365 H1090 L1048 323 V100 Z' verse_path='M500 2075 H1898 Q1942 2075 1942 2119 V2720 Q1942 2764 1898 2764 H500 Q466 2764 466 2730 V2109 Q466 2075 500 2075 Z' def panel(path,inner,ident):return f'{inner}' title_inner='' verse_inner='' panels=panel(title_path,title_inner,'title-backing')+panel(verse_path,verse_inner,'verse-backing') foreground='' rows=[('title',[('DAVID',False)],1490,244,138,'P052',700),('verse-0',[('“',False),('I',True),(' have found David',False)],1320,2264,80,'Sanctification P052',500),('verse-1',[('the son of Jesse, a man',False)],1320,2358,80,'Sanctification P052',500),('verse-2',[('after ',False),('My',True),(' own heart,',False)],1320,2452,80,'Sanctification P052',500),('verse-3',[('who will do all ',False),('My',True),(' will.”',False)],1320,2546,80,'Sanctification P052',500),('reference',[('Acts 13:22 • NKJV',False)],1320,2655,57,'Sanctification P052',500)] def text_nodes(gold_only=False): out=[] for ident,parts,x,y,size,family,weight in rows: spans='' for value,gilded in parts: if gilded:style='fill="url(#gold)" stroke="#5a3e17" stroke-width="4" paint-order="stroke fill"' elif gold_only:style='fill="#000" fill-opacity="0" stroke="none"' else:style='fill="#263f50"' spans+=f'{escape(value)}' out.append(f'{spans}') return ''.join(out) texts=text_nodes();gold_only=text_nodes(True) (P/'text-preview.svg').write_text(head+defs+texts+'');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') # Seam guard: extend dark lead beneath the foreground's antialiased edge only where a backing is active. fg_alpha=Image.open(FOREGROUND).convert('RGBA').getchannel('A');expanded=fg_alpha.filter(ImageFilter.MaxFilter(5)) backing_alpha=Image.open(P/'border-backing-preview.png').convert('RGBA').getchannel('A') ga=np.asarray(expanded,dtype=np.uint16);ba=np.asarray(backing_alpha,dtype=np.uint16);guard_alpha=np.where((ga>0)&(ba>0),255,0).astype('uint8') # Restrict this compositing bleed to the lower backing; it must not alter exposed illustration edges elsewhere. guard_alpha[:2060,:]=0;guard_alpha[:,:450]=0;guard_alpha[:,1950:]=0 rgba=np.zeros((2800,2000,4),dtype=np.uint8);rgba[:,:,:3]=[29,23,18];rgba[:,:,3]=guard_alpha Image.fromarray(rgba,'RGBA').save(P/'foreground-occlusion-guard.png') guard='' 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+guard+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,'occlusionGuard':{'path':'foreground-occlusion-guard.png','darkLeadRGB':[29,23,18],'expansionRadiusMasterPixels':2,'activeRegion':[450,2060,1500,740]}},'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'} partial=(np.asarray(fg_alpha)>0)&(np.asarray(fg_alpha)<255)&(ba>0);ring=(ga>0)&(np.asarray(fg_alpha)==0)&(ba>0);active=np.zeros(partial.shape,bool);active[2060:,450:1950]=True assert np.all(guard_alpha[partial&active]>0) and np.all(guard_alpha[ring&active]>0) report['foreground']['occlusionGuard'].update({'partialEdgePixelsProtected':int(np.count_nonzero(partial&active)),'exteriorBleedPixels':int(np.count_nonzero(ring&active)),'unprotectedActivePartialEdgePixels':int(np.count_nonzero((partial&active)&(guard_alpha==0))),'purpose':'Prevent backing/rule color from showing through the antialiased illustrated edge; composed beneath foreground.'}) (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))