#!/usr/bin/env python3 from __future__ import annotations import csv, re, zipfile from pathlib import Path from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.cidfonts import UnicodeCIDFont import json ROOT=Path('/root/work/pharmit-ghidra') JSONP=ROOT/'out/a4_receipt_template_candidates/quickreport_deep/PM_MAIN.EXE.a4receipt.05.02812c03.현금영수증 문의.bin.qr.objects.json' RAW=ROOT/'out/a4_receipt_three_raw.txt' OUT=ROOT/'out/a4_receipt_quickreport_render_samples' OUT.mkdir(parents=True, exist_ok=True) SCALE=72/96 FONT='HYGothic-Medium' try: pdfmetrics.registerFont(UnicodeCIDFont(FONT)) except Exception: FONT='Helvetica' # Actual SQL fields seen in a4_receipt_three_raw.txt / decomp SQL FIELD_MAP={ 'Q_paname':'PANAME','Q_InDate':'_DATE_FMT','Q_Days':'_DAYS','Q_PreSerial':'PRESERIAL', 'Q_Price_A':'TOT_C','Q_Price_p':'BON_C','Q_Price_c':'CHUNG_C','Q_s_FastMon':'BI_PRICE','Q_Price_n':'_PAY_TOTAL', 'Q_CardPrice':'CARD_C','Q_CashPrint':'PAPER_C','Q_Chah':'CHASH_C','Q_PersonNum':'PANUM', 'Q_Year':'_YEAR','Q_Month':'_MONTH','Q_day':'_DAY', 'Q_name':'_PHARM_NAME','Q_comcode':'_BIZNO','Q_add':'_PHARM_ADDR','Q_comname':'_PHARMACIST', 'qrlApproval_Num':'_CASH_APPROVAL','Q_Bag':'_BAG', } MONEY={'TOT_C','BON_C','CHUNG_C','BI_PRICE','_PAY_TOTAL','CARD_C','PAPER_C','CHASH_C'} def money(v): try: return f"{int(float(str(v).replace(',','') or '0')):,}" except Exception: return str(v or '') def read_sql_rows(): lines=RAW.read_text(encoding='utf-8',errors='replace').splitlines() data=[ln for ln in lines if '|' in ln and not set(ln.replace('|','').strip()) <= {'-'}] rows=[] for r in csv.DictReader(data, delimiter='|'): if r.get('CUSCODE') and not r['CUSCODE'].startswith('-'): rows.append({k:(v or '').strip() for k,v in r.items()}) return rows def enrich(r): r=dict(r) s=re.sub(r'\D','',r.get('INDATE','')) if len(s)>=8: r['_YEAR'],r['_MONTH'],r['_DAY']=s[:4],s[4:6],s[6:8] r['_DATE_FMT']=f'{s[:4]}-{s[4:6]}-{s[6:8]}' else: r['_YEAR']=r['_MONTH']=r['_DAY']=''; r['_DATE_FMT']=r.get('INDATE','') pay=sum(int(float(r.get(k) or 0)) for k in ['CARD_C','CHASH_C','PAPER_C']) if pay==0: pay=int(float(r.get('BON_C') or 0)); r['CHASH_C']=str(pay) r['_PAY_TOTAL']=str(pay); r['_DAYS']=''; r['_BAG']='' if '�' in r.get('PANAME','') or not r.get('PANAME'): r['PANAME']='홍길동' panum=re.sub(r'\D','',r.get('PANUM','')) r['PANUM']=(panum[:6]+'-'+panum[6:7]+'******') if len(panum)>=7 else '050930-3******' r['_PHARM_NAME']='샘플약국'; r['_BIZNO']='123-45-67890'; r['_PHARM_ADDR']='서울특별시 샘플구 약국로 1'; r['_PHARMACIST']='김약사'; r['_CASH_APPROVAL']='' return r def load_objects(): j=json.loads(JSONP.read_text(encoding='utf-8')) q=[o for o in j['objects'] if o.get('class','').startswith('TQR')] # First PM_MAIN 약제비 계산서·영수증 form: q indices 19..103 from object inspection. objs=q[19:104] return objs def prop(o,k,default=''): return o.get('props',{}).get(k,default) def value(r,o): name=o.get('name',''); cap=str(prop(o,'Caption','') or '') fld=FIELD_MAP.get(name) if fld: v=r.get(fld,'') return money(v) if fld in MONEY else str(v) # hide design placeholders if equal to object name if name.startswith('Q_') and cap==name: return '' return cap def draw_shape(c,o,H): try: x=float(prop(o,'Left',0))*SCALE; top=float(prop(o,'Top',0))*SCALE; w=float(prop(o,'Width',0))*SCALE; h=float(prop(o,'Height',0))*SCALE except Exception: return if w<=0 or h<=0: return y=H-top-h c.setStrokeColor(colors.HexColor('#333333')); c.setLineWidth(0.45) if w<=1.6 or h<=1.6: c.line(x,y,x+w,y+h) else: c.rect(x,y,w,h,stroke=1,fill=0) def draw_text(c,r,o,H): txt=value(r,o) if not txt: return try: x=float(prop(o,'Left',0))*SCALE; top=float(prop(o,'Top',0))*SCALE; w=float(prop(o,'Width',0))*SCALE; h=float(prop(o,'Height',0))*SCALE except Exception: return y=H-top-h fh=max(5.5,min(abs(float(prop(o,'Font.Height',-13)))*0.72,16)) c.setFont(FONT,fh); c.setFillColor(colors.black) fld=FIELD_MAP.get(o.get('name','')) if fld in MONEY: c.drawRightString(x+w-2,y+max(1,(h-fh)/2)+1,txt) else: c.drawString(x+1,y+max(1,(h-fh)/2)+1,txt) def render(row, idx, objs): r=enrich(row) out=OUT/f'a4_pm_main_receipt_sql_{idx}_{r["PRESERIAL"]}.pdf' c=canvas.Canvas(str(out),pagesize=A4); W,H=A4 for o in objs: if o.get('class')=='TQRShape': draw_shape(c,o,H) for o in objs: if o.get('class') in {'TQRLabel','TQRDBText'}: draw_text(c,r,o,H) c.setFont(FONT,6); c.setFillColor(colors.HexColor('#777777')) c.drawString(4,4,f'PM_MAIN QuickReport receipt mapped / CUSCODE={r.get("CUSCODE")} PRESERIAL={r.get("PRESERIAL")}') c.save(); return out def main(): rows=read_sql_rows(); objs=load_objects(); outs=[] for i,r in enumerate(rows[:3],1): outs.append(render(r,i,objs)) z=ROOT/'out/a4_pm_main_receipt_sql_mapped_20260628.zip' with zipfile.ZipFile(z,'w',zipfile.ZIP_DEFLATED) as zz: for p in outs+[Path(__file__),JSONP,RAW]: zz.write(p,p.relative_to(ROOT)) print('\n'.join(map(str,outs))); print(z); print(z.stat().st_size) if __name__=='__main__': main()