Generated banner for Slides
Generated logo for Slides

Slides

JetBrainsOfficialCreated by OpenAIDistributed by JetBrains

  • Skill
  • Content generator
  • Documents and content
  • Apache-2.0
  • Low risk
  • Has scripts

From the author

Create and edit presentation slide decks (`.pptx`) with PptxGenJS and render/validation utilities. Use when tasks involve building a new PowerPoint deck, recreating slides from screenshots/PDFs/reference decks, modifying slide content while preserving editable output, adding charts/diagrams/visuals, or diagnosing layout issues such as overflow, overlaps, and font substitution.
— the description by OpenAI

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to creating and modifying PowerPoint slide decks using PptxGenJS. It emphasizes maintaining editable output by using native PowerPoint elements and provides a suite of utility scripts to render, validate, and audit slide decks for layout issues, font consistency, and content overflow.

What it does

  • Generates new PowerPoint presentations using JavaScript-based authoring.
  • Recreates existing slide decks from reference materials like PDFs or screenshots.
  • Renders slide decks to PNG images for visual inspection.
  • Validates slide content for layout overflows and out-of-bounds elements.
  • Detects font substitution issues to ensure consistent typography.
  • Creates contact-sheet style montages for quick deck reviews.

When to use it

  • When you need to build a new presentation deck programmatically.
  • When you must recreate or update an existing slide deck while keeping it editable.
  • When you need to verify that slide content fits correctly within the canvas.
  • When you need to audit a deck for missing or substituted fonts.

Advantages

  • Produces editable PowerPoint files rather than static images.
  • Includes automated tools to detect layout and font issues before final delivery.
  • Supports the use of native PowerPoint charts for future editability.
  • Provides a clear workflow for rendering and validating complex slide layouts.

Usage examples

“Create a 16:9 presentation deck about project milestones using PptxGenJS.”
The agent generates a .js file to build the deck and a corresponding .pptx file, ensuring all text and charts remain editable.
“Check this presentation for layout overflows and font issues.”
The agent runs the validation scripts to identify any content extending beyond the slide boundaries and reports any font substitution errors.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/jetbrains/slides/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip slides.zip -d .agents/skills

Documentation by OpenAI

Slides

Overview

Use PptxGenJS for slide authoring. Do not use python-pptx for deck generation unless the task is inspection-only; keep editable output in JavaScript and deliver both the .pptx and the source .js.

Keep work in a task-local directory. Only copy final artifacts to the requested destination after rendering and validation pass.

Bundled Resources

  • scripts/render_slides.py: Rasterize a .pptx or .pdf to per-slide PNGs.

  • scripts/slides_test.py: Detect content that overflows the slide canvas.

  • scripts/create_montage.py: Build a contact-sheet style montage of rendered slides.

  • scripts/detect_font.py: Report missing or substituted fonts as LibreOffice resolves them.

Workflow

  1. Inspect the request and determine whether you are creating a new deck, recreating an existing deck, or editing one.

  2. Set the slide size up front. Default to 16:9 (LAYOUT_WIDE) unless the source material clearly uses another aspect ratio.

  3. Build the deck in JavaScript with PptxGenJS, an explicit theme font, stable spacing, and editable PowerPoint-native elements when practical.

  4. Run the bundled scripts from this skill directory or copy the needed ones into the task workspace. Render the result with render_slides.py, review the PNGs, and fix layout issues before delivery.

  5. Run slides_test.py for overflow checks when slide edges are tight or the deck is dense.

  6. Deliver the .pptx, the authoring .js, and any generated assets that are required to rebuild the deck.

Authoring Rules

  • Set theme fonts explicitly. Do not rely on PowerPoint defaults if typography matters.

  • Size text boxes deliberately and verify by rendering; do not rely on PptxGenJS fit or autoFit for final layout.

  • Use bullet options, not literal • characters.

  • Use explicit crop/contain calculations when placing images so aspect ratio and framing are intentional.

  • Prefer native PowerPoint charts for simple bar/line/pie/histogram style visuals so reviewers can edit them later.

  • For charts or diagrams that PptxGenJS cannot express well, render SVG externally and place the SVG in the slide.

  • Fix all unintentional overlap and out-of-bounds issues before delivering. If an overlap is intentional, leave a short code comment near the relevant element.

Recreate Or Edit Existing Slides

  • Render the source deck or reference PDF first so you can compare slide geometry visually.

  • Match the original aspect ratio before rebuilding layout.

  • Preserve editability where possible: text should stay text, and simple charts should stay native charts.

Validation Commands

Examples below assume you copied the needed scripts into the working directory. If not, invoke the same script paths relative to this skill folder.

# Render slides to PNGs for review
python3 scripts/render_slides.py deck.pptx --output_dir rendered

# Build a montage for quick scanning
python3 scripts/create_montage.py --input_dir rendered --output_file montage.png

# Check for overflow beyond the original slide canvas
python3 scripts/slides_test.py deck.pptx

