brand-pptx — テンプレート準拠の PowerPoint 生成スキル
概要
テンプレート PPTX を複製し、レイアウトを選んで、プレースホルダーを差し替えるだけで、 そのテンプレートのデザイン(配色・装飾・ロゴ)に沿ったスライドを生成する。
このスキルの肝は 「見た目をプロンプトで指示しない」 こと。 配色・装飾・レイアウトはすべて テンプレート側 に持たせ、生成コードは中身を流し込むだけ。 だから誰が・何度頼んでも、テンプレートから離れたスライドにはならない。
- 同梱のクリーンな既定テンプレート(
assets/template.pptx, 16:9) ですぐに動く。 - 自社テンプレート(.pptx)に差し替えれば、そのブランドをそのまま再現できる。差し替え手順は下記「初回セットアップ」。
なぜテンプレートを使うのか 「青系でモダンに」のようなテキスト指示だけでブランドを固定するのは現実的に不可能で、毎回ブレる。 配色・装飾・レイアウトを PPTX というデータ として固定し、そこから離れられないようにするのがこのスキルの設計思想。
初回セットアップ(最初に一度だけ確認する)
資料を作り始める前に、ユーザーに 一度だけ 確認する。難しい設定は不要。
「自社のテンプレート(.pptx)はありますか? あれば、それに合わせて生成します。無ければ同梱のクリーンなテンプレートで作ります。」
自社テンプレートを渡された場合(スキルが自動で設定する)
ユーザー設定は スキルの外(スキル更新で上書きされない場所)に保存する。既定の保存先は ~/.config/brand-pptx/。
勝手に作らない: このフォルダは「ユーザーが自社テンプレートを渡した=カスタマイズに同意した」場合にのみ作成する。通常利用では何も作成・変更しない。作成前に 保存先をユーザーに一言伝え、別の場所がよければ環境変数
$BRAND_PPTX_HOMEで指定してもらう。
- 保存先(既定
~/.config/brand-pptx/、または$BRAND_PPTX_HOME)をユーザーに伝えてから作成し、テンプレートをそこへコピーする(例:~/.config/brand-pptx/company.pptx)。 python3 tools/inspect_template.py ~/.config/brand-pptx/company.pptxでレイアウト一覧(index・名前・プレースホルダー)を取得する。~/.config/brand-pptx/theme.jsonを作成する(スキル同梱のtheme.jsonをコピーして編集するとよい)。template.pathを自社テンプレート名(例:"company.pptx")にし、layoutMapを自社レイアウトの index に合わせる。判断の目安:- cover: タイトル+サブタイトルのプレースホルダーがある表紙レイアウト
- section: 章扉(背景が濃い/ブランド色のことが多い)
- content: タイトル+本文(body)の標準レイアウト
- contentVisual: タイトルのみ(本文 PH なし)=図形を自由配置できるレイアウト
- ending: クロージング向けレイアウト
- 必要なら
~/.config/brand-pptx/theme.jsonのcolors(KPI カード等の描画色)も自社ブランドに合わせて更新する。 python3 tools/make_sample.pyでサンプルを出し、崩れていないか確認する。
以降スキルは
~/.config/brand-pptx/theme.jsonを 優先して読む(無ければ同梱の既定)。だからスキル本体を更新しても自社設定は保持される。
無い場合
同梱の assets/template.pptx のまま進める。設定は何も要らない。
テンプレートに合うレイアウトが無い役割があるとき(任意・opt-in)
自社テンプレートに、ある役割(例: セクション区切り)に向くレイアウトが無い場合:
- その役割を
theme.jsonのlayoutMapでnullにする(例:"section": null)。 theme.jsonのsetup.drawMissingRolesをtrueにする(既定はfalse)。
すると、その役割のスライドだけ theme の色でスライドに直接描画して補う。
テンプレート(マスター/レイアウト)自体は一切変更しないので、元のテンプレートは安全。
false のままなら、null の役割は白紙レイアウト + タイトルのみの簡素な出力になる。
theme.json の役割
読み込み場所:
~/.config/brand-pptx/theme.jsonがあればそれを優先($BRAND_PPTX_HOMEでも上書き可)。無ければスキル同梱の既定。ユーザー設定をスキル外に置けるので、スキル更新で消えない。
| キー | 役割 |
|---|---|
template.path |
使うテンプレート(既定: assets/template.pptx / 自社: assets/<your>.pptx) |
layoutMap |
役割(cover/section/content/contentVisual/ending)→ テンプレート内レイアウト index |
colors |
コードが描く視覚パーツ(KPI カード・進捗バー・ステップ・テーブル)の色。# を付けない 6 桁 hex |
fonts / typeScale |
プレースホルダーや視覚パーツに適用するフォント・サイズ |
setup.drawMissingRoles |
(任意・既定 false)layoutMap が null の役割を theme 色でスライドに合成描画する。テンプレート自体は変更しない |
表紙やセクションの背景・装飾はテンプレートが持つので、
colorsは主に「コードが描く部分」と「同梱既定テンプレートの再生成」に効く。 同梱テンプレートはpython3 tools/build_template.pyがtheme.jsonの色から生成する(色を変えて再実行すると既定テンプレートの配色も変わる)。
クイックリファレンス
| タスク | 方法 |
|---|---|
| テンプレートのレイアウト確認 | python3 tools/inspect_template.py [PPTX] |
| 新規作成 | テンプレートを複製 → layoutMap でレイアウト選択 → プレースホルダー差し替え |
| サンプル生成(動作確認) | python3 tools/make_sample.py → examples/sample.pptx |
| 既定テンプレートの再生成 | python3 tools/build_template.py → assets/template.pptx |
| 既存ファイルの読み取り | python3 -m markitdown deck.pptx |
| 既存ファイルの編集 | editing.md の XML 編集ワークフロー |
| テンプレートにないカスタムスライド | PptxGenJS で補助生成 → pptxgenjs.md |
新規作成ワークフロー
Step 0: テーマとテンプレートの読み込み
import json, os
from pathlib import Path
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE, PP_PLACEHOLDER
def find_skill_dir():
cands = []
pr = os.environ.get("CLAUDE_PLUGIN_ROOT")
if pr:
cands.append(Path(pr))
cands += [
Path.home() / ".claude" / "skills" / "brand-pptx",
Path.home() / ".agents" / "skills" / "brand-pptx",
Path.home() / ".config" / "skills" / "brand-pptx",
Path(__file__).resolve().parent if "__file__" in globals() else Path.cwd(),
Path.cwd(),
]
for c in cands:
if (c / "theme.json").exists():
return c
return Path.cwd()
SKILL_DIR = find_skill_dir()
def find_config_dir():
# ユーザー設定(theme.json/自社テンプレ)は「スキル更新で消えない場所」を優先する。
# 優先: $BRAND_PPTX_HOME → ~/.config/brand-pptx → (無ければ)スキル同梱の既定。
for c in [os.environ.get("BRAND_PPTX_HOME"), Path.home() / ".config" / "brand-pptx"]:
if c and (Path(c) / "theme.json").exists():
return Path(c)
return SKILL_DIR
CONFIG_DIR = find_config_dir()
theme = json.loads((CONFIG_DIR / "theme.json").read_text(encoding="utf-8"))
C = {k: RGBColor.from_string(v) for k, v in theme["colors"].items()}
F = theme["fonts"]
TS = theme["typeScale"]
LM = theme["layoutMap"]
# テンプレート解決: config_dir(ユーザー設定)→ skill_dir → 同梱既定 → 内蔵 の順
def resolve_template():
tpath = (theme.get("template") or {}).get("path")
if tpath:
p = Path(tpath)
if p.is_absolute():
return p if p.exists() else None
for base in (CONFIG_DIR, SKILL_DIR):
if (base / tpath).exists():
return base / tpath
default = SKILL_DIR / "assets" / "template.pptx"
return default if default.exists() else None
tpl = resolve_template()
prs = Presentation(str(tpl)) if tpl else Presentation()
print("config:", CONFIG_DIR, "| template:", tpl or "(builtin)")
Step 1: 要件確認
トピック、対象者、目的、スライド枚数を確認する(初回セットアップも忘れずに)。
Step 2: スライド構成の計画
各スライドに 役割(layoutMap のキー)を割り当てる。例:
Slide 1: cover (タイトル + サブタイトル/日付)
Slide 2: section (セクション区切り)
Slide 3-6: content / contentVisual
Slide 7: ending (クロージング)
Step 3: 生成(プレースホルダーを埋める)
背景・装飾はテンプレートのレイアウトが持つ。ここでは文字を流し込むだけ。
タイトル色は役割で決める(濃い背景の section / ending は白、白背景の cover / content は dark)。
def delete_all_slides(prs):
rels = '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id'
while len(prs.slides._sldIdLst):
rId = prs.slides._sldIdLst[0].attrib[rels]
prs.part.drop_rel(rId)
prs.slides._sldIdLst.remove(prs.slides._sldIdLst[0])
def _ph(slide, idx):
for ph in slide.placeholders:
if ph.placeholder_format.idx == idx:
return ph
return None
def _title_ph(slide):
"""タイトルPH。idx=0 を優先し、無ければ型でフォールバック(自社テンプレ対応)。"""
p0 = _ph(slide, 0)
if p0 is not None:
return p0
for ph in slide.placeholders:
if ph.placeholder_format.type in (PP_PLACEHOLDER.TITLE, PP_PLACEHOLDER.CENTER_TITLE):
return ph
return None
def _style_text(tf, size, color, bold=False, align=PP_ALIGN.LEFT, font=None, anchor=None):
if anchor is not None:
tf.vertical_anchor = anchor
for p in tf.paragraphs:
p.alignment = align
for r in (p.runs or [p.add_run()]):
r.font.size = Pt(size)
r.font.bold = bold
r.font.color.rgb = color
if font:
r.font.name = font
delete_all_slides(prs)
# --- カバー(白背景・濃い文字)---
slide = prs.slides.add_slide(prs.slide_layouts[LM["cover"]])
t = _title_ph(slide)
if t:
t.text = "プレゼンテーションタイトル"
_style_text(t.text_frame, TS["coverTitle"], C["dark"], bold=True,
font=F["headingCJK"], anchor=MSO_ANCHOR.MIDDLE)
sub = _ph(slide, 1)
if sub:
sub.text = "2026.01.01"
_style_text(sub.text_frame, TS["coverSubtitle"], C["gray"], font=F["bodyCJK"])
# --- セクション区切り(濃い背景・白文字)---
slide = prs.slides.add_slide(prs.slide_layouts[LM["section"]])
t = _title_ph(slide)
if t:
t.text = "セクションタイトル"
_style_text(t.text_frame, TS["sectionTitle"], C["white"], bold=True,
font=F["headingCJK"], anchor=MSO_ANCHOR.MIDDLE)
# --- コンテンツ(図形中心は contentVisual を使う)---
slide = prs.slides.add_slide(prs.slide_layouts[LM["contentVisual"]])
t = _title_ph(slide)
if t:
t.text = "コンテンツタイトル"
_style_text(t.text_frame, TS["title"], C["dark"], bold=True, font=F["headingCJK"])
# → ここに Step 4 のヘルパーで KPI カード等を配置
# --- エンディング(濃い背景・白文字・中央)---
slide = prs.slides.add_slide(prs.slide_layouts[LM["ending"]])
t = _title_ph(slide)
if t:
t.text = "ご清聴ありがとうございました"
_style_text(t.text_frame, TS["sectionTitle"], C["white"], bold=True,
align=PP_ALIGN.CENTER, font=F["headingCJK"], anchor=MSO_ANCHOR.MIDDLE)
os.makedirs("output", exist_ok=True)
prs.save("output/presentation.pptx")
print("saved: output/presentation.pptx")
tools/brandkit.pyに同じロジックのBrandクラス(add_cover/add_section/add_content/add_endingと各視覚パーツ)がある。スクリプトから使うとワークフローが短くなる。
Step 4: 図形・ビジュアルの追加
コンテンツスライドは テキストだけにしない。数値は KPI カード、進捗はバー、手順はステップフローで表現する。
色は theme.json(C[...])から取り、パレット外の色を直接書かない。
重要なルール:
- テキストフレームには必ずマージンを設定する(はみ出し防止)
- 図形内テキストの中央揃えは
word_wrap=True+alignment=CENTER+vertical_anchor=MIDDLEを 3 点セットで - 要素の重なり防止: 前要素の bottom (y+h) に gap を足して次の y を決める。座標をハードコードで並べない
- スライドの安全領域に収める(既定テンプレートは 16:9 = 13.333"×7.5"。左右マージンは
style.pageMarginIn) - 角丸は
theme.jsonのstyle.cornerRadiusInに合わせる: ROUNDED_RECTANGLE はshape.adjustments[0] = min(0.5, int(Inches(style["cornerRadiusIn"])) / min(w, h))(tools/brandkit.pyのround_corners()参照) - バランス(中央揃え)を必ず取る: カードやステップを横に並べるときは コンテンツ幅いっぱいに等幅で配置し、左右の余白を均等にする(左に寄せない。
brandkit.pyのadd_kpi_row()参照)。矢印などのコネクタは要素と要素の 「間の中央」 に置く。タイトルはプレースホルダー内でvertical_anchor=MIDDLE
def _set_margins(tf, l=Inches(0.1), r=Inches(0.1), t=Inches(0.05), b=Inches(0.05)):
tf.margin_left, tf.margin_right, tf.margin_top, tf.margin_bottom = l, r, t, b
tf.word_wrap = True
def add_kpi_card(slide, x, y, w, h, value, label):
card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h)
card.fill.solid(); card.fill.fore_color.rgb = C["bg"]; card.line.fill.background()
val_h = int(h * 0.55)
b1 = slide.shapes.add_textbox(x, y, w, val_h); tf = b1.text_frame
_set_margins(tf); tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.paragraphs[0].text = str(value)
tf.paragraphs[0].font.size = Pt(TS["kpiValue"]); tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = C["accent"]; tf.paragraphs[0].alignment = PP_ALIGN.CENTER
b2 = slide.shapes.add_textbox(x, y + val_h, w, h - val_h); tf2 = b2.text_frame
_set_margins(tf2); tf2.vertical_anchor = MSO_ANCHOR.MIDDLE
tf2.paragraphs[0].text = label
tf2.paragraphs[0].font.size = Pt(TS["caption"]); tf2.paragraphs[0].font.color.rgb = C["gray"]
tf2.paragraphs[0].alignment = PP_ALIGN.CENTER
def add_progress_bar(slide, x, y, w, h, progress):
"""progress: 0.0 ~ 1.0"""
bg = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h)
bg.fill.solid(); bg.fill.fore_color.rgb = C["bg"]; bg.line.fill.background()
bw = int(w * max(0.0, min(1.0, progress)))
if bw > 0:
bar = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, bw, h)
bar.fill.solid(); bar.fill.fore_color.rgb = C["accent"]; bar.line.fill.background()
# KPI カード・進捗バー・ステップフロー・テーブルの完全版は tools/brandkit.py を参照
Step 5: QA(必須)
QA プロセス。最低 1 回は「生成 → markitdown で抽出 → 修正」を回す。
既存ファイルの読み取り
python3 -m markitdown deck.pptx
レファレンスファイル
| ファイル | 内容 |
|---|---|
| design-system.md | theme.json の設計指針・配色/タイポ/スペーシングの考え方 |
| slide-types.md | スライドタイプとレイアウトパターン(layoutMap ロールとの対応表つき) |
| editing.md | 既存 PPTX の XML 編集ワークフロー |
| pitfalls.md | QA プロセス、よくあるミス |
| pptxgenjs.md | PptxGenJS API リファレンス(テンプレート外のカスタムスライド用) |
依存パッケージ
pip install python-pptx "markitdown[pptx]"
npm install pptxgenjs # テンプレート外のカスタムスライド用(任意)