Communitygithub.com

ethanweber/posterskill

根据论文和项目网站,生成一份可打印为 PDF 的 HTML 会议海报。

posterskill 是什么?

这是一款 AI 智能体技能,旨在从论文源文件和项目网站自动生成专业的、可打印的 HTML 会议海报。它通过一个独特的 React 交互式编辑器,允许用户在浏览器中直观地调整布局,然后将优化后的配置反馈给 AI 进行迭代改进。该技能的核心优势在于通过自动测量图像宽高比和 Playwright 优化循环,最大限度地减少空白区域。 它支持从 Overleaf 项目、项目网站、参考海报(用于风格匹配)和机构网站中提取内容。工作流程包括:分析参考风格、从 LaTeX 源文件中提取内容、收集并转换图像、优化布局以消除空白、生成 PDF,最后与用户迭代调整。最终输出是一个完全自包含的 HTML 文件,无需构建步骤,所有资源都位于一个 `poster/` 目录中。用户可以通过拖放列分隔线和行分隔线、交换卡片以及调整字体大小来微调布局。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ethanweber/posterskill/tree/HEAD/.claude/skills/make-poster

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

预览

来自技能 README

posterskill preview

文档

会议海报生成器 (HTML)

生成专业的 HTML 海报。用户备注:$ARGUMENTS

此海报是一个基于 React 的交互式编辑器——一个位于 poster/ 目录下的单一自包含 HTML 文件。无需构建步骤(React/Babel 通过 CDN 加载)。用户可以在浏览器中直观地调整布局,然后将配置导出回 Claude 以进行进一步修改。

项目文件夹结构

<project>/
├── overleaf/              # Overleaf 的论文源文件
│   ├── paper.tex
│   ├── figures/
│   └── ...
├── references/            # 用于风格匹配的参考海报
│   └── (任何格式:pdf, png, jpg, html, pptx, ...)
├── poster/                # 生成的自包含海报网站
│   ├── index.html         # 海报 (React 应用)
│   ├── poster-config.json # 布局配置 (列数、卡片顺序、高度、字体缩放)
│   ├── logos/             # 机构徽标
│   ├── teaser.png         # 复制/转换的图表
│   ├── qr.png             # 项目页面二维码
│   ├── qr-posterskill.png # Posterskill 二维码
│   └── ...
└── .claude/skills/make-poster/
  • overleaf/ 包含论文源文件。读取主 .tex 文件及其通过 \input{} 引入的任何文件。
  • references/ 包含展示用户偏好的视觉风格的示例海报。读取/查看此文件夹中的所有文件以匹配其设计语言。
  • poster/ 是生成的输出——一个自包含的网站。所有图表和资产都与 index.html 放置在一起,相对路径即可正常工作。

输入

  1. 论文源文件 - 位于 overleaf/。询问用户哪个 .tex 文件是主文件(例如 paper.tex、main.tex)。然后读取它及其通过 \input{} 引入的任何文件。
  2. 项目网站 - 如果尚未知晓,询问用户 URL。使用 WebFetch 提取作者信息、托管图像和链接。
  3. 参考海报 - 从 references/ 自动发现。查看其中的所有文件并匹配其风格。
  4. 作者网站 (可选) - 使用 WebFetch 提取设计信号(调色板、字体、徽标)。使用 Playwright 从作者网站下载机构徽标(curl 可能因重定向失败):page.request.get(url) 以下载原始字节。
  5. 格式要求 - 询问海报尺寸、方向和列数。
  6. Git 仓库 (可选) - 询问用户是否有 GitHub 仓库来推送海报。

如果用户未指定格式,请在继续前询问。不要假设尺寸、方向或列数的默认值。

流程

第 0 步:分析风格参考

在 references/ 中查找任何 PDF、PNG 或图像文件。将 PDF 转换为 PNG(在 macOS 上使用 sips -s format png)。查看每个文件并记录视觉风格——布局、颜色、字体、卡片样式、图表放置。匹配参考风格——如果参考是浅色主题,则不要默认使用深色主题。

