""" Generate the A4 cover PDF for the PRISM project. Output: plans/PRISM/PRISM_Cover.pdf Layout (A4, 210 × 297 mm): ┌──────────────────────────────────────────┐ │ 彩色棱镜装饰 (左上 / 右下) │ │ │ │ 🌈 │ │ PRISM │ │ Prior-Registered Integrated │ │ Spatial Memory │ │ ──── 一行 tagline ──── │ │ │ │ [一句话摘要框] │ │ │ │ 项目介绍 (3-4 段) │ │ │ │ 版本 / 日期 / 作者 / 仓库 │ └──────────────────────────────────────────┘ """ from __future__ import annotations from pathlib import Path from reportlab.lib.pagesizes import A4 from reportlab.lib.units import mm from reportlab.lib.colors import HexColor, white, Color from reportlab.pdfgen import canvas from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.platypus import Paragraph, Frame from reportlab.lib.styles import ParagraphStyle from reportlab.lib.enums import TA_LEFT, TA_CENTER # ────────────── 元数据 ────────────── PROJECT_NAME = "PRISM" PROJECT_FULL = "Prior-Registered Integrated Spatial Memory" PROJECT_SUBTITLE = "机器人大脑空间记忆架构" TAGLINE = "融合 iPhone 先验地图 与 ZED 2i 在线感知 的四层空间记忆系统" ONE_LINER = ("iPhone 是教科书,ZED 2i 是眼睛," "PRISM 大脑是一名既会查书又会观察、还会在书页空白处做笔记的学生。") DESCRIPTION_PARAS = [ ("PRISM 是一种面向室内服务机器人(酒店 / 家居 / 办公)的" "空间记忆架构。它把偶发的、高质量的离线扫描数据" "(iPhone RoomPlan + LiDAR)与持续的、视锥级的在线感知数据" "(ZED 2i 双目+IMU)融合进同一套四层记忆体系:" "L1 感知缓冲 → L2 度量 → L3 拓扑 → L4 语义," "并通过 CLIP+ICP 配准充电时的记忆巩固形成闭环。"), ("关键创新:(1) 首次把消费级 iPhone RoomPlan 作为先验地图源;" "(2) 分层而非分库,所有数据共享 SpatialMemory 单一根对象;" "(3) 仿人脑睡眠的延迟巩固机制,避免单次错误观测污染长期记忆;" "(4) 通过锚点持续校正 VIO 长时漂移。"), ("文档结构:15 个 Markdown 文件(README + 14 章),含可立即复制运行的 " "Python schema、ROS 2 节点编排、8 周路线图、国产替代矩阵、" "风险登记表、评测指标、以及一份周末可跑通的最小 MVP。"), ("预期产出:8 周原型 → 4 个月生产 · MTBF ≥ 48h · " "冷启动重定位成功率 > 90% · 任务成功率 > 90%。"), ] VERSION = "v1.1" DATE = "2026-05-16" AUTHOR = "项目规划组" LICENSE = "CC BY-NC 4.0" REPO = "github.com//prism (TBD)" # ────────────── 字体注册(macOS 中文) ────────────── def register_chinese_fonts() -> tuple[str, str]: """Returns (regular_font_name, bold_font_name).""" candidates = [ # (regular_path, regular_subfontidx, bold_path, bold_subfontidx, name_base) ("/System/Library/Fonts/PingFang.ttc", 2, "/System/Library/Fonts/PingFang.ttc", 5, "PingFang"), ("/System/Library/Fonts/STHeiti Light.ttc", 0, "/System/Library/Fonts/STHeiti Medium.ttc", 0, "STHeiti"), ("/System/Library/Fonts/Hiragino Sans GB.ttc", 0, "/System/Library/Fonts/Hiragino Sans GB.ttc", 1, "Hiragino"), ] for reg, ri, bold, bi, base in candidates: try: pdfmetrics.registerFont(TTFont(f"{base}-R", reg, subfontIndex=ri)) pdfmetrics.registerFont(TTFont(f"{base}-B", bold, subfontIndex=bi)) return f"{base}-R", f"{base}-B" except Exception as e: print(f" 字体 {base} 注册失败: {e}") continue # 全失败:回退到内置 Helvetica(可能无法显示中文) print(" ⚠️ 未找到中文字体,中文可能显示不全") return "Helvetica", "Helvetica-Bold" # ────────────── 色彩(棱镜光谱) ────────────── PRISM_COLORS = [ HexColor("#E63946"), # red HexColor("#F77F00"), # orange HexColor("#FCBF49"), # yellow HexColor("#2A9D8F"), # teal-green HexColor("#1D7AB8"), # blue HexColor("#5A189A"), # purple ] INK_DARK = HexColor("#0E1116") INK_MUTED = HexColor("#4A5560") INK_BG = HexColor("#FBFAF7") ACCENT = HexColor("#1D7AB8") GOLD_LINE = HexColor("#C9A227") def draw_prism_decoration(c: canvas.Canvas, page_w: float, page_h: float): """左上角:三角形棱镜分光示意 + 右下角:光谱条带""" # ── 左上 棱镜三角形 + 入射/出射光 ── cx, cy = 30 * mm, page_h - 35 * mm size = 18 * mm # 入射"白光" c.setLineWidth(1.4); c.setStrokeColor(INK_DARK) c.line(cx - size*1.5, cy + 2*mm, cx - 1*mm, cy + 1*mm) # 三角棱镜 c.setFillColor(HexColor("#EAEAEA")); c.setStrokeColor(INK_DARK) p = c.beginPath() p.moveTo(cx, cy + size) p.lineTo(cx + size*0.866, cy - size*0.5) p.lineTo(cx - size*0.866, cy - size*0.5) p.close() c.drawPath(p, stroke=1, fill=1) # 七色出射 for i, col in enumerate(PRISM_COLORS): c.setStrokeColor(col); c.setLineWidth(1.6) y_off = (i - len(PRISM_COLORS)/2 + 0.5) * 2.0 * mm x_start = cx + size*0.6 x_end = cx + size*3.0 y_start = cy y_end = cy + y_off c.line(x_start, y_start, x_end, y_end) # ── 右下 光谱条带(放在 footer 上方,避免与 footer 文字重叠)── bar_w = 60 * mm bar_h = 3 * mm bx = page_w - 20*mm - bar_w by = 21 * mm # 上移到 meta 与 footer 之间的留白处 seg = bar_w / len(PRISM_COLORS) for i, col in enumerate(PRISM_COLORS): c.setFillColor(col); c.setStrokeColor(col) c.rect(bx + i*seg, by, seg, bar_h, fill=1, stroke=0) def draw_letter_glyphs(c: canvas.Canvas, x: float, y: float, letters: list[tuple[str, str]], font: str, font_size: float, gap: float): """彩色大字母 P R I S M ,每个字母带颜色,下方写英文 token""" for (letter, _), col, i in zip(letters, PRISM_COLORS, range(len(letters))): # 这里我们让 P R I S M 取前 5 个颜色;Hex 不取第 6 个 pass def main(): out = Path(__file__).resolve().parents[1] / "PRISM_Cover.pdf" print(f"→ output: {out}") reg, bold = register_chinese_fonts() print(f" fonts: regular={reg}, bold={bold}") page_w, page_h = A4 c = canvas.Canvas(str(out), pagesize=A4) c.setTitle("PRISM — Prior-Registered Integrated Spatial Memory") c.setAuthor(AUTHOR) c.setSubject(PROJECT_SUBTITLE) c.setKeywords("PRISM, spatial memory, robot, iPhone RoomPlan, ZED 2i, SLAM, world model") c.setCreator("PRISM make_cover_pdf.py") # ── 背景纸色 ── c.setFillColor(INK_BG) c.rect(0, 0, page_w, page_h, fill=1, stroke=0) # ── 装饰 ── draw_prism_decoration(c, page_w, page_h) # ── 顶部小标签 ── c.setFont(reg, 9) c.setFillColor(INK_MUTED) c.drawString(20*mm, page_h - 15*mm, "ROBOT · SPATIAL · MEMORY") c.drawRightString(page_w - 20*mm, page_h - 15*mm, f"VERSION {VERSION} · {DATE}") # ── 主标题 PRISM ── title_y = page_h - 75 * mm c.setFont(bold, 78) title = "PRISM" # 等距彩色 5 个字母 spectrum5 = PRISM_COLORS[:5] title_total_w = c.stringWidth(title, bold, 78) start_x = (page_w - title_total_w) / 2 cur_x = start_x for ch, col in zip(title, spectrum5): c.setFillColor(col) c.drawString(cur_x, title_y, ch) cur_x += c.stringWidth(ch, bold, 78) # ── 副标题英文全称 ── c.setFont(bold, 14) c.setFillColor(INK_DARK) sub_en = PROJECT_FULL c.drawCentredString(page_w/2, title_y - 13*mm, sub_en) # ── 中文副标题 ── c.setFont(reg, 16) c.setFillColor(INK_DARK) c.drawCentredString(page_w/2, title_y - 23*mm, PROJECT_SUBTITLE) # ── 分隔线 + tagline ── rule_y = title_y - 33*mm c.setStrokeColor(GOLD_LINE); c.setLineWidth(0.8) rule_half = 60*mm c.line(page_w/2 - rule_half, rule_y, page_w/2 + rule_half, rule_y) c.setFont(reg, 11); c.setFillColor(INK_MUTED) c.drawCentredString(page_w/2, rule_y - 7*mm, TAGLINE) # ── 字母含义注解 P-R-I-S-M ── legend_y = rule_y - 23*mm legend_items = [ ("P", "Prior"), ("R", "Registered"), ("I", "Integrated"), ("S", "Spatial"), ("M", "Memory"), ] item_w = 28*mm total_w = item_w * len(legend_items) start_x = (page_w - total_w) / 2 for i, (letter, word) in enumerate(legend_items): x = start_x + i*item_w + item_w/2 c.setFont(bold, 22); c.setFillColor(PRISM_COLORS[i]) c.drawCentredString(x, legend_y, letter) c.setFont(reg, 9); c.setFillColor(INK_DARK) c.drawCentredString(x, legend_y - 6*mm, word) # ── 一句话摘要框 ── box_y_top = legend_y - 18*mm box_h = 28*mm box_x = 25*mm box_w = page_w - 50*mm # 浅色背景 c.setFillColor(HexColor("#F0EEE6")); c.setStrokeColor(ACCENT) c.setLineWidth(0.6) c.roundRect(box_x, box_y_top - box_h, box_w, box_h, 4*mm, fill=1, stroke=1) # 标签 c.setFont(bold, 9); c.setFillColor(ACCENT) c.drawString(box_x + 6*mm, box_y_top - 6*mm, "一句话摘要 · ONE-LINER") # 引言文字 quote_style = ParagraphStyle( "quote", fontName=reg, fontSize=12.5, leading=18, textColor=INK_DARK, alignment=TA_LEFT, firstLineIndent=0, leftIndent=0, rightIndent=0) para = Paragraph(f"“{ONE_LINER}”", quote_style) frame = Frame(box_x + 6*mm, box_y_top - box_h + 3*mm, box_w - 12*mm, box_h - 12*mm, showBoundary=0, leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0) frame.addFromList([para], c) # ── 项目介绍正文 ── intro_y_top = box_y_top - box_h - 8*mm intro_x = 25*mm intro_w = page_w - 50*mm intro_h = intro_y_top - 50*mm # 底部留 50mm 给元信息 body_style = ParagraphStyle( "body", fontName=reg, fontSize=9.5, leading=14, textColor=INK_DARK, alignment=TA_LEFT, spaceAfter=5, firstLineIndent=0) # 给底部 meta 块预留 45mm:meta 顶在 45mm,文字行底 40mm,值底 33mm META_TOP = 45 * mm INTRO_BOT = META_TOP + 5*mm # intro frame 底部留 5mm 空白 intro_h = intro_y_top - INTRO_BOT paragraphs = [Paragraph(p, body_style) for p in DESCRIPTION_PARAS] frame = Frame(intro_x, INTRO_BOT, intro_w, intro_h, showBoundary=0, leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0) frame.addFromList(paragraphs, c) # ── 底部元信息块 ── meta_y = 40 * mm # 标签行 val_y = meta_y - 5.5*mm # 值行 c.setStrokeColor(INK_MUTED); c.setLineWidth(0.4) c.line(20*mm, META_TOP, page_w - 20*mm, META_TOP) meta_items = [ ("VERSION", VERSION), ("DATE", DATE), ("AUTHOR", AUTHOR), ("LICENSE", LICENSE), ] col_w = (page_w - 40*mm) / len(meta_items) for i, (k, v) in enumerate(meta_items): x = 20*mm + i*col_w c.setFont(bold, 8); c.setFillColor(INK_MUTED) c.drawString(x, meta_y, k) c.setFont(reg, 10); c.setFillColor(INK_DARK) c.drawString(x, val_y, v) # ── 底部 footer(左:仓库;右:slogan;两者避开右下光谱条所在的 21mm 处)── c.setFont(reg, 7.5); c.setFillColor(INK_MUTED) c.drawString(20*mm, 12*mm, "plans/PRISM/ · 15 documents (README + Ch.00–14)") c.drawString(20*mm, 8*mm, "Repo: " + REPO) # 右侧 slogan 放在光谱条上方(避免覆盖) c.setFont(reg, 8); c.setFillColor(INK_MUTED) c.drawRightString(page_w - 20*mm, 12*mm, "Prism splits perception into memory.") c.showPage() c.save() print(f"✅ done: {out} ({out.stat().st_size/1024:.1f} KB)") if __name__ == "__main__": main()