PowerPointテンプレが無いなら、Slidevのデザインから自作する

Hero banner: bold Japanese headline 'テンプレが無いなら コードで作る' with subtle background shapes and SIOS Tech.Lab logo top-right.

ども!SlidevからPowerPointへスライドを渡す道を、ひとつずつ試している龍ちゃんです。

Slidevで作ったデッキをPowerPointに持っていく実験をずっとやっていて、今回はそのうちの1本です。Slidevにもpptxの書き出し機能はあるんですが、出てくるのは各スライドを画像にして貼っただけのファイルで、開いても誰も直せません。以前「Claude CodeでSlidevのスライドを作る話」で「配布用の最終形と割り切る」と書いたのがこれですね。

じゃあ画像じゃない形で渡そうとなるんですが、そのとき流し込む先のPowerPointテンプレが手元にありませんでした。ただ、デザインそのものはSlidev側でもう詰めてあるんですよね。色も文字サイズも帯の太さも、手元で表示を確かめながら決めた値がテーマに書いてあります。今回はそこで、テンプレの方をpython-pptxのコードから作ってみた、という話です。デザインを決めるのはHTMLとCSSの世界でやって、決まったものだけPowerPointへ持ち込む形ですね。

やってみて受け取れたものを先に書いておきます。Slidevのデザインが、PowerPointのテンプレとして受け取れます。 自分のテーマの色もフォントも帯も、PowerPointの「新しいスライド」に並ぶレイアウトとして出てきます。しかもその変換器は、AIに書かせられます。何を渡せば揺れずに出てくるのかまで、この記事に置いておきます。

SlidevからPPTXへ渡す道はこれ一本ではなくて、この記事は僕が並べて検証したうちの一本です。全体像はSlidevからPowerPointへ渡す道を整理した記事にまとめてあるので、自分に合う道から選びたい人はそちらへ。この記事だけで完結するので、そのまま読み進めても大丈夫です。

流し込む先が無いなら、テンプレの方をコードで作る

先にゴールを置きます。Slidevで作り込んだ自分のデザインを、PowerPointで他の人が直せる形のまま残したい。 ここから制約を足していくと、やることが決まっていきます。

まず、Slidevのpptx書き出しをそのまま使う道は最初に外れます。出てくるのが画像なので、編集できる形にならないんですよね。なので運ぶものを出力からデザインの値そのものに切り替えます。色・フォント・帯の寸法といった、デザインを決めている数字の方を持っていく、ということです。さっきの割り切りを取り消したいわけではなくて、渡して見てもらうだけならあのままでいいと思っています。今回は自分のデザインを残したまま後から中身を直したい、という別の要件の話ですね。

次に、その値を流し込む先が無い。会社の空テンプレが出てこなかったので、python-pptxでテンプレごとコードから作ることにしました。ここが今回のアプローチです。会社のPowerPointテンプレをちゃんと持っているなら、そこに中身を流し込む方が楽ですし、出てくる見た目は再現ですらなく本物です。そっちはSlidevの中身を会社のPowerPointテンプレに入れる話に書いてあります。この記事は、そのテンプレが手元に無い側の話ですね。

最後に、編集できる状態のまま、手作業も増やしたくない。ここは欲張らないことにしました。見た目の完全再現と、手作業を最小にすることと、編集できることの3つは同時に取れません。前の2つを取ると画像を貼ったpptxになって編集できなくなる、というのがさっきの記事の結論でした。なので完全再現の方を諦めて、繰り返し出てくる枠が揃っていればいい、というところまで弱めています。

この記事が作る範囲の図。Slidevのテーマに書いてあるデザイン値(色6つ・フォント2種・文字サイズ8階層・帯68px)を、AIに一度だけ書かせた変換器スクリプトに通すと、テーマとレイアウトを持つテンプレのpptxが出てくる。ここまでがこの記事の範囲で一度だけの作業。以降は内容を流すだけでデッキになる

