Slides
JetBrainsOfficialCreated by OpenAIDistributed by JetBrains
- Skill
- Content generator
- Documents and content
- Apache-2.0
- Low risk
- Has scripts
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
Download the skill as a ZIP:
https://autogineer.com/agent-skills/jetbrains/slides/downloadUnzip 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.pptxor.pdfto 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
Inspect the request and determine whether you are creating a new deck, recreating an existing deck, or editing one.
Set the slide size up front. Default to 16:9 (
LAYOUT_WIDE) unless the source material clearly uses another aspect ratio.Build the deck in JavaScript with PptxGenJS, an explicit theme font, stable spacing, and editable PowerPoint-native elements when practical.
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.Run
slides_test.pyfor overflow checks when slide edges are tight or the deck is dense.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
fitorautoFitfor 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 --jsonFiles
- ATTRIBUTION.mdAdded by AutoGineer875 B
- LICENSE.txt11.1 KiB
- SKILL.md3.7 KiB
- agents/openai.yaml283 B
- assets/slides-small.svg2.4 KiB
- assets/slides.png1.8 KiB
- scripts/create_montage.pyScript8.9 KiB
- scripts/detect_font.pyScript34.0 KiB
- scripts/render_slides.pyScript9.3 KiB
- scripts/slides_test.pyScript6.3 KiB
scripts/create_montage.py
#!/usr/bin/env python3
# Copyright (c) OpenAI. All rights reserved.
import argparse
import re
from math import ceil
from os import listdir
from os.path import basename, expanduser, isfile, join, splitext
from typing import Literal
from PIL import Image, ImageDraw, ImageFont, ImageOps
SUPPORTED_EXTS = {".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".webp"}
def _make_placeholder(w: int, h: int) -> Image.Image:
"""Create a visible placeholder tile with a light gray fill and a red X cross."""
ph = Image.new("RGBA", (w, h), (220, 220, 220, 255))
ph_draw = ImageDraw.Draw(ph)
line_color = (180, 0, 0, 255)
ph_draw.line([(0, 0), (ph.width - 1, ph.height - 1)], fill=line_color, width=3)
ph_draw.line([(ph.width - 1, 0), (0, ph.height - 1)], fill=line_color, width=3)
return ph
def _load_images_with_placeholders(
input_files: list[str], fail_on_image_error: bool = False
) -> tuple[list[str], list[Image.Image | None]]:
labels = [basename(p) for p in input_files]
images: list[Image.Image | None] = []
for p in input_files:
try:
images.append(Image.open(p))
except Exception as e:
if fail_on_image_error:
raise
print(f'Warning: Failed to load image "{p}": {e}')
images.append(None)
return labels, images
def _natural_key(s: str) -> list:
"""Key function for natural sorting (e.g., Slide2 before Slide10)."""
return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", s)]
def create_montage(
input_files: list[str],
output_file: str,
num_col: int,
cell_w: int,
cell_h: int,
gap: int,
label_mode: Literal["number", "filename", "none"],
fail_on_image_error: bool = False,
) -> None:
"""Build a montage with a fixed number of columns.
Each cell has size `cell_w` x `cell_h`. Every input image is resized isotropically to fit inside
the cell. `gap` controls spacing around and between cells (outer margin equals gap).
Label behavior is controlled by `label_mode` which can be one of:
- "none": no labels are drawn
- "number": draw a 1-based index beneath each image
- "filename": draw the filename (no directory) beneath each image
"""
if num_col <= 0:
raise ValueError("num_col must be positive")
if cell_w <= 0 or cell_h <= 0:
raise ValueError("cell_w and cell_h must be positive")
labels, images = _load_images_with_placeholders(
input_files=input_files,
fail_on_image_error=fail_on_image_error,
)
num_images = len(images)
num_valid = sum(1 for im in images if im is not None)
if num_valid == 0:
raise ValueError("No valid images to render.")
if num_valid < num_images:
cell_size = round(min(cell_w, cell_h) * 0.6)
placeholder = _make_placeholder(cell_size, cell_size)
else:
placeholder = None
cols = num_col
rows = ceil(num_images / cols)
temp_canvas = Image.new("RGB", (10, 10), (255, 255, 255))
temp_draw = ImageDraw.Draw(temp_canvas)
# Choose a readable default font size relative to cell height
font: ImageFont.FreeTypeFont | ImageFont.ImageFont
try:
# Attempt to use a common system font for clarity; fallback to default
font_size = max(12, min(36, int(cell_h * 0.12)))
font = ImageFont.truetype("arial.ttf", font_size)
except Exception:
font = ImageFont.load_default()
# Adjust default font effect size estimate
font_size = 12
draw_labels = label_mode != "none"
label_height = 0
if draw_labels:
# Height is approximately constant across strings for a given font
# Use 'Ag' to approximate ascent ('A') and descender ('g') for filename text
sample_text = "1" if label_mode == "number" else "Ag"
lbbox = temp_draw.textbbox((0, 0), sample_text, font=font)
label_height = ceil(lbbox[3] - lbbox[1]) + 6
row_h = cell_h + label_height
canvas_w = cols * cell_w + (cols + 1) * gap
canvas_h = rows * row_h + (rows + 1) * gap
# Light grey canvas background as in typical slide sorter view
canvas = Image.new("RGB", (canvas_w, canvas_h), (242, 242, 242))
draw = ImageDraw.Draw(canvas)
for idx, img in enumerate(images):
col = idx % cols
row = idx // cols
# Top-left corner of the cell including outer margin and gaps
x0 = gap + col * (cell_w + gap)
y0 = gap + row * (row_h + gap)
# Fit the image within the cell while preserving aspect ratio
if label_mode == "number":
label = str(idx + 1)
elif label_mode == "filename":
label = labels[idx]
else:
label = ""
if draw_labels:
bbox = draw.textbbox((0, 0), label, font=font)
text_w = bbox[2] - bbox[0]
else:
text_w = 0
if img:
resized = ImageOps.contain(
img.convert("RGBA"),
(cell_w, cell_h),
method=Image.Resampling.LANCZOS,
)
else:
print(f"Warning: Using placeholder for invalid image at row={row + 1}, col={col + 1}")
assert placeholder is not None
resized = placeholder
paste_x = x0 + (cell_w - resized.width) // 2
paste_y = y0 + (cell_h - resized.height) // 2
canvas.paste(
resized,
(paste_x, paste_y),
mask=resized.split()[3] if resized.mode == "RGBA" else None,
)
border_color = (160, 160, 160)
bw = 1
draw.rectangle(
[
paste_x - bw,
paste_y - bw,
paste_x + resized.width,
paste_y + resized.height,
],
outline=border_color,
width=bw,
)
if draw_labels:
tx = x0 + round((cell_w - text_w) / 2)
ty = y0 + cell_h + 3
draw.text((tx, ty), label, font=font, fill=(0, 0, 0))
canvas.save(output_file)
print(f"Montage saved to {output_file}")
def main() -> None:
parser = argparse.ArgumentParser(
description=(
"Create a montage with a fixed number of columns. "
"Each image is resized isotropically to fit inside a cell of size (cell_width x cell_height)."
)
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--input_files", nargs="+", help="List of input image file paths")
group.add_argument("--input_dir", help="Directory containing input images")
parser.add_argument(
"--output_file",
required=True,
help=(
"Path to save the output montage image. The format is inferred from the file extension."
),
)
parser.add_argument(
"--num_col",
type=int,
default=5,
help="Number of images per row (default: 5)",
)
parser.add_argument(
"--cell_width",
type=int,
default=400,
help="Container width in pixels for each image (default: 400)",
)
parser.add_argument(
"--cell_height",
type=int,
default=225,
help="Container height in pixels for each image (default: 225)",
)
parser.add_argument(
"--gap",
type=int,
default=16,
help="Gap in pixels between images and canvas margins (default: 16)",
)
parser.add_argument(
"--label_mode",
choices=["number", "filename", "none"],
default="number",
help=(
"Label mode: 'number' to draw 1-based indices (default), 'filename' to use the "
"image's filename (no directory), or 'none' for no labels"
),
)
parser.add_argument(
"--fail_on_image_error",
action="store_true",
default=False,
help=(
"If set, fail immediately when any image conversion/loading fails (no placeholders). "
"By default, failures are tolerated and placeholders are used."
),
)
args = parser.parse_args()
output_path = expanduser(args.output_file)
if args.input_files:
input_files = [expanduser(p) for p in args.input_files]
else:
input_dir = expanduser(args.input_dir)
names = sorted(listdir(input_dir), key=_natural_key)
dir_entries = [join(input_dir, f) for f in names]
input_files = [
p for p in dir_entries if isfile(p) and splitext(p)[1].lower() in SUPPORTED_EXTS
]
if not input_files:
raise ValueError(
"No image files with supported extensions were found in the specified directory."
)
create_montage(
input_files=input_files,
output_file=output_path,
num_col=args.num_col,
cell_w=args.cell_width,
cell_h=args.cell_height,
gap=args.gap,
label_mode=args.label_mode,
fail_on_image_error=args.fail_on_image_error,
)
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