跳到正文
cccskills
免费在 GitHub 公开

beamer-ppt

Create Beamer-style academic PPTX presentations using python-pptx. Produces publication-quality .pptx files with navy-blue Metropolis theme (16:9, frame title bars, progress bar) for conference talks, job market presentations, and seminar slides. Called by /present command.

中文简介正在准备中。当前显示原文说明。

集成工具PowerPoint

实测评分 未实测

查看安装方法

包含的文件(1)

  • SKILL.md14.6 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Beamer-ppt-Creator

Purpose

This skill generates professional academic PPTX presentations that faithfully replicate the visual style of LaTeX Beamer (Metropolis theme). Output is a .pptx file that can be opened, edited, and presented directly in PowerPoint or LibreOffice Impress — no LaTeX installation required.

When to Use

  • Called by /present command to produce the final slides/slides.pptx
  • Preparing conference, seminar, or job market slides
  • Converting a completed economics paper into a slide deck

Design Principles

  • One idea per slide — split if content overflows
  • Minimum 20pt for body text; 24pt for frame titles
  • Consistent palette — navy blue primary, one accent color only
  • Figures over tables — embed PNG images at ≥ 200 DPI
  • Last slide = Takeaways, never "Questions?"

Implementation

This skill executes Python code using python-pptx. Always install dependencies first:

pip install python-pptx pdf2image --break-system-packages
apt-get install -y poppler-utils 2>/dev/null || true

Color Palettes by Theme

ThemeTitle Bar bgAccentSlide bg
A. Metropolis (default)RGB(0, 35, 82) navyRGB(180, 30, 30) redRGB(245, 245, 245) light gray
B. Minimal (job market)RGB(0, 35, 82) navyRGB(0, 35, 82) navyRGB(255, 255, 255) white
C. Madrid (traditional)RGB(31, 73, 125) dark blueRGB(189, 152, 44) goldRGB(255, 255, 255) white

Core Helper Functions

from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
import os

# ── Presentation setup ───────────────────────────────────────────
prs = Presentation()
prs.slide_width  = Inches(13.33)   # 16:9 widescreen (Beamer aspectratio=169)
prs.slide_height = Inches(7.5)

# ── Color definitions (Metropolis theme) ─────────────────────────
NAVY  = RGBColor(0, 35, 82)
RED   = RGBColor(180, 30, 30)
LGRAY = RGBColor(245, 245, 245)
WHITE = RGBColor(255, 255, 255)
BLACK = RGBColor(30, 30, 30)
MGRAY = RGBColor(100, 100, 100)


def add_bg(slide, prs, color):
    """Full-slide background rectangle."""
    shape = slide.shapes.add_shape(
        1, 0, 0, prs.slide_width, prs.slide_height)
    shape.fill.solid()
    shape.fill.fore_color.rgb = color
    shape.line.fill.background()
    return shape


def add_frame_title(slide, prs, text, bg=NAVY, fg=WHITE):
    """Navy title bar (1.1 in tall) — mimics Beamer \\frametitle."""
    bar = slide.shapes.add_shape(
        1, 0, 0, prs.slide_width, Inches(1.1))
    bar.fill.solid()
    bar.fill.fore_color.rgb = bg
    bar.line.fill.background()
    tf = bar.text_frame
    tf.word_wrap = False
    tf.margin_left = Inches(0.3)
    tf.margin_top  = Inches(0.22)
    p = tf.paragraphs[0]
    p.text = text
    p.font.bold  = True
    p.font.size  = Pt(24)
    p.font.color.rgb = fg
    p.alignment  = PP_ALIGN.LEFT


def add_progress_bar(slide, prs, current, total, color=NAVY):
    """Metropolis-style thin progress bar at bottom."""
    h   = Inches(0.055)
    top = prs.slide_height - h
    # Gray track
    track = slide.shapes.add_shape(
        1, 0, top, prs.slide_width, h)
    track.fill.solid()
    track.fill.fore_color.rgb = RGBColor(200, 200, 200)
    track.line.fill.background()
    # Filled portion
    filled_w = int(prs.slide_width * current / max(total, 1))
    if filled_w > 0:
        bar = slide.shapes.add_shape(1, 0, top, filled_w, h)
        bar.fill.solid()
        bar.fill.fore_color.rgb = color
        bar.line.fill.background()


def add_speaker_notes(slide, notes_text):
    """Add speaker notes to a slide."""
    slide.notes_slide.notes_text_frame.text = notes_text