ついでに立ち位置も書いておきます。今回もAIは使っています。ただ書かせているのは変換器の方で、成果物ではありません。デザインの値はもう自分のテーマに書いてあるので、それをテンプレ.pptxに変換するスクリプトをClaude Codeに書いてもらって、あとはそれを実行するだけです。pptxを作る生成AI系のツールはたくさんあって、どれもテンプレも中身も毎回AIに出させる形なんですよね。一度変換器にしてしまえば、AIに毎回お願いするのは変換器にできない部分だけで済みます。

自分のテーマに書いてある値を、テーマとレイアウトに移す

やっていることは、Slidev側にある値をPowerPoint側の入れ物に移し替える作業です。

前提として、デザインの値は目分量で測る必要がありません。自分で作ったテーマなら、色もフォントも寸法もCSSに書いてあるからです。僕の場合は色もサイズもCSS変数で縛る作りにしていて、そのあたりは以前「SlidevのデザインシステムをCSS変数とCLAUDE.mdで作る」に書きました。ただ、どこに何を書いてあるかは人それぞれなので、以下は僕のテーマの場合として読んでください。

移し先の呼び方も先に置いておきます。PowerPointで色とフォントの定義を持っているのが「テーマ」、スライドの型が「レイアウト」(それを束ねているのがスライドマスター)、レイアウトの中でテキストを入れる枠が「プレースホルダ」です。何を吸い出して何を作るかで並べると、こうなります。

