Import project sources
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
from pathlib import Path
|
||||
from io import BytesIO
|
||||
from xml.etree import ElementTree as ET
|
||||
import json,html,base64,re,hashlib
|
||||
from PIL import Image,ImageDraw
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.utils import ImageReader
|
||||
from compose import BOOK,ROOT,font,wrap,SCALE
|
||||
|
||||
# Each newly generated panel is an illustration asset. Typeset and assemble the
|
||||
# assets into the comic page, rather than placing letters over its characters.
|
||||
RECTS=json.loads((ROOT/'tmp/source-rects.json').read_text())
|
||||
LAYOUT=[]
|
||||
OVERLAY={
|
||||
(1,3):(450,896,537,200),
|
||||
(2,0):(40,24,944,120),(2,1):(450,530,530,200),
|
||||
(4,0):(40,32,580,150),(4,1):(40,566,450,100),
|
||||
(5,0):(36,28,520,74),(5,1):(614,32,376,170),(5,2):(400,555,580,150),
|
||||
(6,0):(40,36,620,240),(6,1):(40,650,944,120),
|
||||
(7,0):(40,36,590,250),(7,1):(36,616,314,200),(7,2):(410,620,570,200),
|
||||
(8,3):(40,1120,650,210)
|
||||
}
|
||||
|
||||
def compose(page):
|
||||
n=page['number'];sourcepath=ROOT/'art'/f'page-{n:02d}.png'
|
||||
source=Image.open(sourcepath).convert('RGB')
|
||||
rects=RECTS[f'page-{n:02d}'];assert len(rects)==len(page['panels'])
|
||||
rows=[]
|
||||
for k,rect in enumerate(rects):
|
||||
if not rows or abs(rows[-1][0][1][1]-rect[1])>10:rows.append([])
|
||||
rows[-1].append((k,rect))
|
||||
scratch=ImageDraw.Draw(Image.new('RGB',(10,10)))
|
||||
rowdata=[]
|
||||
for row in rows:
|
||||
entries=[]
|
||||
for k,rect in row:
|
||||
x0,y0,x1,y1=rect;w=x1-x0;pad=24 if w>500 else 20
|
||||
overlay=OVERLAY.get((n,k))
|
||||
size=30 if w>800 or overlay else 28
|
||||
if overlay:
|
||||
tx,ty,tw,th=overlay
|
||||
for size in [30,29,28,27,26,25,24]:
|
||||
step=round(size*1.2)
|
||||
groups=[(speaker,wrap(text,size,tw,scratch)) for speaker,text in page['panels'][k]['dialogue']]
|
||||
used=sum(len(lines)*step for _,lines in groups)+max(0,len(groups)-1)*12
|
||||
if used<=th:break
|
||||
else:raise ValueError(f'Overlay {n}/{k+1} does not fit')
|
||||
else:
|
||||
step=round(size*1.2)
|
||||
groups=[(speaker,wrap(text,size,w-pad*2,scratch)) for speaker,text in page['panels'][k]['dialogue']]
|
||||
h=sum(len(lines)*step for _,lines in groups)+max(0,len(groups)-1)*12
|
||||
textheight=h+36 if groups and not overlay else 0
|
||||
entries.append((k,rect,pad,size,step,groups,textheight,overlay))
|
||||
rowdata.append((entries,max(e[-2] for e in entries),max(e[1][3]-e[1][1] for e in entries)))
|
||||
gap=18;margin=12
|
||||
H=margin*2+sum(band+arth for _,band,arth in rowdata)+gap*(len(rowdata)-1)
|
||||
pageimg=Image.new('RGB',(1024*SCALE,H*SCALE),'white');d=ImageDraw.Draw(pageimg)
|
||||
data=base64.b64encode(sourcepath.read_bytes()).decode()
|
||||
svg=[f'<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="{H}" viewBox="0 0 1024 {H}"><defs><image id="source" width="1024" height="1536" href="data:image/png;base64,{data}"/></defs><rect width="1024" height="{H}" fill="white"/>']
|
||||
y=margin
|
||||
for entries,band,arth in rowdata:
|
||||
for k,rect,pad,size,step,groups,textheight,overlay in entries:
|
||||
x0,y0,x1,y1=rect;w=x1-x0;h=y1-y0
|
||||
# Pure page composition: the illustration pixels remain untouched.
|
||||
art=source.crop(tuple(rect)).resize((w*SCALE,h*SCALE),Image.Resampling.LANCZOS)
|
||||
desty=y+band
|
||||
pageimg.paste(art,(x0*SCALE,desty*SCALE))
|
||||
clipid=f'p{n}f{k}'
|
||||
svg.append(f'<defs><clipPath id="{clipid}"><rect x="{x0}" y="{desty}" width="{w}" height="{h}"/></clipPath></defs><g clip-path="url(#{clipid})"><use href="#source" transform="translate(0 {desty-y0})"/></g>')
|
||||
if overlay:
|
||||
tx,oy,tw,th=overlay;ty=desty+oy-y0
|
||||
else:tx=x0+pad;ty=y+14
|
||||
for speaker,lines in groups:
|
||||
color=BOOK['characters'][speaker]['color']
|
||||
for line in lines:
|
||||
d.text((tx*SCALE,ty*SCALE),line,font=font(size),fill=color,anchor='lt')
|
||||
svg.append(f'<text x="{tx}" y="{ty}" dominant-baseline="text-before-edge" font-family="Hannotate SC,sans-serif" font-size="{size}" fill="{color}">{html.escape(line)}</text>')
|
||||
ty+=step
|
||||
ty+=12
|
||||
if groups and not overlay:assert ty-12<=desty
|
||||
LAYOUT.append({'page':n,'panel':k+1,'source_rect':rect,'art_rect':[x0,desty,w,h],'text_size':size if groups else None,'lines':sum(len(ls) for _,ls in groups),'page_height':H,'lettering':'inside' if overlay else 'above'})
|
||||
y+=band+arth+gap
|
||||
svg.append('</svg>')
|
||||
path=ROOT/'pages'/f'page-{n:02d}.png';pageimg.save(path)
|
||||
svgpath=ROOT/'editable'/f'page-{n:02d}.svg';svgpath.write_text('\n'.join(svg))
|
||||
actual=''.join(e.text or '' for e in ET.parse(svgpath).findall('{http://www.w3.org/2000/svg}text'))
|
||||
expected=''.join(text for p in page['panels'] for _,text in p['dialogue'])
|
||||
assert re.sub(r'\s+','',actual)==re.sub(r'\s+','',expected)
|
||||
return path
|
||||
|
||||
if __name__=='__main__':
|
||||
import sys
|
||||
nums=[int(x) for x in sys.argv[1:]]
|
||||
pages=[p for p in BOOK['pages'] if not nums or p['number'] in nums]
|
||||
paths=[compose(p) for p in pages]
|
||||
if not nums:
|
||||
out=ROOT/'output/pdf';out.mkdir(parents=True,exist_ok=True)
|
||||
pdf=out/'Argon讨论-第二组.pdf'
|
||||
c=canvas.Canvas(str(pdf));c.setTitle(BOOK['title']);c.setAuthor('Codex')
|
||||
for path in paths:
|
||||
im=Image.open(path).convert('RGB');w,h=im.size;b=BytesIO();im.save(b,format='JPEG',quality=96,subsampling=0);b.seek(0)
|
||||
c.setPageSize((512,512*h/w));c.drawImage(ImageReader(b),0,0,width=512,height=512*h/w);c.showPage()
|
||||
c.save()
|
||||
sheet=Image.new('RGB',(1840,2100),'#e9e6ed')
|
||||
for i,path in enumerate(paths):
|
||||
im=Image.open(path);im.thumbnail((440,1010));sheet.paste(im,((i%4)*465,(i//4)*1050))
|
||||
sheet.save(ROOT/'overview.jpg',quality=95)
|
||||
reader='''<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>漫画</title><style>body{margin:0;background:#e9e6ed}main{max-width:1000px;margin:auto}img{width:100%;height:auto;display:block;margin-bottom:24px}@media print{img{margin:0;break-after:page}}</style><main>'''
|
||||
reader+=''.join(f'<img src="pages/page-{i:02d}.png" alt="">' for i in range(1,9))+'</main></html>'
|
||||
(ROOT/'index.html').write_text(reader)
|
||||
(ROOT/'tmp/final-layout.json').write_text(json.dumps(LAYOUT,ensure_ascii=False,indent=2))
|
||||
print(json.dumps({'rendered':[p['number'] for p in pages],'page_heights':sorted(set(x['page_height'] for x in LAYOUT)),'font_sizes':sorted(set(x['text_size'] for x in LAYOUT if x['text_size']))},ensure_ascii=False))
|
||||
Reference in New Issue
Block a user