Slide Factory Functions

# ── 1. Title slide ───────────────────────────────────────────────
def make_title_slide(prs, title, subtitle, author, institute, date_line):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    add_bg(slide, prs, NAVY)

    def _tb(left, top, w, h):
        tb = slide.shapes.add_textbox(
            Inches(left), Inches(top), Inches(w), Inches(h))
        tb.text_frame.word_wrap = True
        return tb.text_frame

    # Paper title
    tf = _tb(1, 1.7, 11.33, 2.0)
    p = tf.paragraphs[0]
    p.text = title; p.font.bold = True
    p.font.size = Pt(34); p.font.color.rgb = WHITE
    p.alignment = PP_ALIGN.CENTER

    # Subtitle
    if subtitle:
        p2 = tf.add_paragraph()
        p2.text = subtitle; p2.font.size = Pt(20)
        p2.font.color.rgb = LGRAY; p2.alignment = PP_ALIGN.CENTER

    # Author + institute
    tf2 = _tb(1, 4.3, 11.33, 1.4)
    p3 = tf2.paragraphs[0]
    p3.text = author; p3.font.size = Pt(18)
    p3.font.color.rgb = WHITE; p3.alignment = PP_ALIGN.CENTER
    p4 = tf2.add_paragraph()
    p4.text = institute; p4.font.size = Pt(15)
    p4.font.color.rgb = LGRAY; p4.alignment = PP_ALIGN.CENTER

    # Date / conference
    tf3 = _tb(1, 6.1, 11.33, 0.8)
    p5 = tf3.paragraphs[0]
    p5.text = date_line; p5.font.size = Pt(13)
    p5.font.color.rgb = LGRAY; p5.alignment = PP_ALIGN.CENTER
    return slide


# ── 2. Content slide (bullet list) ──────────────────────────────
def make_content_slide(prs, title, bullets,
                       current=None, total=None, bg=LGRAY):
    """
    bullets: list of (indent_level, text) tuples.
    indent_level 0 = top-level bullet, 1 = sub-bullet.
    """
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    add_bg(slide, prs, bg)
    add_frame_title(slide, prs, title)

    tb = slide.shapes.add_textbox(
        Inches(0.5), Inches(1.3), Inches(12.33), Inches(5.8))
    tf = tb.text_frame; tf.word_wrap = True
    for i, (lvl, text) in enumerate(bullets):
        p = tf.paragraphs[i] if i == 0 else tf.add_paragraph()
        p.text = text; p.level = lvl
        p.font.size = Pt(20 if lvl == 0 else 17)
        p.font.color.rgb = BLACK
        p.space_before = Pt(8 if lvl == 0 else 4)

    if current and total:
        add_progress_bar(slide, prs, current, total)
    return slide


# ── 3. Figure slide ──────────────────────────────────────────────
def make_figure_slide(prs, title, img_path, caption="",
                      current=None, total=None):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    add_bg(slide, prs, LGRAY)
    add_frame_title(slide, prs, title)

    slide.shapes.add_picture(
        img_path,
        left=Inches(1.17), top=Inches(1.3),
        width=Inches(11.0), height=Inches(5.2))

    if caption:
        cap = slide.shapes.add_textbox(
            Inches(0.5), Inches(6.6), Inches(12.33), Inches(0.7))
        cap.text_frame.paragraphs[0].text = caption
        cap.text_frame.paragraphs[0].font.size = Pt(11)
        cap.text_frame.paragraphs[0].font.color.rgb = MGRAY

    if current and total:
        add_progress_bar(slide, prs, current, total)
    return slide