吸い出す(Slidev側)出どころ(僕のテーマの場合)作る(PowerPoint側)
色6つ(--navy-900: #0B1F3A ほか)とグラデの停止点theme/styles/index.css:rootテーマの配色
フォント2種(Noto Sans JP / Inter)同上と DESIGN.mdテーマのフォント
使っている文字サイズ(表紙64px・章扉48px・h2 30px・本文19px・kicker〈見出しの上に小さく載せるラベル〉12pxなど)DESIGN.md のタイポスケールレイアウトのプレースホルダの既定書式
帯68px・本文の余白24×40px・kickerのamberチップtheme/layouts/*.vueレイアウトの上に置く図形と余白
表紙・章扉の全面navytheme/components/CoverHero.vuesection.vue表紙レイアウト・章扉レイアウト

見てほしいのは右の列で、色とフォントはテーマ側、見た目の骨格はレイアウト側、と行き先が分かれるところです。真ん中の列はあくまで僕のテーマの置き場所なので、自分のテーマだと違う場所にあると思います。

単位はSlidev側がpx、PowerPoint側がptやEMUなので変換が要るんですが、ここは一律の掛け算で済みます。掛ける数をひとつ決めたら、あとは機械的に置き換わるだけ。元の比率も元の数字もそのまま残るので、ここで悩むことはありませんでした(掛ける数の決め方は付録に置いておきます)。

で、ここが一番おいしいところなんですが、書式はテーマとレイアウトが持っている状態になります。つまりここまでが一度だけの作業で、あとは内容を流すだけでSlidevで使っていたのと同じ見た目が出ます。

実際どこまで揃ったのかは、並べて見てもらった方が早いですね。

作ったテンプレを、元のデザインと並べてみた

まず、そもそもテンプレとして成立しているのか。PowerPointで開いて「新しいスライド」を押した画面がこれです。

WindowsのPowerPointで作ったテンプレを開き「新しいスライド」を展開した画面。RAG STANDARDというグループ名の下に、表紙・本文・章扉の3つのレイアウトがサムネイル付きで並んでいる

RAG STANDARD というグループ名の下に、表紙・本文・章扉が並んでいます。ここに出てくるということは、PowerPointの側からは普通のテンプレとして見えている、ということですね。3つしか並んでいないのは後で書きます。

次はデザインです。確かめたのは、全スライドで繰り返し出てくる枠です。 色とフォント、文字サイズ、帯、表紙と章扉の骨格。ここから先の数字は全部この範囲の中の話になります。

元のSlidevと並べたのがこれです。

比較画像。左列が元のSlidev、中列が1920px基準で作ったテンプレに同じ内容を流したもので文字も帯も全体に小さい、右列が980px基準に取り直したもの。上段が表紙、下段が本文のスライドで、右列は本文が左列とほぼ揃う一方、表紙の大見出しは左列より大きくなっている

左が元のSlidev、真ん中がコードで作ったテンプレに同じ内容を流したもの、右は後で出てくる手直し版です。上が表紙、下が本文のスライドですね。

通ったものから書きます。

  • 色は主要な6つ(navy-900・navy-700・blue-600・amber・背景の白・本文の文字色)がHEXでそのまま一致しました
  • フォント2種(Noto Sans JP と Inter)と、実際に使っている8階層の文字サイズが、比率を保ったまま乗りました
  • 帯の高さは設計値どおりで、スライド高さの6.30%(68px / 1080px)です
  • 内容を変えても手作業は増えませんでした。違う内容3本で試して、毎回コマンド1発。項目を増やして本文があふれそうな3本目でも、あふれは出ていません
  • 本文はテキストのまま残ります。照合は16/16で、画像になった箇所は0でした

確かめ方は3通りです。PPTXをzipとして展開して中のXMLを直接読む、LibreOfficeという別のエンジンでレンダリングして目で見る、そして内容を3本流してみる、の3つですね。

ちなみにこの帯もテーマの色も、python-pptxの高水準なAPIからは触れませんでした。1.0.2で確認したんですが、レイアウトの図形を扱うクラスには図形を足すメソッドがそもそも1つもないんですよね。なのでOOXML(PowerPointのファイルの中身に入っているXMLです)を直接書いて置いています。裏返して言うと、OOXMLまで降りれば届く層がある、ということでもあります。

ただ、並べると見えてしまうものがあります。測った項目としては通っているんですが、目で見ると気になる、という別の観点の話です。

気になるのは真ん中の列です。色も配置も比率も合っているのに、文字も帯も一律で小さい。全体が半分くらいのサイズで乗っています。原因は換算の分母で、僕は設計上の1920pxを取ったんですが、Slidevが実際にレンダリングしているキャンバスは980px幅なんですよね。同じCSS pxが、実物では倍くらいの存在感で出ていたわけです。帯の高さで言うと、元が12.38%に対してこっちは6.30%。ほぼ半分ですね。

対処は分母を実レンダの980pxに取り直すだけです。デッキの中での比率は分母に関係ないので、さっきの判定は動かないまま見た目だけが揃います。それが右の列なんですが、これで全部揃ったわけではありませんでした。帯と本文は左とほぼ同じ大きさになったのに、表紙の大見出しだけは行き過ぎて元の1.5倍くらいになっています。分母は一発の正解じゃなくて出発点で、表紙の大見出しは個別に詰める必要が残りました。

どこまで確かめたかも、ここで1回だけまとめておきます。このファイル、WindowsのPowerPointでも開いて確認しています。修復ダイアログは出ませんでしたし、さっきの画面のとおりテンプレとして機能しますし、本文はテキストとして直せますし、フォントも意図したとおりに出ました。残っている限定はひとつで、デザインを当てたのが繰り返し使う3つのレイアウトだけ、という範囲の話です。ただこれは弱点というより、次の話そのものなんですよね。

テンプレに入るのは繰り返す枠で、1枚モノは入らない

じゃあ自分のデッキ、全部これで出せるのか。ここが今回の一番の話です。

線は1本だけで、繰り返すか、1枚モノかです。

入る側はもう見せていて、さっきの「新しいスライド」に並んでいた表紙・本文・章扉の3つがそれです。python-pptxが最初から持っているレイアウトは11個あるんですが、使うのはこの3つだけなので、残りは消してあります。繰り返し使うものだけ用意すれば足りるので、開いたときに3つしか出てこないのはそのためですね。

1枚モノを入れないのは、無理だからというより過剰だからです。レイアウトは同じ形を何度も使うための入れ物なので、1回しか出てこない図をレイアウトに彫っても、そのレイアウトは一生に一度しか使われません。作る意味がないんですよね。

ただ、これは「1枚モノは大したことない」という話ではないです。むしろ逆で、デッキの色が出るのはこっち側なんですよね。1枚だけ出しておきます。

セミナー資料のスライド1枚。上部に紺色の帯と「② データを繋ぐ」の見出し、その下に丸数字②と大見出しが並び、アイコン付きのカードが2枚横並びに置かれ、一番下にオレンジの注意書きの帯がある

文字は取り出せます。でもこのスライドで意味を持っているのはカードの並びとアイコンの方なので、枠に文字を入れても同じ絵にはなりません。こういった配置が自由にできて、誰でも編集ができるのがPPTXの強みです。 カードの配置そのものはOOXMLを手で書けば形にできますが、このアイコン(Material Iconsのリガチャ)と和文の文節折返し(word-break: auto-phrase)の2つだけは、OOXMLの仕様を当たった限り等価な書き方が無くて、ライブラリを変えても出せません。

そしてコストの形が、この線からそのまま出てきます。繰り返す枠の方は一度書けば終わりで、デッキを何本作っても手作業は増えません。減らないのは1枚モノの方だけで、デッキを作るたびにそこだけが毎回発生します。どれくらい時間がかかるかは今回きちんと計っていないので、重いと数字で言い切ることはしません。

正直に書くと、僕が一番やりたいのはそこなんですよね。今回のやり方で一番色が出るのは、ちょうどOOXMLを手で書かないと届かない側でした。テンプレの方はあっさり出来たのに、一番作り込みたいところにこの道では届かない。そこがつらいところだなと思っています。

なので今回の検証で得たものは2つあると思っていて、ひとつはテンプレが作れたこと、もうひとつは穴の在処がはっきりしたことです。だから別の手が要る、というところまでが今回ですね。

で、自分はこの道に乗る側なのか

判断は2段階でいいと思います。

ひとつめは、そもそも使える会社のPowerPointテンプレを持っているかどうかです。持っているならそれを開いて中身を流し込む方が楽ですし、出てくる見た目は再現ですらなく本物のブランドです。この道が要るのは、テンプレが出てこない場合か、自分のデザインを持っていてそれを残したい場合ですね。

ふたつめは、自分のデッキの見せ場がどちら側にあるかです。枚数の話ではありません。表紙や章扉や本文みたいな繰り返す枠に価値の大半が乗っているなら、一度書いた分がそのまま効いてきます。逆に、毎回作り込む1枚モノで勝負しているデッキだと、テンプレに入らない側が支配的になるので効きません。

実際のところ、たいていのデッキは混ざっていると思います。僕のもそうで、繰り返す枠のあいだに作り込んだ1枚が挟まる形です。その場合はどちらかを選ぶんじゃなくて、繰り返す枠はテンプレに任せて、1枚モノにだけ別の手を用意する、という振り分けになります。

自分のデッキを仕分ける図。表紙・本文・章扉のように繰り返し出てくるスライドと、作り込んだ図のスライドが混ざったデッキを2つに仕分ける。繰り返す枠はテンプレに入れて、一度書けばあとは内容を流すだけ。作り込んだ図の1枚モノは別の手が要り、デッキを作るたびに発生する

その振り分けで進めるなら、けっこう気持ちよく回ります。デザインの値がコードの中にあるのでgitで差分が見えますし、色を変えたければ値を書き換えて作り直すだけです。リブランドがかかっても同じで、テンプレを作り直して内容を流し直すだけになります。実際に手を動かすときの材料(換算の決め方、デザイン値の書き出し方、僕が踏んだ罠)は、記事の最後に付録としてまとめてあります。

1枚モノに届かない話の続きも書いておきます。OOXMLで書ける部分だけAgentに書かせて、会社のテンプレと合体させる、というやり方があります。実際にそこまでやったのがSlidevの中身をAgentに書かせて会社テンプレへ合体させる話で、この連載の到達点です。一番作り込みたいところが残ってしまった人は、そちらへどうぞ。

あと、会社のPowerPointの資料を持っている人には、そこからSlidevへ持ってくるという選択肢もあります(会社のPowerPointの資料をSlidevへ移す話)。

最後に一度だけ書いておくと、繰り返す枠と1枚モノで切る、というのはこの道での結論です。テンプレが手元に無い、という条件から出発した一本なので、そもそも自分の条件だとどこに着くのかを先に見たい人は、SlidevからPowerPointへ渡す道を整理した記事の方に4つ並べてあります。

正直なところ、PowerPointを一度も開かずに、Slidevで作ったものをデザインパターンとして持ち込めるところまでは来ました。ただ今回のやり方だと中身までは作ってくれないので、資料づくりの時間がガツンと減るかというと、たぶんそこまではいかないんですよね。そこがつらいところです。

付録:手元で再現するための情報

ここから下は、同じことを自分の環境でやるための材料です。実際に踏んだ罠もそのまま置いておきます。

環境

Python 3.12 と python-pptx、それにlxmlです。python-pptxは1.0.2で確認しています。元のデッキ側はSlidev 51系です。仮想環境を作らずに試すならuvが楽ですね。

uv run --with python-pptx --with lxml python build_template.py

換算の決め方

スライド幅を960pt、Slidevのキャンバスを1920pxとして、k = 960pt / 1920px = 0.5 を一律で掛けています。EMUで言うと 12192000 / 1920 = 6350 がpxあたりの値です。文字サイズはcentipoint(ptの100倍)で書くので、px × 50 になります。

ここで1920pxは設計上の分母です。Slidevが既定でレンダリングしているのは980px幅なので、投影したときの絶対的な大きさを実物に合わせたいなら、そちらを分母に取り直す運用になります。ただし本文で書いたとおり、取り直しても全部は揃いません(帯と本文は揃いますが、表紙の大見出しは目標の1.5倍くらいに行き過ぎます)。分母は一発の正解ではなく出発点だと思ってください。

デザイン値の出どころ

色はtheme/styles/index.css:root、タイポスケールはDESIGN.md、帯やpaddingはtheme/layouts/*.vueから拾いました。コードを書き始める前に全部書き出して固めておくのがおすすめです。途中で拾いに戻ると値がぶれます。

AIに変換器を書かせるときに渡したもの

このスクリプト、僕が1行ずつ書いたわけではなくてClaude Codeに書かせています。ただ「Slidevのデザインをpptxテンプレにして」だけ投げると、それっぽいけど値が微妙に違うものが出てきて、直すたびに別のところがずれる、みたいなことになります。振り返ると、揺れなくなったのは次の3つを先に用意してからでした。

  1. デザイン値を凍結した1枚。色のHEX、フォント、使っている文字サイズをpxとptの両方で、帯の高さは相対値(0.06296のような)で、許容誤差まで書いてJSONにしておきます。「index.cssを見て」ではなく、値を確定させたものを渡す。ここが曖昧なままだと出力が毎回変わります
  2. 入れ物の対応。どの値をテーマに入れて、どの値をレイアウトに入れるか(本文に出した対応表がそれです)。これを決めずに書かせると、書式がスライド側に直接乗る形になって、下の罠2のとおりテンプレとして使い回せないものが出てきます
  3. 合否を機械で出せる判定コード。作ったpptxをzip展開して、色・フォント・サイズ・幾何を1で凍結した値と突き合わせるスクリプトを先に書いておきます。これがあるとAIが自分で回して自分で直せるので、こちらが目視で差し戻す回数が激減します

あと先に教えておくと詰まらないのが、下の罠1(レイアウトには高水準APIで図形を置けない)です。知らないとadd_shapeがある前提のコードを書いて、動かないので遠回りを始めます。

渡すのはコードではなく、値と、入れ物の対応と、合否の判定ですね。中身のコードはAIが書けます。

テンプレ構築コードの抜粋

帯を1本引く部分と、拾ったHEXをテーマの配色に置く部分だけ出しておきます。

PX = 6350                    # EMU/px(12192000 EMU / 1920px)

def cp(px):                  # px → centipoint(pt×100)。k = 960pt / 1920px = 0.5
    return int(round(px * 50))

NAVY900, NAVY700 = "0B1F3A", "153764"   # index.css の :root から拾った値
AMBER, BLUE600 = "F59E0B", "2563EB"

# 帯:高さ68px(1080pxの6.30%)、navy-900 → navy-700 のグラデーション
band = rect_sp(101, "fixed-band", 0, 0, SLIDE_W, 68 * PX,
               grad_fill_xml([(NAVY900, 0), (NAVY700, 100)], 0))  # CSSの90deg = OOXMLの0
append_to_layout(L_FIXED, band)

# テーマの配色:拾ったHEXをそのまま srgbClr に置く
f'<a:accent1><a:srgbClr val="{BLUE600}"/></a:accent1>'
f'<a:accent3><a:srgbClr val="{AMBER}"/></a:accent3>'

こんな調子で、色・帯・文字サイズを一個ずつ置いていってテンプレ(build_template.py)を組み、あとは内容を流す(fill_template.py)という構成です。

使わないレイアウトは消して、残す方に名前を付ける

python-pptxが既定で持っているレイアウトは11個ありますが、作るのは繰り返し使う3つ(表紙・本文・章扉)で足ります。残りはSlideLayouts.remove()で消して、残した3つに日本語の名前を付けました。こうしておくと、テンプレを開いた人にそのまま「何を繰り返すか」が伝わります。消しても流し込む側のコードは無変更で動きます(インデックスの0/1/2が保たれるため)。

踏んだ罠

  • python-pptxの高水準APIは、レイアウトを組む用にはできていません。 1.0.2で確認したところ、レイアウトの図形を扱うクラスの公開APIは6つで、図形を足すadd_*が1つもありません(スライド側のクラスには9つあります)。なので帯やkickerは<p:sp>のXMLを組んでspTreeにappendしています。テーマの中身も素のデータなので、配色・フォント・名前の3つをXMLで直接書き換えました。名前を忘れると「新しいスライド」のグループ見出しがOffice Themeのまま残ります。画面に出る唯一の既定文字列なので、ここは直しておいた方がいいです。なお、レイアウトの削除と改名は高水準APIにあります。届かないのはレイアウトに図形を置くことと、テーマの中身の2つだけですね
  • 流し込む側では書式を1行も書かないでください。 runに書式を書いた瞬間にレイアウト側のdefRPrの継承が切れて、テンプレを差し替えても効かなくなります。この方式の旨味がそこで消えます
  • レイアウトに図形として置いた文字は、そのレイアウトを使う全スライドで同じものになります。 kickerのRAG STANDARDCHAPTER 01slideLayout2.xmlslideLayout3.xmlにだけあって、どのslideN.xmlにもありません。章扉を2枚作ったデッキを流し込んだら、2枚目もタイトルは「第2章」なのにeyebrowはCHAPTER 01のまま出ました。変えたいならプレースホルダにしてください
  • pandocにpython-pptx製のテンプレを--reference-docで渡すと0スライドになります。 pandocは自前のreference.pptxの構造を前提にしているので、渡すなら純正のreferenceに色を着せ直す前段が要ります

pandocという道もあります

「pandocでいいのでは」と思った人向けに書いておくと、純正のreference.pptxに同じデザインを着せれば、色とフォントと帯までは乗ります。ただし内容の形からレイアウトを自動で選ぶ仕組みなので、表紙と章扉のデザインは落ちます。そういう道もある、という提示までにしておきます。

途中の確認

コンテナの中で回しているので、1枚作るたびにPowerPointへ持っていくのは面倒です。僕はLibreOfficeでPDFにしてから画像にして目で見ていました。コマンドはSlidevの中身を会社のPowerPointテンプレに入れる話の付録と同じなので、そちらを見てください。最後の確認は実機で開く、という使い分けで、今回もそこまでやっています。

ではまた!

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。
エンジニア募集中!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です