Files
Pier_v1/Code/교각모델링/md_to_pdf.py
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:20:18 +09:00

126 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Render a project Markdown report (headings, tables, bullets, quotes) to an A4 PDF with Korean fonts.
usage: python md_to_pdf.py <input.md> <output.pdf> [header text]
The .md file stays the canonical document; the PDF is a rendition of it.
"""
import re
import sys
from pathlib import Path
from reportlab.lib import colors
from reportlab.lib.enums import TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import (HRFlowable, KeepTogether, Paragraph, SimpleDocTemplate, Spacer, Table,
TableStyle)
pdfmetrics.registerFont(TTFont("Malgun", r"C:\Windows\Fonts\malgun.ttf"))
pdfmetrics.registerFont(TTFont("MalgunBd", r"C:\Windows\Fonts\malgunbd.ttf"))
pdfmetrics.registerFontFamily("Malgun", normal="Malgun", bold="MalgunBd", italic="Malgun", boldItalic="MalgunBd")
BASE = ParagraphStyle("base", fontName="Malgun", fontSize=9, leading=13, alignment=TA_LEFT)
ST = {
"h1": ParagraphStyle("h1", parent=BASE, fontName="MalgunBd", fontSize=15, leading=20, spaceAfter=6),
"h2": ParagraphStyle("h2", parent=BASE, fontName="MalgunBd", fontSize=12, leading=16, spaceBefore=10, spaceAfter=4),
"h3": ParagraphStyle("h3", parent=BASE, fontName="MalgunBd", fontSize=10, leading=14, spaceBefore=6, spaceAfter=3),
"p": BASE,
"quote": ParagraphStyle("q", parent=BASE, leftIndent=8, textColor=colors.HexColor("#444444"),
backColor=colors.HexColor("#F2F2F2"), borderPadding=4, spaceBefore=4, spaceAfter=4),
"li": ParagraphStyle("li", parent=BASE, leftIndent=12, bulletIndent=4),
"li2": ParagraphStyle("li2", parent=BASE, leftIndent=24, bulletIndent=16),
"cell": ParagraphStyle("cell", parent=BASE, fontSize=7.5, leading=10),
"cellh": ParagraphStyle("cellh", parent=BASE, fontName="MalgunBd", fontSize=7.5, leading=10),
}
WIDTH = A4[0] - 30 * mm
def inline(text):
text = text.replace("−", "-") # Malgun Gothic has no U+2212 minus glyph (renders blank)
text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"`(.+?)`", r'<font color="#7A3E00">\1</font>', text)
text = re.sub(r"~~(.+?)~~", r"<strike>\1</strike>", text)
return text
def table(lines):
rows = [[c.strip() for c in l.strip().strip("|").split("|")] for l in lines]
rows = [r for r in rows if not all(re.fullmatch(r":?-{2,}:?", c) for c in r if c)]
n = max(len(r) for r in rows)
rows = [r + [""] * (n - len(r)) for r in rows]
# minimum width = longest unbreakable token (so short codes like 'E-01' or '오류' never wrap per glyph),
# the remaining width is shared in proportion to text length
from reportlab.pdfbase.pdfmetrics import stringWidth
plain = lambda c: re.sub(r"[*`~]", "", c)
mins = [max(stringWidth(tok, "MalgunBd", 7.5) for r in rows for tok in (plain(r[i]).split() or [""])) + 8
for i in range(n)]
mins = [min(m, WIDTH * 0.3) for m in mins]
weights = [max(min(len(r[i]), 60) for r in rows) + 2 for i in range(n)]
spare = max(WIDTH - sum(mins), 0)
widths = [m + spare * w / sum(weights) for m, w in zip(mins, weights)]
if sum(widths) > WIDTH:
widths = [w * WIDTH / sum(widths) for w in widths]
data = [[Paragraph(inline(c), ST["cellh"] if k == 0 else ST["cell"]) for c in r] for k, r in enumerate(rows)]
t = Table(data, colWidths=widths, repeatRows=1)
t.setStyle(TableStyle([
("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#999999")),
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#E6E6E6")),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("LEFTPADDING", (0, 0), (-1, -1), 3), ("RIGHTPADDING", (0, 0), (-1, -1), 3),
]))
return t
def build(md, out, header):
lines = md.splitlines()
story, i = [], 0
while i < len(lines):
l = lines[i]
if l.startswith("|"):
block = []
while i < len(lines) and lines[i].startswith("|"):
block.append(lines[i])
i += 1
story += [table(block), Spacer(1, 4)]
continue
if l.startswith("### "):
story.append(Paragraph(inline(l[4:]), ST["h3"]))
elif l.startswith("## "):
story.append(Paragraph(inline(l[3:]), ST["h2"]))
elif l.startswith("# "):
story.append(Paragraph(inline(l[2:]), ST["h1"]))
elif l.startswith(">"):
story.append(Paragraph(inline(l.lstrip("> ")), ST["quote"]))
elif re.match(r"^\s{2,}- ", l):
story.append(Paragraph(inline(l.strip()[2:]), ST["li2"], bulletText="–"))
elif l.startswith("- "):
story.append(Paragraph(inline(l[2:]), ST["li"], bulletText="•"))
elif l.strip() == "---":
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#BBBBBB"), spaceBefore=4,
spaceAfter=4))
elif l.strip():
story.append(Paragraph(inline(l), ST["p"]))
i += 1
def deco(c, d):
c.saveState()
c.setFont("Malgun", 7.5)
c.setFillColor(colors.HexColor("#666666"))
c.drawString(15 * mm, A4[1] - 10 * mm, header)
c.drawRightString(A4[0] - 15 * mm, 10 * mm, f"{d.page}")
c.restoreState()
doc = SimpleDocTemplate(str(out), pagesize=A4, leftMargin=15 * mm, rightMargin=15 * mm, topMargin=16 * mm,
bottomMargin=16 * mm, title=Path(out).stem, author="심영표")
doc.build(story, onFirstPage=deco, onLaterPages=deco)
if __name__ == "__main__":
src, dst = Path(sys.argv[1]), Path(sys.argv[2])
hdr = sys.argv[3] if len(sys.argv) > 3 else src.stem
build(src.read_text(encoding="utf-8"), dst, hdr)
print(dst, dst.stat().st_size)