# Detect missing or substituted fonts
python3 scripts/detect_font.py deck.pptx --json

Files

scripts/render_slides.py

#!/usr/bin/env python3
# Copyright (c) OpenAI. All rights reserved.
import argparse
import os
import re
import subprocess
import tempfile
import xml.etree.ElementTree as ET
from os import makedirs, replace
from os.path import abspath, basename, exists, expanduser, join, splitext
from typing import Sequence, cast
from zipfile import ZipFile

from pdf2image import convert_from_path, pdfinfo_from_path

EMU_PER_INCH: int = 914_400


def calc_dpi_via_ooxml(input_path: str, max_w_px: int, max_h_px: int) -> int:
    """Calculate DPI from OOXML `ppt/presentation.xml` slide size (cx/cy in EMUs)."""
    with ZipFile(input_path, "r") as zf:
        xml = zf.read("ppt/presentation.xml")
    root = ET.fromstring(xml)
    ns = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main"}
    sld_sz = root.find("p:sldSz", ns)
    if sld_sz is None:
        raise RuntimeError("Slide size not found in presentation.xml")
    cx = int(sld_sz.get("cx") or 0)
    cy = int(sld_sz.get("cy") or 0)
    if cx <= 0 or cy <= 0:
        raise RuntimeError("Invalid slide size values in presentation.xml")
    width_in = cx / EMU_PER_INCH
    height_in = cy / EMU_PER_INCH
    return round(min(max_w_px / width_in, max_h_px / height_in))


def calc_dpi_via_pdf(input_path: str, max_w_px: int, max_h_px: int) -> int:
    """Compute DPI from PDF page size.

    For non-PDF inputs, first convert to PDF via LibreOffice to read page size.
    For PDFs, use the PDF directly (avoids unnecessary conversion and failures).
    """
    is_pdf = input_path.lower().endswith(".pdf")
    with tempfile.TemporaryDirectory(prefix="soffice_profile_") as user_profile:
        with tempfile.TemporaryDirectory(prefix="soffice_convert_") as convert_tmp_dir:
            stem = splitext(basename(input_path))[0]
            pdf_path = (
                input_path
                if is_pdf
                else convert_to_pdf(input_path, user_profile, convert_tmp_dir, stem)
            )
            if not (pdf_path and exists(pdf_path)):
                raise RuntimeError("Failed to produce/read PDF for DPI computation.")

            info = pdfinfo_from_path(pdf_path)
            size_val = info.get("Page size")
            if not size_val:
                for k, v in info.items():
                    if isinstance(v, str) and "size" in k.lower() and "pts" in v:
                        size_val = v
                        break
            if not isinstance(size_val, str):
                raise RuntimeError("Failed to read PDF page size for DPI computation.")

            def _parse_page_size_to_pts(s: str) -> tuple[float, float]:
                # Common formats from poppler/pdfinfo:
                # - "612 x 792 pts (letter)"
                # - "595.276 x 841.89 pts (A4)"
                # - sometimes inches: "8.5 x 11 in"
                m_pts = re.search(
                    r"([0-9]+(?:\.[0-9]+)?)\s*x\s*([0-9]+(?:\.[0-9]+)?)\s*pts\b",
                    s,
                )
                if m_pts:
                    return float(m_pts.group(1)), float(m_pts.group(2))
                m_in = re.search(
                    r"([0-9]+(?:\.[0-9]+)?)\s*x\s*([0-9]+(?:\.[0-9]+)?)\s*in\b",
                    s,
                )
                if m_in:
                    w_in = float(m_in.group(1))
                    h_in = float(m_in.group(2))
                    return w_in * 72.0, h_in * 72.0
                # Sometimes poppler returns without an explicit unit; treat as points.
                m = re.search(r"([0-9]+(?:\.[0-9]+)?)\s*x\s*([0-9]+(?:\.[0-9]+)?)\b", s)
                if m:
                    return float(m.group(1)), float(m.group(2))
                raise RuntimeError(f"Unrecognized PDF page size format: {s!r}")

            width_pts, height_pts = _parse_page_size_to_pts(size_val)
            width_in = width_pts / 72.0
            height_in = height_pts / 72.0
            if width_in <= 0 or height_in <= 0:
                raise RuntimeError("Invalid PDF page size values.")
            return round(min(max_w_px / width_in, max_h_px / height_in))


def run_cmd_no_check(cmd: list[str]) -> None:
    subprocess.run(
        cmd,
        check=False,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        env=os.environ.copy(),
    )