# ── 4. Regression table slide ────────────────────────────────────
def make_table_slide(prs, title, headers, rows,
                     footnote="", highlight_last_col=True,
                     current=None, total=None):
    """
    headers: list of str (first col is row label).
    rows:    list of lists of str.
    Last column is treated as the preferred specification (bolded).
    """
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    add_bg(slide, prs, LGRAY)
    add_frame_title(slide, prs, title)

    nc = len(headers); nr = len(rows) + 1
    tbl = slide.shapes.add_table(
        nr, nc,
        Inches(0.5), Inches(1.4),
        Inches(12.33), Inches(4.5)).table

    # Header row — navy background, white bold text
    for j, h in enumerate(headers):
        c = tbl.cell(0, j)
        c.text = h
        c.text_frame.paragraphs[0].font.bold = True
        c.text_frame.paragraphs[0].font.size = Pt(14)
        c.text_frame.paragraphs[0].font.color.rgb = WHITE
        c.fill.solid(); c.fill.fore_color.rgb = NAVY

    # Data rows
    for i, row in enumerate(rows):
        for j, val in enumerate(row):
            c = tbl.cell(i + 1, j)
            c.text = str(val)
            c.text_frame.paragraphs[0].font.size = Pt(13)
            if highlight_last_col and j == nc - 1:
                c.text_frame.paragraphs[0].font.bold = True

    if footnote:
        fn = slide.shapes.add_textbox(
            Inches(0.5), Inches(6.0), Inches(12.33), Inches(1.2))
        fn.text_frame.paragraphs[0].text = footnote
        fn.text_frame.paragraphs[0].font.size = Pt(10)
        fn.text_frame.paragraphs[0].font.color.rgb = MGRAY

    if current and total:
        add_progress_bar(slide, prs, current, total)
    return slide


# ── 5. Two-column slide ──────────────────────────────────────────
def make_two_col_slide(prs, title, left_bullets, right_bullets,
                       current=None, total=None):
    """Two-column layout (e.g. Robustness slide)."""
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    add_bg(slide, prs, LGRAY)
    add_frame_title(slide, prs, title)

    for col_bullets, left_offset in [(left_bullets, 0.4),
                                      (right_bullets, 6.9)]:
        tb = slide.shapes.add_textbox(
            Inches(left_offset), Inches(1.35),
            Inches(5.8), Inches(5.8))
        tf = tb.text_frame; tf.word_wrap = True
        for i, (lvl, text) in enumerate(col_bullets):
            p = tf.paragraphs[i] if i == 0 else tf.add_paragraph()
            p.text = text; p.level = lvl
            p.font.size = Pt(18 if lvl == 0 else 15)
            p.font.color.rgb = BLACK
            p.space_before = Pt(6 if lvl == 0 else 3)

    if current and total:
        add_progress_bar(slide, prs, current, total)
    return slide

PDF → PNG Conversion (for figures from /plot)

import subprocess

def pdf_to_png(pdf_path, dpi=200):
    """Convert PDF figure to PNG for embedding in PPTX."""
    png_base = pdf_path.replace(".pdf", "")
    try:
        subprocess.run(
            ["pdftoppm", "-r", str(dpi), "-png", "-singlefile",
             pdf_path, png_base],
            check=True, capture_output=True)
        return png_base + ".png"
    except (subprocess.CalledProcessError, FileNotFoundError):
        # Fallback: pdf2image
        from pdf2image import convert_from_path
        imgs = convert_from_path(pdf_path, dpi=dpi)
        png_path = png_base + ".png"
        imgs[0].save(png_path, "PNG")
        return png_path

Save, Export PDF & Verify

import subprocess

def save_and_verify(prs, output_path, export_pdf=True):
    """Save PPTX, optionally export PDF via LibreOffice, then verify."""
    os.makedirs(os.path.dirname(output_path), exist_ok=True)
    prs.save(output_path)

    # ── Verify PPTX ──────────────────────────────────────────────
    check = Presentation(output_path)
    n = len(check.slides)
    assert n > 0, "PPTX is empty — check slide generation."
    print(f"✅ PPTX saved : {output_path}")
    print(f"   {n} slides | {os.path.getsize(output_path) // 1024} KB")

    # ── Export PDF ───────────────────────────────────────────────
    pdf_path = None
    if export_pdf:
        pdf_path = _pptx_to_pdf(output_path)

    return output_path, pdf_path


def _pptx_to_pdf(pptx_path):
    """Convert PPTX → PDF using LibreOffice headless."""
    out_dir = os.path.dirname(pptx_path)
    try:
        result = subprocess.run(
            ["libreoffice", "--headless", "--convert-to", "pdf",
             "--outdir", out_dir, pptx_path],
            capture_output=True, text=True, timeout=120
        )
        pdf_path = pptx_path.replace(".pptx", ".pdf")
        if os.path.exists(pdf_path):
            print(f"✅ PDF exported: {pdf_path}")
            print(f"   {os.path.getsize(pdf_path) // 1024} KB")
            return pdf_path
        else:
            print(f"⚠️  LibreOffice conversion failed: {result.stderr.strip()}")
            print("   → Open slides.pptx in PowerPoint and export manually.")
            return None
    except FileNotFoundError:
        print("⚠️  LibreOffice not found. Install with:")
        print("   apt-get install -y libreoffice   # Ubuntu/Debian")
        print("   brew install --cask libreoffice  # macOS")
        print("   → You can also export PDF from PowerPoint / LibreOffice Impress.")
        return None
    except subprocess.TimeoutExpired:
        print("⚠️  LibreOffice timed out. Try running manually:")
        print(f"   libreoffice --headless --convert-to pdf {pptx_path}")
        return None

