ruby_pptx: file PowerPoint con Ruby
In Python, quando bisogna generare una presentazione PowerPoint da codice, si ricorre a python-pptx di Steve Canny. È una libreria ben testata, con un modello a oggetti completo per il formato OOXML PresentationML. Volevo la stessa cosa in Ruby, quindi l’ho portata.
ruby_pptx crea, legge e modifica file .pptx. Sotto c’è lo stesso modello a oggetti di python-pptx; sopra, un’API pubblica riprogettata per Ruby: keyword argument, lunghezze esplicite, collezioni che si comportano come array e pattern matching.
Installazione
La gem richiede Ruby 3.3 o successivo:
gem install ruby_pptx
Dipende da rubyzip, Nokogiri e REXML. La versione attuale è la 0.2.0 e il sorgente si trova su GitHub.
Una prima slide
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")
Lunghezze, e 2.cm con una refinement
PowerPoint memorizza quasi ogni distanza in EMU (English Metric Units), un intero in cui un pollice vale 914’400. ruby_pptx non accetta numeri nudi per posizioni e dimensioni: usa oggetti Pptx::Length, che si creano con gli helper del modulo Pptx: Pptx.emu, Pptx.inches, Pptx.cm, Pptx.mm, Pptx.pt e Pptx.centipoints. Una Length contiene l’intero EMU, si riconverte con .emu, .inches, .cm, .mm e .pt e supporta +, - e * numero.
È esplicito, ma Pptx.cm(2) ovunque diventa pesante. Per questo la gem include anche una refinement, Pptx::Lengths. Non viene caricata da require "ruby_pptx": va richiesta a parte e attivata con 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")
La refinement aggiunge emu, inches/inch, cm, mm, pt/point/points e centipoints a Numeric, quindi funzionano sia con interi sia con float, e ognuno restituisce una Pptx::Length. Senza la refinement, le stesse due forme si scrivono così:
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)])
Ho scelto apposta una refinement. using Pptx::Lengths agisce solo sul file in cui compare, quindi Numeric non acquisisce un metodo cm nel resto della vostra applicazione o in altre gem che girano nello stesso processo. Se volete davvero i metodi ovunque, require "ruby_pptx/core_ext" modifica Numeric per l’intero processo. All’interno di una libreria resterei con la refinement.
Costruire un’intera presentazione
Per i report generati di solito voglio descrivere la presentazione dall’inizio alla fine. Pptx.build è un sottile strato dichiarativo sopra la stessa 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")
Leggere presentazioni esistenti
Per rileggere una presentazione si usa case/in. Nei pattern i valori enum compaiono come simboli:
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
Le collezioni si indicizzano come array (slides[-1], slides[1..3]) e leggere una proprietà non modifica mai il documento. In python-pptx alcuni getter, come i titoli degli assi e dei grafici, scrivono nel file. Qui no.
Cosa copre
Ogni classe e membro pubblico di python-pptx 1.0.2 ha un corrispettivo, e una spec lo verifica direttamente contro python-pptx. Questo include testo e font, riempimenti e linee, immagini, video, tabelle con celle unite, connettori, gruppi, segnaposto, oggetti OLE, note del relatore e grafici. Le spec confrontano anche, parte per parte, i pacchetti che le due librerie scrivono per la stessa operazione. Dove ruby_pptx si discosta di proposito, per lo più perché non riproduce i bug di python-pptx, PORTING.md elenca ogni caso.
Ci sono inoltre cinque cose che python-pptx non fa:
- immagini SVG (con un fallback PNG da fornire per i visualizzatori più vecchi)
- sezioni di slide
- suddividere una tabella lunga su tutte le slide necessarie
- grafici combinati con asse secondario
- definire da codice uno schema diapositiva, il suo tema e i suoi layout
Esecuzione in ruby.wasm
La versione 0.2.0 ha aggiunto un backend XML in puro Ruby basato su REXML. Se Nokogiri non si carica, per esempio con ruby.wasm nel browser, la gem passa a REXML senza alcuna configurazione. Il risultato è identico e la CI esegue ogni spec con entrambi i backend. REXML però è più lento: per una presentazione di 60 slide con testo, tabelle e grafici, costruzione e salvataggio hanno richiesto circa 3,3 s contro 0,5 s con Nokogiri. Quale backend sia attivo lo indica Pptx.xml_backend.
Stato e riconoscimenti
ruby_pptx è giovane: la 0.1.0 e la 0.2.0 sono uscite entrambe il 28 settembre 2026. Fino alla 1.0 una versione minore può cambiare l’API pubblica, quindi fissate la versione se ne dipendete.
La gem è rilasciata con licenza MIT. È un port di python-pptx di Steve Canny, anch’esso con licenza MIT, e include alcuni file modello di python-pptx, come la presentazione predefinita e i modelli delle note. Gran parte del merito per il design del modello a oggetti va a quel progetto.
Se la provate e qualcosa non funziona o vi sembra poco «Ruby», mi farebbe piacere saperlo. Codice e issue sono su GitHub, la gem è su RubyGems.