第 1 步:从论文源文件提取内容

询问用户要读取哪个 .tex 文件。提取:标题、作者、附属机构、摘要(2-3 句)、关键方法、结果(表格 + 图表)、关键公式(最多 1-2 个)、结论。

第 2 步:获取项目网站

使用 WebFetch 获取作者姓名、附属机构、图表 URL、用于 QR 的项目 URL、代码/arxiv/视频的链接。

第 3 步:收集资产到 poster/

图表: 从 overleaf/figures/ 复制,将 PDF 转换为高分辨率 PNG:

sips -s format png input.pdf --out poster/output.png -Z 3000

网站图像: 使用 Playwright 从项目网站下载更高质量的图像(不要用 curl——许多网站会重定向):

resp = page.request.get(url)
with open('poster/filename.png', 'wb') as f:
    f.write(resp.body())

徽标: 使用 Playwright 从作者个人网站下载机构徽标。保存到 poster/logos/。模板会自动将其反色为白色以用于标题。

二维码: 生成并保存:

curl -sL -o poster/qr.png "https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=PROJECT_URL"
curl -sL -o poster/qr-posterskill.png "https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=https://github.com/ethanweber/posterskill"

第 4 步:测量图像宽高比

这对于消除空白至关重要。 测量每张图像:

sips -g pixelWidth -g pixelHeight poster/*.png poster/*.jpg

然后根据宽高比将图像分配到列:

  • 宽图像 (比例 > 2:1,例如展示图、架构图):放在最宽的列中
  • 方形图像 (比例 ~ 1:1):放在较窄的列中
  • 竖版图像 (比例 < 1:1):放在最窄的列中

这可以防止头号空白问题:宽图像放在窄单元格中(反之亦然)会留下巨大空隙。

第 5 步:生成海报 HTML

使用 ${{CLAUDE_SKILL_DIR}}/template.html 作为起点。模板是一个 React 应用,包含:

架构:

  • CARD_REGISTRY — 定义每个卡片的内容(标题、颜色、JSX 主体)
  • DEFAULT_LAYOUT — 定义列结构和卡片顺序
  • DEFAULT_LOGOS — 用于标题的机构徽标
  • React 状态管理布局,具有 localStorage 持久化
  • window.posterAPI 公开用于程序控制的函数

关键的自定义项:

  1. 使用论文内容更新 CARD_REGISTRY(每个部分是一张卡片)
  2. 使用经过宽高比优化的列分配更新 DEFAULT_LAYOUT
  3. 使用用户的机构徽标更新 DEFAULT_LOGOS
  4. 更新 DEFAULT_FONT_SCALE(从 1.3 开始,用户可以通过 A-/A+ 按钮调整)
  5. 更新标题(标题、作者、附属机构、会议徽章、二维码)
  6. 更新 @page { size: WIDTHmm HEIGHTmm; } 和 body { width: WIDTHmm; height: HEIGHTmm; } 以适应海报尺寸
  7. 使用相同尺寸更新 posterAPI 的 fit() 函数

卡片内容模式:

  • 图表卡片:<div className="fig"><div className="fig-wrap"><img src="file.png" alt="..." /></div><div className="cap"><b>标题。</b>描述。</div></div>
  • 文本卡片:<div className="hl"><p>高亮文本</p></div><ul><li>要点 1</li></ul>
  • 表格卡片:<table><thead>...</thead><tbody>...</tbody></table> 并在获胜单元格上使用 className="best"
  • 公式卡片:<div className="eq">{'$LaTeX equation$'}</div>(在 JSX 中转义反斜杠)

关键 CSS 规则(零空白):

  • 图像必须使用 width:100%; height:100%; object-fit:contain(不能使用 max-width/max-height——它们会阻止放大)
  • 每列必须始终有一张 grow: true(flex:1)的卡片来填充剩余空间
  • isCardGrow() 函数会自动确保这一点——如果没有卡片增长,最后一张卡片将获得增长属性

视口缩放:

  • 使用 body 上的 translate() + scale() 将海报居中对齐并适配任何浏览器视口
  • transform-origin: top left 至关重要
  • @media print 必须设置 transform: none !important 以获得正确的打印分辨率
  • getBoundingClientRect() 返回缩放后的值——始终在调整大小处理程序中除以 currentScaleRef.current

第 6 步:使用 Playwright 自动优化布局

生成后,使用 Playwright 测量空白并找到最佳列宽:

from playwright.sync_api import sync_playwright
import os

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={'width': 3200, 'height': 2260})
    page.goto('file://' + os.path.abspath('poster/index.html'))
    page.wait_for_load_state('networkidle')
    page.wait_for_timeout(3000)

    # 测量空白
    waste = page.evaluate('window.posterAPI.getWaste()')
    print(f"总空白: {waste['total']}px")
    for d in waste['details']:
        print(f"  {d['card']}: H={d['wasteH']} W={d['wasteW']} ({d['pct']}%)")

    # 尝试不同的列宽以最小化空白
    best_waste = waste['total']
    best_c1, best_c3 = 300, 230
    for c1 in range(200, 350, 10):
        for c3 in range(160, 280, 10):
            page.evaluate(f'window.posterAPI.setColumnWidth("col1", {c1})')
            page.evaluate(f'window.posterAPI.setColumnWidth("col3", {c3})')
            page.wait_for_timeout(30)
            w = page.evaluate('window.posterAPI.getWaste().total')
            if w < best_waste:
                best_waste = w
                best_c1, best_c3 = c1, c3

    # 应用最佳值并截屏
    page.evaluate(f'window.posterAPI.setColumnWidth("col1", {best_c1})')
    page.evaluate(f'window.posterAPI.setColumnWidth("col3", {best_c3})')
    page.wait_for_timeout(500)
    page.screenshot(path='/tmp/poster_screenshot.png')

    # 也尝试在列之间交换卡片
    page.evaluate('window.posterAPI.swapCards("cardA", "cardB")')
    # ... 再次测量空白 ...

    browser.close()

然后读取 /tmp/poster_screenshot.png 进行视觉检查。迭代多次——截屏、修复问题、重新截屏,直到海报的空白区域最小。

找到最佳值后,将它们烘焙到 HTML 的 DEFAULT_LAYOUT、DEFAULT_CARD_HEIGHTS 等中。

第 7 步:生成 PDF 并验证

page.pdf(
    path='poster/poster.pdf',
    width='841mm', height='594mm',  # 匹配海报尺寸
    margin={'top':'0','right':'0','bottom':'0','left':'0'},
    print_background=True
)

将 PDF 转换为 PNG 并读取它以验证它能以全分辨率渲染:

sips -s format png poster/poster.pdf --out /tmp/poster_pdf_check.png -Z 3000

第 8 步:打开并与用户迭代

在浏览器中打开海报:

open poster/index.html

向用户解释编辑控件:

  • 预览 — 关闭编辑 UI 以查看打印时的确切效果
  • A-/A+ — 全局调整字体大小
  • 拖动列分隔线 (垂直蓝色条) — 左右调整列宽
  • 拖动行分隔线 (水平蓝色条) — 在列内上下调整卡片高度
  • 单击交换 — 单击一张卡片的菱形手柄(变为橙色),然后单击另一张卡片进行交换
  • 移动/插入 — 单击卡片手柄,然后单击虚线橙色放置区将其移动到那里
  • 保存 — 下载 poster-config.json
  • 复制配置 — 将布局 JSON 复制到剪贴板
  • 重置 — 恢复默认值

主动建议改进: 展示初稿后,建议具体更改:

  • "模型架构卡片有一些空白——尝试将其上方的行分隔线向下拖动,给它更少的空间"
  • "完成图可能放在第 2 列更好看,因为它更宽——尝试单击其菱形,然后单击第 2 列中的一个放置区"
  • "您可能想使用 A+ 按钮增加几次字体大小"

鼓励反馈循环: 告诉用户:

尝试在浏览器中重新排列海报!当您对布局满意时,单击右上角工具栏中的复制配置并粘贴到这里——我会将这些更改烘焙到默认值中,以便它们持久化。

  • 保存 — 下载 poster-config.json
  • 复制配置 — 将布局 JSON 复制到剪贴板以粘贴到 Claude
  • 重置 — 恢复默认值

当用户粘贴配置 JSON 时,更新 HTML 中的 DEFAULT_LAYOUT、DEFAULT_CARD_HEIGHTS、DEFAULT_FONT_SCALE 和 DEFAULT_LOGOS 以匹配。同时将其写入 poster-config.json。

第 9 步:推送到 GitHub(可选)

如果用户提供 GitHub 仓库 URL:

cd poster
git init
git remote add origin <REPO_URL>
git add .
git commit -m "Poster: <paper title>"
git push -u origin main

重要指南

  • 无空白区域。 这是第一优先级。使用宽高比感知的列分配、width:100%; height:100%; object-fit:contain 的图像、自动增长卡片和 Playwright 优化器。迭代直到空白最小化。
  • 文本尽量精简。 海报是视觉化的——使用要点,不要用段落。目标是在 2 分钟内理解。
  • 匹配参考风格。 如果参考海报是浅色/干净的,不要使用深色主题。匹配整体美学。
  • 字体缩放。 所有文本大小使用 calc(Xpt * var(--font-scale)),以便 A-/A+ 按钮工作。从 --font-scale: 1.3 开始,让用户调整。
  • 打印优化的 CSS。 @media print 隐藏所有编辑 UI 并设置 transform: none !important。@page 设置精确尺寸。
  • Posterskill 二维码。 始终在标题中包含一个指向 https://github.com/ethanweber/posterskill 的二维码,标签为"使用我的 Claude 技能制作的海报"。
  • 无致谢页脚。 保持海报干净——默认没有页脚。
  • 标题中的徽标。 从作者网站下载机构徽标,保存到 poster/logos/,并在 DEFAULT_LOGOS 中列出。它们通过 CSS 滤镜自动反色为白色。
  • 自包含。 无需构建步骤、npm 或服务器。单个 HTML 文件,依赖 CDN。直接作为 file:// 打开即可工作。
  • 公式。 使用 KaTeX(通过 CDN 加载)。在 JSX 字符串中转义反斜杠:{'$\\mathcal{E}$'}。

图表处理

  • 始终将所需的图表复制到 poster/ 中——不要在 HTML 中引用 overleaf/ 的路径。
  • 将 PDF 转换为高分辨率 PNG:sips -s format png input.pdf --out poster/output.png -Z 3000
  • 通过 Playwright 的 page.request.get() 下载网站图像(不要用 curl——网站通常重定向)。
  • 使用 sips -g pixelWidth -g pixelHeight 测量宽高比,并相应地分配到列。

用户配置更新工作流

用户可以在浏览器中调整海报并将更改分享回来:

  1. 用户单击工具栏中的"复制配置"
  2. 用户将 JSON 粘贴到聊天中
  3. Claude 更新 index.html 中的 DEFAULT_LAYOUT、DEFAULT_CARD_HEIGHTS、DEFAULT_FONT_SCALE、DEFAULT_LOGOS 以匹配
  4. Claude 还将配置写入 poster-config.json
  5. 用户刷新页面并单击"重置"以加载新的默认值

程序化 API (window.posterAPI)

可通过浏览器控制台或 Playwright 使用:

  • swapCards(id1, id2) — 交换两张卡片(跨列工作)
  • moveCard(cardId, targetColId, position) — 将卡片移动到指定位置
  • setColumnWidth(colId, widthMm) — 设置列宽(null 表示 flex)

…

[已截断]

相关技能