Files
dsh-skills/skills/drawio-skill/scripts/drawio2pptx.py

113 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Turn a .drawio into a PowerPoint deck — one slide per page.
Exports each page of a (multi-page) .drawio to a PNG via the draw.io CLI and
lays them into a 16:9 .pptx, one page per slide, scaled to fit with the page
name as the slide title. A C4 model (Context / Container / Component) becomes a
ready-to-present deck; any multi-page diagram becomes a slide sequence.
python3 drawio2pptx.py c4.drawio -o c4.pptx
python3 drawio2pptx.py architecture.drawio # -> architecture.pptx
Needs the draw.io CLI (for the PNG export) and the `python-pptx` package
(`pip install python-pptx`) for writing the deck. Slides are 13.333in × 7.5in
(16:9); each image is centred and scaled to fit inside a small margin.
Usage: python3 drawio2pptx.py <file.drawio> [-o out.pptx] [--scale N]
"""
import argparse
import os
import struct
import subprocess
import sys
import tempfile
import xml.etree.ElementTree as ET
def page_names(path):
"""Names of the <diagram> pages, in order (None where unnamed)."""
try:
root = ET.parse(path).getroot()
except (ET.ParseError, OSError) as exc:
sys.exit(f"error: cannot parse {path}: {exc}")
diagrams = root.findall("diagram")
return [d.get("name") for d in diagrams] if diagrams else [None]
def png_size(path):
"""(width, height) in pixels from a PNG's IHDR header."""
with open(path, "rb") as f:
head = f.read(24)
return struct.unpack(">II", head[16:24])
def export_page(drawio_file, index, out_png, scale):
"""Export one page (1-based index) to PNG via the draw.io CLI."""
r = subprocess.run(["drawio", "-x", "-f", "png", "--page-index", str(index),
"-s", str(scale), "-o", out_png, drawio_file],
capture_output=True)
return r.returncode == 0 and os.path.exists(out_png)
def main():
ap = argparse.ArgumentParser(description="Export a .drawio to a PowerPoint deck (one slide per page).")
ap.add_argument("file")
ap.add_argument("-o", "--output", help="output .pptx (default: alongside input)")
ap.add_argument("--scale", type=float, default=2.0, help="PNG export scale (default 2)")
args = ap.parse_args()
if not os.path.isfile(args.file):
sys.exit(f"error: {args.file} not found")
try:
from pptx import Presentation
from pptx.util import Emu, Pt
except ImportError:
sys.exit("error: python-pptx is required (pip install python-pptx)")
names = page_names(args.file)
out = args.output or os.path.splitext(args.file)[0] + ".pptx"
prs = Presentation()
prs.slide_width = Emu(12192000) # 13.333in — 16:9
prs.slide_height = Emu(6858000) # 7.5in
blank = prs.slide_layouts[6] # the built-in "Blank" layout
sw, sh = int(prs.slide_width), int(prs.slide_height)
margin = Emu(457200) # 0.5in
title_h = Emu(500000)
made = 0
with tempfile.TemporaryDirectory() as tmp:
for i, name in enumerate(names, 1): # draw.io --page-index is 1-based
png = os.path.join(tmp, f"page{i}.png")
if not export_page(args.file, i, png, args.scale):
sys.stderr.write(f"warning: page {i} export failed — skipped\n")
continue
slide = prs.slides.add_slide(blank)
top_pad = margin
if name:
box = slide.shapes.add_textbox(margin, Emu(180000),
Emu(sw - 2 * int(margin)), title_h)
tf = box.text_frame
tf.text = name
tf.paragraphs[0].runs[0].font.size = Pt(20)
tf.paragraphs[0].runs[0].font.bold = True
top_pad = Emu(180000) + title_h
cw, ch = sw - 2 * int(margin), sh - int(top_pad) - int(margin)
pw, ph = png_size(png)
scale = min(cw / pw, ch / ph) # fit, preserve aspect
iw, ih = int(pw * scale), int(ph * scale)
left = Emu(int((sw - iw) / 2))
top = Emu(int(top_pad) + int((ch - ih) / 2))
slide.shapes.add_picture(png, left, top, width=Emu(iw), height=Emu(ih))
made += 1
if not made:
sys.exit("error: no pages exported (is the draw.io CLI installed?)")
prs.save(out)
sys.stderr.write(f"wrote {out} ({made} slide{'s' if made != 1 else ''})\n")
if __name__ == "__main__":
main()