Slide Structure by Presentation Type

Slide Section15-min conf (≤15)45-min seminar (≤30)Job market (≤20)
Title111
Motivation1–22–32–3
This Paper111
Related Lit—1–21–2
Data122
Identification23–43
Main Results35–74–5
Robustness12–32
Heterogeneity—2–31–2
Takeaways111

Best Practices

  1. One message per slide — split if content overflows
  2. Use figures over tables — embed PNG at ≥ 200 DPI
  3. Bold the preferred specification column in regression tables
  4. Add speaker notes to every key slide via add_speaker_notes()
  5. Prepare appendix slides for anticipated Q&A
  6. Timing: budget 1.5 min/slide; final slide must be Takeaways

Common Pitfalls

  • ❌ Too much text (max 5 bullets per slide, max 10 words per bullet)
  • ❌ Tables with more than 4 columns
  • ❌ Ending with "Thank you / Questions?" — use Takeaways instead
  • ❌ Embedding low-resolution images (< 150 DPI looks blurry on projectors)
  • ❌ Skipping the "This Paper" preview slide

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

End-to-end data pipeline for empirical research: fetch economic data from APIs (FRED, World Bank, IMF, BLS, OECD, Yahoo Finance), clean and transform raw data, construct strategy-specific variables, and validate panel structure. Use when asked to fetch data, download data, clean data, merge datasets, prepare analysis-ready data.

中文简介正在准备中。当前显示原文说明。

zhouziyue233/great-econometrics82026年4月17日 更新

Econometrics skill for Difference-in-Differences (DID) analysis. Activates when the user asks about: "difference in differences", "DID", "DiD", "diff-in-diff", "parallel trends", "treatment group", "control group", "pre-treatment", "post-treatment", "policy evaluation", "natural experiment", "staggered DID", "event study regression", "two-way fixed effects DID", "callaway santanna", "sun and abraham", "双重差分", "倍差法", "平行趋势", "处理组", "对照组", "政策评估", "事件研究", "交错DID", "渐进处理"

中文简介正在准备中。当前显示原文说明。

zhouziyue233/great-econometrics82026年4月17日 更新

figure

免费

Called by /plot to generate and upgrade econometric figures to top-journal standards.

中文简介正在准备中。当前显示原文说明。

zhouziyue233/great-econometrics82026年4月17日 更新

Econometrics skill for instrumental variables and treatment effect estimation. Activates when the user asks about: "instrumental variables", "IV estimation", "2SLS", "two-stage least squares", "endogeneity", "weak instruments", "first stage", "Sargan test", "overidentification", "propensity score matching", "PSM", "average treatment effect", "ATT", "LATE", "local average treatment effect", "endogenous regressor", "instrument validity", "工具变量", "两阶段最小二乘", "内生性", "弱工具变量", "倾向得分匹配", "平均处理效应", "处理效应", "局部平均处理效应"

中文简介正在准备中。当前显示原文说明。

zhouziyue233/great-econometrics82026年4月17日 更新

Search, summarize, and synthesize economics literature. find research gaps, position your contribution.

中文简介正在准备中。当前显示原文说明。

zhouziyue233/great-econometrics82026年4月17日 更新

ml-causal

免费

Econometrics skill for machine learning methods in causal inference. Activates when the user asks about: "causal forest", "generalized random forest", "GRF", "double machine learning", "DML", "debiased machine learning", "LASSO for variable selection", "post-LASSO", "heterogeneous treatment effects", "CATE", "conditional average treatment effect", "BLP analysis", "CLAN analysis", "causal tree", "honest estimation", "因果森林", "双重机器学习", "异质性处理效应", "条件平均处理效应", "LASSO变量选择", "机器学习因果推断", "去偏机器学习"

中文简介正在准备中。当前显示原文说明。

zhouziyue233/great-econometrics82026年4月17日 更新

查看 zhouziyue233 的全部技能

报告此技能的问题