def convert_to_pdf(
    pptx_path: str,
    user_profile: str,
    convert_tmp_dir: str,
    stem: str,
) -> str:
    # Try direct PPTX -> PDF
    cmd_pdf = [
        "soffice",
        "-env:UserInstallation=file://" + user_profile,
        "--invisible",
        "--headless",
        "--norestore",
        "--convert-to",
        "pdf",
        "--outdir",
        convert_tmp_dir,
        pptx_path,
    ]
    run_cmd_no_check(cmd_pdf)

    pdf_path = join(convert_tmp_dir, f"{stem}.pdf")
    if exists(pdf_path):
        return pdf_path

    # Fallback: PPTX -> ODP, then ODP -> PDF
    # Rationale: Saving as ODP normalizes PPTX-specific constructs via the ODF serializer,
    # which often bypasses Impress PDF export issues on problematic decks.
    cmd_odp = [
        "soffice",
        "-env:UserInstallation=file://" + user_profile,
        "--invisible",
        "--headless",
        "--norestore",
        "--convert-to",
        "odp",
        "--outdir",
        convert_tmp_dir,
        pptx_path,
    ]
    run_cmd_no_check(cmd_odp)

    odp_path = join(convert_tmp_dir, f"{stem}.odp")

    if exists(odp_path):
        # ODP -> PDF
        cmd_odp_pdf = [
            "soffice",
            "-env:UserInstallation=file://" + user_profile,
            "--invisible",
            "--headless",
            "--norestore",
            "--convert-to",
            "pdf",
            "--outdir",
            convert_tmp_dir,
            odp_path,
        ]
        run_cmd_no_check(cmd_odp_pdf)
        if exists(pdf_path):
            return pdf_path

    return ""


def rasterize(
    input_path: str,
    out_dir: str,
    dpi: int,
) -> Sequence[str]:
    """Rasterise PPTX/PDF to PNG files placed in out_dir and return the image paths."""
    makedirs(out_dir, exist_ok=True)
    input_path = abspath(input_path)
    stem = splitext(basename(input_path))[0]

    # Use a unique user profile to avoid LibreOffice profile lock when running concurrently
    with tempfile.TemporaryDirectory(prefix="soffice_profile_") as user_profile:
        # Write conversion outputs into a temp directory to avoid any IO oddities
        with tempfile.TemporaryDirectory(prefix="soffice_convert_") as convert_tmp_dir:
            is_pdf = input_path.lower().endswith(".pdf")
            pdf_path = (
                input_path
                if is_pdf
                else convert_to_pdf(input_path, user_profile, convert_tmp_dir, stem)
            )

            if not pdf_path or not exists(pdf_path):
                raise RuntimeError(
                    "Failed to produce PDF for rasterization (direct and ODP fallback)."
                )

            # Perform rasterization while the temp PDF still exists
            paths_raw = cast(
                list[str],
                convert_from_path(
                    pdf_path,
                    dpi=dpi,
                    fmt="png",
                    thread_count=8,
                    output_folder=out_dir,
                    paths_only=True,
                    output_file="slide",
                ),
            )
    # Rename convert_from_path's output format f'slide{thread_id:04d}-{page_num:02d}.png'
    slides = []
    for src_path in paths_raw:
        base = splitext(basename(src_path))[0]
        slide_num_str = base.split("-")[-1]
        slide_num = int(slide_num_str)
        dst_path = join(out_dir, f"slide-{slide_num}.png")
        replace(src_path, dst_path)
        slides.append((slide_num, dst_path))
    slides.sort(key=lambda t: t[0])
    final_paths = [path for _, path in slides]
    return final_paths


def main() -> None:
    parser = argparse.ArgumentParser(description="Render slides to images.")
    parser.add_argument(
        "input_path",
        type=str,
        help="Path to the input PowerPoint or PDF file.",
    )
    parser.add_argument(
        "--output_dir",
        type=str,
        default=None,
        help=(
            "Output directory for the rendered images. "
            "Defaults to a folder next to the input named after the input file (without extension)."
        ),
    )
    parser.add_argument(
        "--width",
        type=int,
        default=1600,
        help=(
            "Approximate maximum width in pixels after isotropic scaling (default 1600). "
            "The actual value may exceed slightly."
        ),
    )
    parser.add_argument(
        "--height",
        type=int,
        default=900,
        help=(
            "Approximate maximum height in pixels after isotropic scaling (default 900). "
            "The actual value may exceed slightly."
        ),
    )
    args = parser.parse_args()

    input_path = abspath(expanduser(args.input_path))
    out_dir = abspath(expanduser(args.output_dir)) if args.output_dir else splitext(input_path)[0]
    if input_path.lower().endswith((".pptx", ".ppsx", ".potx", ".pptm", ".ppsm", ".potm")):
        dpi = calc_dpi_via_ooxml(input_path, args.width, args.height)
    else:
        dpi = calc_dpi_via_pdf(input_path, args.width, args.height)
    rasterize(input_path, out_dir, dpi)
    print("Slides rendered to " + out_dir)


if __name__ == "__main__":
    main()

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
OpenAI
Publisher
JetBrains (Official)
Distributed by
JetBrains
Source
JetBrains/skills · slides · commit 3f53782
Licence
Apache License 2.0 (redistribution allowed)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by OpenAI or JetBrains.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download slides.zip