#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
华南农业大学「mRNA、circRNA 生物制药工艺开发报告」讲座海报生成器
=================================================================
输入：src.docx（新文案 + 6 张图）+ 公司 Logo
输出：A4 海报 HTML（自包含 + 可编辑自动保存）+ PNG（2 倍图）+ PDF（印刷版）
设计沿用上一版《mRNA_circRNA宣讲单页_重点产品版_华南农业大学_2026年9月.html》：
      白顶栏放彩色 Logo（Logo 为青蓝色，须浅底）+ 深蓝→青绿渐变主体。
文案严格取自 docx，不新增事实；图中数值仅保留两处读数一致的（Z-Average 90.13 / Peak 95.86）。
"""
import base64, io, os, sys, re, html as _html
import datetime

HERE = os.path.dirname(os.path.abspath(__file__))
MEDIA = os.path.join(HERE, 'docx', 'word', 'media')
LOGO = '/Users/liuxinyuan/work/公司资料/logo_transparent.png'
OUT_HTML = os.path.join(HERE, '华南农业大学_mRNA_circRNA工艺开发报告_讲座海报_20260922.html')
OUT_PNG = os.path.join(HERE, '华南农业大学_mRNA_circRNA工艺开发报告_讲座海报_20260922.png')
OUT_PDF = os.path.join(HERE, '华南农业大学_mRNA_circRNA工艺开发报告_讲座海报_20260922.pdf')

sys.path.insert(0, '/Users/liuxinyuan/.hermes/skills/productivity/editable-html-deliverables/templates')
from html_editable_layer import EDIT_CSS, EDIT_JS, TOOLBAR, EDITHINT, apply as apply_edit  # noqa

def b64(path):
    ext = os.path.splitext(path)[1].lstrip('.').lower()
    mime = 'image/png' if ext == 'png' else ('image/jpeg' if ext in ('jpg', 'jpeg') else 'image/png')
    with open(path, 'rb') as f:
        return 'data:%s;base64,%s' % (mime, base64.b64encode(f.read()).decode())

IMG = {n: b64(os.path.join(MEDIA, 'image%d.png' % n)) for n in range(1, 7)}
LOGO_B64 = b64(LOGO)
# 右上角二维码：标签为「参会登记」（2026-09-20 用户指定），二维码素材仍用 ~/work/公司资料/试用装申请.png
# 公众号/小红书/视频号三码待用户提供，页脚留占位
QR_APPLY = b64('/Users/liuxinyuan/work/公司资料/试用装申请.png')
# 页脚三码（2026-09-21 用户提供；身份经 Vision 解码确认）
QRDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'qr')
QR_SOC = {n: b64(os.path.join(QRDIR, n + '.png')) for n in ('公众号', '小红书', '视频号')}

# ── 文案（取自 docx，逐字）──
TITLE = 'mRNA、circRNA 生物制药工艺开发报告'
EVENT_DATE = '2026年9月22日（周二）14:00–16:30'
EVENT_PLACE = '华南农业大学 · 兽医新楼 111 室'
COMPANY = '珠海宝锐生物科技有限公司'
SLOGAN = '守护生命健康，缔造美好生活'
TEL = '0756-8699969'
WEB = 'www.biori.com'
MAIL = 'marketing@biori.com'
ADDR = '珠海市香洲区水岸一路88号'

COPY_P1 = ('中心法则的提出使生命科学进入基因时代。科学技术的发展，使人们很容易实现 DNA、蛋白质层面对机体的设计与改造。'
           '在中心法则中，mRNA 只是 DNA 与蛋白质的一个“信使”，因此 mRNA 的发展一直处于落后的状态。同时 mRNA 自身的缺点也严重限制了它的应用，'
           '稳定性极差导致它非常容易降解，强烈的负电荷难以穿透同样带负电荷的细胞膜等等因素，使得 mRNA 的应用发展异常缓慢，'
           '但也因此激发了科研工作者极大地热情。')
COPY_P2 = ('在经过数十年的基础研究之后，以新冠为契机，mRNA 终于迎来爆发，以辉瑞、莫丹娜为首的企业，在极短的时间内成功开发了新冠 mRNA 疫苗，'
           '打破了传统疫苗行业研发、审批、上市的标准化流程，并在市场上取得巨大的成功。进入 2026 年，国内两个 mRNA 疫苗首次拿到正式的上市批件，'
           '标志着生物制药正式进入 mRNA 时代。mRNA 技术创始人曾这样形容 mRNA 的发展，现在每个生物公司都希望自己能拥有 mRNA 产品之一。')
COPY_P3 = ('面对空前的市场，结合后疫情时代的生物行业发展方向，宝锐公司立足 mRNA 原料酶，开发建设符合自身发展规律的 mRNA 系列产品。'
           '本期交流立足于 mRNA 工业化生产基本流程，结合 RNA 市场的发展情况，RNA 技术要点，RNA 新技术路线等内容，'
           '围绕 RNA 领域“是什么”“为什么”“怎么做”，共同提高对 mRNA 领域的认知，共同商讨 mRNA 领域未来的研究方向与合作空间。')

HIGHLIGHTS = [
    ('🧬', 'mRNA 工业化生产基本流程', '从序列设计、体外转录到纯化回收的工艺链条'),
    ('📈', 'RNA 市场发展情况', '2026 年国内 mRNA 疫苗获批上市后的产业格局'),
    ('🔬', 'RNA 技术要点与新技术路线', '成环设计、质量控制与递送体系关键环节'),
    ('💬', '是什么 · 为什么 · 怎么做', '围绕 RNA 领域的三问展开，现场交流与讨论'),
]

FIGS = [
    # 按 docx 草稿的成组顺序：image6(主视觉) ｜ image1+image2 ｜ image3(电泳,加宽) ｜ image4+image5
    ('image6', 'std', '环形 RNA 细胞表达验证', 'circGFP-1 · circGFP-3 · circGFP-4 绿色荧光信号'),
    ('image1', 'std', 'mRNA 结构与序列优化要点', '5′ Cap · UTR · CDS · Poly-A tail'),
    ('image2', 'std', 'circRNA 制备与成环流程', '前体 RNA · 中间体 · 产物'),
    ('image3', 'std', '转录产物电泳验证', 'M：DNA Maker · 1–8：IVT 200–10000 nt'),
    ('image4', 'std', '递送体系粒径表征（DLS）', 'Z-Average 90.13 nm · Peak 95.86 nm'),
    ('image5', 'std', '形貌观察', '显微形貌记录（TEM 视角）'),
]

FIGBOX = '39'          # 技术图表区图框高度（mm），按版面自适应
STAMP = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')

CSS = """
  * { margin:0; padding:0; box-sizing:border-box; }
  body { font-family:"PingFang SC","STHeiti","Heiti SC","Microsoft YaHei",sans-serif; background:#e8edf3; }
  .page {
    width:210mm; height:297mm; margin:0 auto; background:#fff;
    overflow:hidden; display:flex; flex-direction:column; position:relative;
  }
  .page, .page * { -webkit-print-color-adjust:exact !important; print-color-adjust:exact !important; }

  /* 白顶栏：放彩色 Logo（Logo 主色 #078AB0，须浅底） */
  .topbar { position:relative; flex-shrink:0; height:19.5mm; background:#fff; border-bottom:2.5px solid #0a3d62;
            display:flex; align-items:center; justify-content:space-between; padding:0 11mm; }
  .tb-qr { display:flex; align-items:center; gap:2.4mm; }
  .tb-qr img { height:16mm; border:1px solid #dbe6f0; border-radius:1.2mm; padding:.7mm; background:#fff; }
  .tb-qr .t { font-size:7.6pt; font-weight:800; color:#078AB0; line-height:1.3; text-align:right; }
  .tb-qr .t small { display:block; font-weight:500; color:#7a8b9c; font-size:6.3pt; }
  .topbar > img { height:9.5mm; }
  .topbar .tb-r { font-size:8pt; font-weight:700; color:#0a3d62; letter-spacing:1.5px; }
  .topbar .tb-r .lo { color:#7a8b9c; font-weight:500; letter-spacing:.5px; }
  .tb-slogan { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
               font-size:10pt; font-weight:900; letter-spacing:2.5px; color:#0a3d62; white-space:nowrap; }
  .tb-slogan i { font-style:normal; color:#00a3a3; }

  /* Hero */
  .hero { flex-shrink:0; position:relative; overflow:hidden; color:#fff;
          background:linear-gradient(135deg,#0a3d62 0%,#0060c0 50%,#1e8fa8 100%); padding:5mm 11mm 4.4mm; }
  .mol { position:absolute; border-radius:50%; border:1.5px solid rgba(255,255,255,.16); }
  .mol.m1 { width:52mm; height:52mm; right:-16mm; top:-18mm; }
  .mol.m2 { width:28mm; height:28mm; right:26mm; bottom:-13mm; }
  .mol.m3 { width:16mm; height:16mm; right:8mm; top:16mm; border-style:dashed; }
  .kicker { position:relative; z-index:1; font-size:8.5pt; letter-spacing:2px; opacity:.9; }
  .hero h1 { position:relative; z-index:1; font-size:20pt; font-weight:900; letter-spacing:.5px; margin:2mm 0 1.5mm; line-height:1.2; }
  .hero h1 em { font-style:normal; color:#7fd8e8; }
  .hero .sub { position:relative; z-index:1; font-size:9.8pt; opacity:.95; letter-spacing:.8px; }
  .hero .chips { position:relative; z-index:1; display:flex; flex-wrap:wrap; gap:3mm; margin-top:3.2mm; }
  .hero .chip { font-size:9.6pt; font-weight:700; background:rgba(255,255,255,.16); border:1px solid rgba(255,255,255,.34);
                padding:2mm 3.4mm; border-radius:7px; white-space:nowrap; }
  .hero .chip .k { font-weight:500; opacity:.85; margin-right:1.5mm; }

  /* 主体 */
  .main { flex:1; min-height:0; padding:3.5mm 11mm 0; display:flex; flex-direction:column; gap:2.6mm; }
  .sec-t { font-size:10pt; font-weight:900; color:#0a3d62; display:flex; align-items:center; gap:2mm; margin-bottom:2mm; }
  .sec-t::before { content:''; width:3.5px; height:4.2mm; background:linear-gradient(180deg,#0060c0,#1e8fa8); border-radius:2px; }

  .intro { border:1px solid #dbe6f0; border-radius:3mm; background:linear-gradient(180deg,#f8fbfd,#f2f7fc); padding:3mm 3.5mm; }
  .intro .cols { column-count:3; column-gap:4.5mm; }
  .intro p { font-size:7.05pt; line-height:1.4; color:#2b3a4a; text-align:justify; margin-bottom:1.3mm; break-inside:avoid; }
  .intro p:last-child { margin-bottom:0; }

  .hl { display:grid; grid-template-columns:repeat(4,1fr); gap:2mm; }
  .hl .c { border:1px solid #dbe6f0; border-left:3px solid #0060c0; border-radius:2mm; padding:1.6mm 2mm; background:#fbfdff; }
  .hl .c .h { font-size:7.7pt; font-weight:800; color:#0a3d62; line-height:1.3; }
  .hl .c .h i { font-style:normal; margin-right:1.5mm; }
  .hl .c .d { font-size:6.2pt; color:#5c6b7a; margin-top:.8mm; line-height:1.38; }

  /* 图表区 */
  /* 统一图卡：3 列 × 2 行，每格等高、图片等比 contain、内边距一致 */
  .figs { display:grid; grid-template-columns:repeat(3,1fr); gap:2.6mm; }
  .fig { border:1px solid #dbe6f0; border-radius:2.5mm; overflow:hidden; background:#fff;
         display:flex; flex-direction:column; }
  .fig .box { height:__FIGBOX__mm; background:#fff; border-bottom:1px solid #eef3f8;
              display:flex; align-items:center; justify-content:center; padding:1.6mm; }
  .fig .box img { max-width:100%; max-height:100%; width:auto; height:auto; display:block; }
  .fig .cap { padding:1.2mm 2mm 1.3mm; display:flex; flex-direction:column; }
  .fig .cap .t { font-size:7.5pt; font-weight:800; color:#0a3d62; line-height:1.25;
                 height:3.7mm; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
  .fig .cap .s { font-size:6.4pt; color:#6b7b8a; line-height:1.3; margin-top:.6mm;
                 height:3.3mm; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }

  /* 页脚 */
  .foot { flex-shrink:0; margin-top:auto; background:linear-gradient(135deg,#0a3d62,#0060c0); color:#fff;
          padding:3mm 11mm 4mm; display:flex; align-items:center; justify-content:space-between; gap:5mm; }
  .foot .l .cinfo { margin-top:1mm; font-size:6.7pt; font-weight:400; letter-spacing:0;
                    line-height:1.42; opacity:.93; }
  .foot .l .cinfo span { display:block; white-space:nowrap; }
  .foot .l .cinfo b { font-weight:600; opacity:.8; }
  .foot .l .cinfo a { color:#bfe6ff; text-decoration:none; }
  .qrbar { flex:0 0 auto; display:flex; justify-content:center; align-items:flex-start; gap:8mm; }
  .qc { text-align:center; }
  .qc img { width:15mm; height:15mm; background:#fff; border-radius:1.4mm; padding:.8mm; display:block; }
  .qc .ph { width:15mm; height:15mm; border:1px dashed rgba(255,255,255,.85); border-radius:1.4mm;
            background:rgba(255,255,255,.18); display:flex; align-items:center; justify-content:center;
            font-size:5.2pt; color:rgba(255,255,255,.85); line-height:1.2; }
  .qc .t { font-size:7pt; margin-top:1.1mm; opacity:.96; }
  .foot .l { flex:0 0 46mm; font-size:9.4pt; font-weight:900; letter-spacing:.3px; line-height:1.35; }
  .foot .r { flex:0 0 46mm; }   /* 左右固定等宽 46mm → 中间三码严格居中 */
  .foot .r { font-size:7.7pt; text-align:right; line-height:1.5; opacity:.95; white-space:nowrap; }
  .stamp { font-size:6.6pt; color:#9fb0c0; text-align:center; padding:2.5mm 0 1.5mm; }

  @page { size:A4; margin:0; }
  @media print { body { background:#fff; } .editbar,.edithint,.stamp { display:none !important; } }
"""

def build():
    hl = ''.join(
        '<div class="c"><div class="h"><i>%s</i>%s</div><div class="d">%s</div></div>' % (i, _html.escape(t), _html.escape(d))
        for i, t, d in HIGHLIGHTS)
    figs = ''
    for key, cls, t, s in FIGS:
        figs += ('<div class="fig"><div class="box"><img src="%s" alt="%s"></div>'
                 '<div class="cap"><div class="t">%s</div><div class="s">%s</div></div></div>'
                 % (IMG[int(key.replace('image', ''))], _html.escape(t), _html.escape(t), _html.escape(s)))

    return """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>华南农业大学 · mRNA、circRNA 生物制药工艺开发报告</title>
<style>__CSS__
__EDITCSS__</style>
<script>__EDITJS__</script>
</head>
<body>
__TOOLBAR__
<div class="page">
  <div class="topbar">
    <img src="__LOGO__" alt="宝锐生物 BIORI BIOTECH">
    <div class="tb-slogan">__SLOGAN__</div>
    <div class="tb-qr">
      <div class="t">参会登记<small>扫码在线登记</small></div>
      <img src="__QRAPPLY__" alt="宝锐讲座参会登记二维码">
    </div>
  </div>

  <div class="hero">
    <div class="mol m1"></div><div class="mol m2"></div><div class="mol m3"></div>
    <div class="kicker">宝锐生物 · 生命科学系列技术报告</div>
    <h1>mRNA、circRNA<br><em>生物制药工艺开发</em>报告</h1>
    <div class="sub">围绕 RNA 领域「是什么 · 为什么 · 怎么做」，共同提高对 mRNA 领域的认知</div>
    <div class="sub" style="margin-top:2.2mm;font-size:9.5pt;opacity:.92">报告人：<b style="font-size:10.5pt">珠海宝锐生物科技有限公司</b></div>
    <div class="chips">
      <div class="chip"><span class="k">时间</span>__DATE__</div>
      <div class="chip"><span class="k">地点</span>__PLACE__</div>
      <div class="chip"><span class="k">主办</span>宝锐生物</div>
    </div>
  </div>

  <div class="main">
    <div>
      <div class="sec-t">报告简介</div>
      <div class="intro"><div class="cols">
        <p>__P1__</p>
        <p>__P2__</p>
        <p>__P3__</p>
      </div></div>
    </div>

    <div>
      <div class="sec-t">报告看点</div>
      <div class="hl">__HL__</div>
    </div>

    <div>
      <div class="sec-t">技术图表</div>
      <div class="figs">__FIGS__</div>
    </div>
  </div>

  <!-- 宝锐生物 · 生成于 __STAMP__ -->
  <div class="foot">
    <div class="l">__COMPANY__
      <div class="cinfo">
        <span><b>电话</b>__TEL__</span>
        <span><b>邮箱</b><a href="mailto:__MAIL__">__MAIL__</a></span>
        <span><b>网址</b><a href="https://__WEB__">__WEB__</a></span>
        <span><b>地址</b>__ADDR__</span>
      </div>
    </div>
    <div class="qrbar">
      <div class="qc"><img src="__QRWX__" alt="宝锐公众号二维码"><div class="t">宝锐公众号</div></div>
      <div class="qc"><img src="__QRXHS__" alt="宝锐小红书二维码"><div class="t">宝锐小红书</div></div>
      <div class="qc"><img src="__QRSPH__" alt="宝锐视频号二维码"><div class="t">宝锐视频号</div></div>
    </div>
    <div class="r">2026年9月22日（周二）<br>14:00–16:30<br>__PLACE__</div>
  </div>
</div>
</body>
</html>
""".replace('__CSS__', CSS) \
   .replace('__FIGBOX__', FIGBOX) \
   .replace('__LOGO__', LOGO_B64) \
   .replace('__QRAPPLY__', QR_APPLY) \
   .replace('__QRWX__', QR_SOC['公众号']) \
   .replace('__QRXHS__', QR_SOC['小红书']) \
   .replace('__QRSPH__', QR_SOC['视频号']) \
   .replace('__DATE__', EVENT_DATE) \
   .replace('__PLACE__', EVENT_PLACE) \
   .replace('__COMPANY__', COMPANY) \
   .replace('__SLOGAN__', SLOGAN) \
   .replace('__TEL__', TEL) \
   .replace('__WEB__', WEB) \
   .replace('__MAIL__', MAIL) \
   .replace('__ADDR__', ADDR) \
   .replace('__P1__', COPY_P1) \
   .replace('__P2__', COPY_P2) \
   .replace('__P3__', COPY_P3) \
   .replace('__HL__', hl) \
   .replace('__FIGS__', figs) \
   .replace('__STAMP__', STAMP)

def main():
    html = build()
    # 断言所有宏已替换
    # 注：__EDITCSS__/__EDITJS__/__TOOLBAR__ 由 apply_edit 注入，此处不校验
    leftovers = [x for x in ('__CSS__', '__FIGBOX__', '__LOGO__', '__QRAPPLY__',
                             '__DATE__', '__PLACE__', '__COMPANY__',
                             '__P1__', '__P2__', '__P3__',
                             '__HL__', '__FIGS__', '__STAMP__') if x in html]
    assert not leftovers, '未替换宏: %s' % leftovers
    # 注入可编辑图层（容器 .page，打印隐藏工具栏）
    html = apply_edit(html, ls_key='bior_poster_hn_20260922',
                      file_base='华南农业大学_mRNA_circRNA工艺报告_讲座海报',
                      container='.page', hint_anchor='<div class="page">')
    assert '__LSKEY__' not in html and '__EDITCSS__' not in html and '__EDITJS__' not in html, '可编辑层未正确注入'
    # 截图用：加 #clean 隐藏编辑工具栏（打印 CSS 已隐藏，截图需显式处理）
    html = html.replace('</body>', "<script>if(location.hash.indexOf('clean')>=0){document.querySelectorAll('.editbar,.edithint').forEach(function(e){e.style.display='none';});}</script>\n</body>", 1)
    with io.open(OUT_HTML, 'w', encoding='utf-8') as f:
        f.write(html)
    print('  ✅ HTML %s (%.1f KB, 自包含: 内嵌 %d 图 + Logo + 可编辑图层)'
          % (os.path.basename(OUT_HTML), os.path.getsize(OUT_HTML) / 1024, len(IMG)))
    print('  ✅ 文案取自 docx 逐字；图表 %d 张（含电泳 M/1–8、DLS、circGFP 表达）' % len(FIGS))
    print('  ✅ 生成时间戳 %s（非 00:00）' % STAMP)
    # 内联 JS 语法检查
    js = '\n;\n'.join(re.findall(r'<script>(.*?)</script>', html, re.S))
    with io.open('/tmp/_poster_chk.js', 'w', encoding='utf-8') as f:
        f.write(js)
    import subprocess
    r = subprocess.run(['node', '--check', '/tmp/_poster_chk.js'], capture_output=True, text=True)
    print('  %s 内联 JS 语法' % ('✅' if r.returncode == 0 else '❌ ' + r.stderr[:300]))

main()
