ruby_pptx: RubyでPowerPointファイルを扱う
PythonでPowerPointのスライドをコードから生成したいときは、Steve Cannyさんのpython-pptxを使うのが定番です。よくテストされたライブラリで、OOXMLのPresentationML形式をオブジェクトモデルとして一通り表現しています。同じものをRubyでも使いたかったので、移植することにしました。
ruby_pptxは.pptxファイルの作成・読み込み・更新ができるgemです。内部のオブジェクトモデルはpython-pptxと同じものですが、公開APIはRuby向けに設計し直しています。キーワード引数、明示的な長さの指定、配列のように扱えるコレクション、そしてパターンマッチに対応しています。
インストール
Ruby 3.3以降が必要です。
gem install ruby_pptx
依存しているのはrubyzip、Nokogiri、REXMLです。現在のバージョンは0.2.0で、ソースコードはGitHubで公開しています。
最初のスライド
require "ruby_pptx"
prs = Pptx::Presentation.new_default # or .open("deck.pptx")
slide = prs.slides.add(prs.slide_layouts["Title and Content"])
slide.shapes.title.text = "Quarterly Review"
body = slide.placeholders[1].text_frame
body.text = "Revenue up 12%\nCosts flat"
body.paragraphs.first.runs.first.font.tap do |font|
font.bold = true
font.size = Pptx.pt(24)
font.color.rgb = Pptx::RGBColor["C0504D"]
end
box = slide.shapes.add_shape(:rounded_rectangle,
at: [Pptx.inches(1), Pptx.inches(5)],
size: [Pptx.inches(3), Pptx.inches(1)])
box.fill.solid
box.fill.fore_color.rgb = Pptx::RGBColor["1F497D"]
box.text_frame.text = "Next steps"
slide.notes = "Mention the Q3 dip."
prs.save("out.pptx")
長さの指定と、refinementによる2.cm
PowerPointはほとんどの距離をEMU(English Metric Units)という整数で保存しています。1インチは914,400 EMUです。ruby_pptxでは位置やサイズにただの数値を渡すのではなく、Pptx::Lengthオブジェクトを使います。作成にはPptxモジュールのヘルパー、Pptx.emu、Pptx.inches、Pptx.cm、Pptx.mm、Pptx.pt、Pptx.centipointsを使います。LengthはEMUの整数値を保持していて、.emu、.inches、.cm、.mm、.ptで各単位に変換でき、+、-、* 数値で計算もできます。
明示的でわかりやすい反面、あちこちにPptx.cm(2)と書くと読みにくくなります。そこでこのgemにはPptx::Lengthsというrefinementも用意しています。require "ruby_pptx"では読み込まれないので、別途requireしてからusingで有効にします。
require "ruby_pptx"
require "ruby_pptx/refinements"
using Pptx::Lengths
prs = Pptx::Presentation.new_default
slide = prs.slides.add(prs.slide_layouts["Blank"])
box = slide.shapes.add_textbox(at: [2.cm, 2.cm], size: [10.cm, 1.5.cm])
box.text_frame.text = "Placed in centimetres"
slide.shapes.add_shape(:rectangle, at: [2.cm, 4.cm], size: [3.inch, 1.inch])
2.cm.emu #=> 720000
1.inch == 72.points #=> true
prs.save("out.pptx")
このrefinementはNumericにemu、inches/inch、cm、mm、pt/point/points、centipointsを追加します。そのため整数でも浮動小数点数でも使え、どのメソッドもPptx::Lengthを返します。refinementを使わない場合、同じ2つの図形は次のように書きます。
slide.shapes.add_textbox(at: [Pptx.cm(2), Pptx.cm(2)], size: [Pptx.cm(10), Pptx.cm(1.5)])
slide.shapes.add_shape(:rectangle, at: [Pptx.cm(2), Pptx.cm(4)],
size: [Pptx.inches(3), Pptx.inches(1)])
refinementにしたのは意図的です。using Pptx::Lengthsの効果は、それを書いたファイルの中だけに限られます。アプリケーションのほかの部分や、同じプロセスで動くほかのgemのNumericにcmメソッドが増えることはありません。どうしてもすべての場所で使いたい場合は、require "ruby_pptx/core_ext"でプロセス全体のNumericを拡張することもできます。ただ、ライブラリの中ではrefinementを使うことをおすすめします。
プレゼンテーション全体を組み立てる
レポートを自動生成するときは、スライドを上から順に記述したいことが多いです。Pptx.buildは同じAPIの上にかぶせた薄い宣言的なレイヤーです。
deck = Pptx.build do |d|
d.slide_size = :widescreen
d.slide("Title Slide") do |s|
s.title = "Annual Report"
s.subtitle = "Prepared in Ruby"
end
d.section("Detail") do
d.slide("Blank") do |s|
s.shape :rounded_rectangle, at: [Pptx.inches(1), Pptx.inches(1)],
size: [Pptx.inches(3), Pptx.inches(1)],
fill: "1F497D", text: "Next steps"
s.chart :column_clustered, categories: %w[East West],
series: { "Q1" => [1, 2] },
at: [Pptx.inches(1), Pptx.inches(3)],
size: [Pptx.inches(6), Pptx.inches(4)]
end
end
end
deck.save("out.pptx")
既存のファイルを読む
読み込んだスライドはcase/inで扱えます。enumの値はパターンの中ではシンボルとして現れます。
slide.shapes.each do |shape|
case shape
in {shape_type: :PICTURE, name:} then puts "picture #{name}"
in {shape_type: :PLACEHOLDER, placeholder_format: {type: :TITLE}}
then puts shape.text_frame.text
in {width:} if width > Pptx.inches(5) then puts "#{shape.name} is wide"
else next
end
end
コレクションは配列と同じようにインデックスで参照できます(slides[-1]、slides[1..3]など)。また、プロパティを読むだけでドキュメントが変わることはありません。python-pptxでは、軸やグラフのタイトルなど一部のgetterがファイルに書き込みますが、ruby_pptxではそうなりません。
対応範囲
python-pptx 1.0.2のすべての公開クラスとメンバーに対応するものがあり、specがpython-pptx本体と照らし合わせて確認しています。テキストとフォント、塗りつぶしと線、画像、動画、セル結合ができる表、コネクタ、グループ、プレースホルダー、OLEオブジェクト、発表者ノート、グラフなどが含まれます。さらにspecでは、同じ操作に対して両方のライブラリが書き出すパッケージをパーツごとに比較しています。ruby_pptxが意図的に異なる動作をする箇所は、ほとんどがpython-pptxのバグを再現しないためのもので、PORTING.mdに一つずつ記載しています。
python-pptxにはない機能も5つあります。
- SVG画像(古いビューア向けのPNGのフォールバックは自分で用意します)
- スライドのセクション
- 長い表を必要な枚数のスライドに分割する機能
- 第2軸付きの複合グラフ
- スライドマスターとそのテーマ、レイアウトをコードで定義する機能
ruby.wasmでの動作
バージョン0.2.0では、REXMLをベースにしたピュアRubyのXMLバックエンドを追加しました。ブラウザ上のruby.wasmなど、Nokogiriを読み込めない環境では、設定なしで自動的にREXMLに切り替わります。出力は同じで、CIではすべてのspecを両方のバックエンドで実行しています。ただしREXMLは遅く、テキスト・表・グラフを含む60枚のスライドの作成と保存に、Nokogiriの約0.5秒に対して約3.3秒かかりました。どちらが使われているかはPptx.xml_backendで確認できます。
現状とクレジット
ruby_pptxはまだ新しいgemで、0.1.0と0.2.0はどちらも2026年9月28日にリリースしました。1.0になるまではマイナーバージョンで公開APIが変わる可能性があるので、使う場合はバージョンを固定しておくことをおすすめします。
ライセンスはMITです。Steve Cannyさんのpython-pptx(こちらもMITライセンス)の移植で、デフォルトのプレゼンテーションやノートのテンプレートなど、python-pptxのテンプレートファイルをいくつか同梱しています。オブジェクトモデルの設計の功績の大部分はpython-pptxにあります。
試してみて動かないところやRubyらしくないと感じるところがあれば、ぜひ教えてください。コードとissueはGitHubに、gemはRubyGemsにあります。