from pathlib import Path from xml.sax.saxutils import escape from reportlab.pdfgen import canvas from reportlab.platypus import SimpleDocTemplate,Paragraph,Spacer,KeepTogether from reportlab.lib.styles import ParagraphStyle from reportlab.lib.enums import TA_CENTER,TA_JUSTIFY from reportlab.lib.colors import HexColor from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib.pagesizes import A4 from pypdf import PdfReader base=Path('work/trilium');out=Path('output/pdf');out.mkdir(parents=True,exist_ok=True) pdfmetrics.registerFont(TTFont('Korean','C:/Windows/Fonts/malgun.ttf')) pdfmetrics.registerFont(TTFont('KoreanBold','C:/Windows/Fonts/malgunbd.ttf')) body=ParagraphStyle('body',fontName='Korean',fontSize=10.5,leading=18,spaceAfter=9,wordWrap='CJK',textColor=HexColor('#20252c'),allowWidows=0,allowOrphans=0) title=ParagraphStyle('title',parent=body,fontName='KoreanBold',fontSize=24,leading=32,spaceAfter=12) author=ParagraphStyle('author',parent=body,fontSize=12,leading=19,spaceAfter=24) section=ParagraphStyle('section',parent=body,alignment=TA_CENTER,spaceBefore=18,spaceAfter=18,textColor=HexColor('#697386'),keepWithNext=True) parts=(base/'last-question.ko.txt').read_text(encoding='utf8').replace('제로이드','제로드').split('\n\n') # First block contains title and author; all subsequent paragraphs follow the source narrative. story=[Paragraph('마지막 질문',title),Paragraph('아이작 아시모프',author)] for block in parts[1:]: text=block.strip() if not text:continue story.append(Paragraph(escape(text),section if text=='* * *' else body)) def footer(c,doc): c.saveState();c.setFont('Korean',8);c.setFillColor(HexColor('#697386')) c.drawString(48,29,'마지막 질문 · 아이작 아시모프') c.drawRightString(A4[0]-48,29,str(doc.page));c.restoreState() target=out/'마지막-질문-한국어.pdf' doc=SimpleDocTemplate(str(target),pagesize=A4,rightMargin=48,leftMargin=48,topMargin=48,bottomMargin=49,title='마지막 질문',author='아이작 아시모프',subject='Trilium 기본 제공 예제의 한국어 번역') paragraphs=len(story) doc.build(story,onFirstPage=footer,onLaterPages=footer) r=PdfReader(str(target));text='\n'.join(p.extract_text() for p in r.pages) assert '빛이 있으라' in text and '작품' in text assert '\ufffd' not in text print({'file':str(target),'pages':len(r.pages),'bytes':target.stat().st_size,'paragraphs':paragraphs})