立体物をthree.jsで作成し、あらゆる角度から確認できるビューアーに表示します。OBJ + MTLやGLBで保存し、他の3D制作ソフトへ持ち込めます。
- 立体物を作って全方向から確認したいとき
- Blenderで編集するモデルの作成
- シルエットや素材の見え方の確認
PowerPoint資料の新規作成や既存スライドの編集、内容抽出、PDF・画像への書き出しを支援し、会議や授業で使える構成と見た目に整えます。
Use this skill any time a presentation file is the primary input or output. Covers: creating new .pptx decks from scratch or from an outline/document (pptxgenjs / python-pptx); editing and restyling an existing .pptx; reading and extracting text/structure from a deck (markitdown); and rendering a deck to PDF (LibreOffice) or to per-slide PNG images (pdftoppm) for previews, thumbnails, or visual review. Trigger when the user references a .pptx file by name or path, says 'make/build a deck, slides, presentation, PPT, 幻灯片, 演示文稿, 汇报/路演材料', or asks to convert, export, render, or preview slides as PDF or images (e.g. 'PPT转PDF', 'ppt导出图片', 'turn these slides into a PDF').
実行スコア 未検証
インストール方法を見るPowerPoint(.pptx)を中心に、アウトラインや文書からの新規スライド作成、既存資料の編集・デザイン変更、スライド内の文章や構成の読み取り、PDFやページごとのPNG画像への書き出しを支援します。資料では、各スライドに明確な主張を一つ置き、図表や画像を使って内容を伝える構成、色や文字の読みやすさ、余白や情報量の調整などを扱います。作成・編集後は、内容の抜けや文字のはみ出しを確認し、必要に応じてレンダリングした画像で見た目を確かめる手順も示します。既存ファイルは原則として元の場所に残し、更新版を別ファイルとして作成します。
会議、授業、顧客向け提案などのスライドを、メモや文書をもとに形にしたいときに向いています。既存デッキの見た目を整えたい場合や、内容を取り出して再利用したい場合、PDFや画像で共有・確認したい場合にも使えます。コードを使った生成方法としてpptxgenjsやpython-pptxも資料に含まれています。
.pptxの内容を抽出して、PDFとスライド画像にしてください」資料に記載されたPDF書き出しにはLibreOffice、スライド画像化にはpdftoppmを使います。新規作成や編集の方法にはpptxgenjs、python-pptx、内容抽出にはmarkitdownが挙げられています。ライセンスはProprietaryで、詳細はLICENSE.txtに記載されています。
この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。
SKILL.md の記述から、注意が要る操作と導入の前提を AI が確認しました。点数は実際に動かした結果(実行検証)だけで付けています。
点数には入れていません。 2026年10月24日 時点で AI(OpenAI Codex)が判定しました。目安なので、入れる前に原文も確認してください。
インストールする前に、エージェントに与えられる指示の中身を確認できます。
In one sentence: don't make boring slides. Bullet points on a white background are forgettable.
But "not boring" is not "flashy." The goal is a deck that looks ready to use in real life — the kind you could drop into a real meeting, class, or client pitch without editing.
Substance comes before styling: every slide must carry one specific, concrete claim — real numbers, names and mechanisms, and the "so what" behind them — not a topic label or generic filler. Depth is not density: it comes from picking the sharpest fact and cutting the rest, never from more text.
Original file preserved: user-provided decks are read-only by default. Deliverables go to new files
(<stem>_updated.pptx next to the input); edit an original in place only when the user explicitly
asks — and then copy it to <stem>_backup.pptx next to it (never /tmp) first. See "Editing an
existing PowerPoint presentation" in Part 2.
① Pick a content-informed color palette
The palette should feel designed for this topic. A good test: if you could drop your colors into a completely unrelated deck and it would still "work," your choices aren't specific enough. Derive the hues from the subject itself — its place, industry, brand, or mood — rather than reaching for a default.
② Dominance, not equality
One color should dominate 60–70% of the visual weight, supported by 1–2 secondary tones and one sharp accent. Never give all colors equal weight.
③ Dark/light contrast + a visual motif
Build the palette on the BACKGROUND → PRIMARY → ACCENT model, and reuse those exact same three roles on every single slide:
BG, PRIMARY, ACCENT, plus TEXT and MUTED) and reference only those constants — never hand-pick ad-hoc colors slide by slide.Every slide needs a visual element — image, chart, diagram, or shape. Text-only slides are forgettable.
Every slide needs ONE clear visual focal point — an oversized number, a chart, an image, a single bold statement — that dominates the composition. Everything else is subordinate to it.
Prefer container-free grouping — use it on the majority of slides. Grouping comes from alignment, spacing and weight, not from drawing a box around things:
Pick layouts that serve each slide's purpose:
Data display: (point sizes here and throughout assume the 13.33 × 7.5" canvas — see §6)
Density & rhythm:
addChart / add_chart with CategoryChartData, or composed shapes + connectors + text boxes) so they inherit the palette and cost zero image calls.sizing: { type: 'cover' } or an explicit crop), never distort it by setting a w/h that breaks the ratio. This matters most on covers and full-bleed backgrounds, where a stretched photo is the most visible defect on the deck.run.font.name in python-pptx, fontFace in pptxgenjs) — do not rely on inherited theme fonts.Preview caveat: the font names you write into the
.pptxare rendered by the user's PowerPoint, not by your build environment. If you preview via LibreOffice, it substitutes any font it doesn't have — and substitutes with different character widths make the preview's "overflow / fits" verdict disagree with the real deck. Prefer faces that both ship with Office and render true-to-width locally; where you can't, leave ~10% slack instead of trusting the preview.
margin to 0 (or offset the shape to compensate)FFFFFF or your brand color; avoid warm-neutral defaults like F5F5DC, FAF0E6, FAEBD7, FFF8E1Content QA: check for missing content, typos, wrong order; when using a template, grep for leftover placeholders (xxx, lorem, TODO, [insert, etc.).
Code QA: run a short python-pptx script over the finished deck to flag text overflow (estimated text height/width vs. the shape box, plus boxes outside the slide) and overlap (bounding-box intersection between two text-bearing shapes), then fix the real hits and re-run.
Visual QA once only: render the deck to images and use the visual-judge subagent (if not present, check it yourself) when necessary. Do not call an external VLM to check slides for visual QA.
Output QA (before handing the deck over): the user's original input file is untouched at its original path (unless the user explicitly asked for in-place editing); any backup you created stays next to it — these are NOT temp/retry artifacts. The deliverable is a new file (<stem>_updated.pptx), and intermediate renders (the PDF and per-slide PNGs from the commands below) are cleaned up.
PPTX → images is two steps — pdftoppm reads PDF, not .pptx, so LibreOffice must convert first:
# Step 1: PPTX → PDF (LibreOffice; also the command for any "export deck as PDF" request)
soffice --headless --convert-to pdf --outdir <out_dir> deck.pptx
# Step 2: PDF → per-slide PNGs
pdftoppm -png -r 150 <out_dir>/deck.pdf <out_dir>/slide
# Generates slide-1.png, slide-2.png, ...
If
sofficeis only available under a full path (macOS:
/Applications/LibreOffice.app/Contents/MacOS/soffice), call it there. Only one LibreOffice
instance may run at a time — if a conversion hangs, add
--env:UserInstallation=file:///tmp/libreoffice_tmp.🔴
sofficenot installed? You MUST install it, not substitute it — using PowerPoint/Keynote/WPS
to render instead is forbidden, and a large download is not a reason to skip. Once installed,
register it onPATHand re-verifysoffice --versionso the next probe doesn't reinstall it
(step 4 of the hard requirement). See
HARD REQUIREMENT below.
pptxgenjs generates .pptx files in JavaScript / Node.js. Coordinates are in inches.
npm install -g pptxgenjs
const pptxgen = require("pptxgenjs");
let pres = new pptxgen();
pres.layout = 'LAYOUT_WIDE'; // 13.33 × 7.5" — the default for this skill (see below)
pres.author = 'Your Name';
pres.title = 'Presentation Title';
// Canvas constants — derive every position from these, never hardcode 10 or 13.33
const W = 13.33, H = 7.5, M = 0.5; // width, height, margin (true width is 13.333"; 13.33 keeps you inside it)
let slide = pres.addSlide();
slide.addText("Hello World!", { x: M, y: M, w: W - 2 * M, fontSize: 36, color: "363636" });
pres.writeFile({ fileName: "Presentation.pptx" }).then(() => console.log("done"));
| Layout | Size (inches) |
|---|---|
LAYOUT_WIDE | 13.33 × 7.5 — use this |
LAYOUT_16x9 | 10 × 5.625 (pptxgenjs default) |
LAYOUT_16x10 | 10 × 6.25 |
LAYOUT_4x3 | 10 × 7.5 |
⚠️ Set
LAYOUT_WIDEexplicitly on every deck. pptxgenjs defaults toLAYOUT_16x9, which is only 10 × 5.625" — but every type size in this skill (§6: 44–72pt titles, ~24pt body; §3: 60–72pt stat callouts) is tuned for a 13.33 × 7.5" canvas. Leaving the default gives you a canvas 25% narrower and 25% shorter with type sized for the big one, which overflows systematically — the exact failure §3, §9 and §10 all tell you to avoid. If you deliberately target a 10"-wide layout, scale every font size and the 0.5" margins down by the same factor (×0.75).
// Basic text
slide.addText("Simple Text", {
x: 1, y: 1, w: 8, h: 2, fontSize: 24, fontFace: "Arial",
color: "363636", bold: true, align: "center", valign: "middle"
});
// Character spacing: use charSpacing (letterSpacing is silently ignored)
slide.addText("SPACED TEXT", { x: 1, y: 1, w: 8, h: 1, charSpacing: 6 });
// Rich text array (mixed styles in one paragraph)
slide.addText([
{ text: "Bold ", options: { bold: true } },
{ text: "Italic ", options: { italic: true } }
], { x: 1, y: 3, w: 8, h: 1 });
// Multi-line (each line needs breakLine: true; the last may omit it)
slide.addText([
{ text: "Line 1", options: { breakLine: true } },
{ text: "Line 2", options: { breakLine: true } },
{ text: "Line 3" }
], { x: 0.5, y: 0.5, w: 8, h: 2 });
// Text-box padding: set margin: 0 to align with shapes/lines
slide.addText("Title", { x: 0.5, y: 0.3, w: 9, h: 0.6, margin: 0 });
⚠️ Rich text arrays emit one
<a:pPr>per run, not per paragraph. Two or more consecutive items withoutbreakLineland in the same<a:p>, each carrying its own<a:pPr>— which violates thepPr? (r|br|fld)* endParaRPr?schema. LibreOffice renders it fine, so a PDF preview will not catch it; PowerPoint paints the first frame correctly, then re-lays-out the paragraph and the line garbles. Two safe options: give every itembreakLine: true(one run per paragraph), or, when you genuinely need mixed formatting inline, post-process the slide XML afterwriteFile()and drop every<a:pPr>after the first one inside each<a:p>. Do not fix it by splitting the runs into separate paragraphs — that silently turns one inline-mixed line into two lines and changes the layout you designed.
// ✅ Correct: multiple bullets
slide.addText([
{ text: "First item", options: { bullet: true, breakLine: true } },
{ text: "Second item", options: { bullet: true, breakLine: true } },
{ text: "Third item", options: { bullet: true } }
], { x: 0.5, y: 0.5, w: 8, h: 3 });
// ❌ Wrong: never use unicode bullets (creates double bullets)
slide.addText("• First item", { ... });
// Sub-items & numbered lists
{ text: "Sub-item", options: { bullet: true, indentLevel: 1 } }
{ text: "First", options: { bullet: { type: "number" }, breakLine: true } }
bullet: true looks amateurish)The bare bullet: true renders a big dot with a huge gap to the text (pptxgenjs defaults to a ~27pt hanging indent) — a classic AI-list tell. Always style bullets:
// bullet is a PARAGRAPH property — put it in EACH item's options, not top-level.
// A top-level `bullet` only styles the first paragraph; the rest get <a:buNone/> (no dot).
const bu = () => ({ code: "2022", indent: 14 }); // factory: fresh object per item (pptxgenjs mutates in place)
slide.addText([
{ text: "First item", options: { bullet: bu(), breakLine: true } },
{ text: "Second item", options: { bullet: bu(), breakLine: true } },
{ text: "Third item", options: { bullet: bu() } }
], {
x: 0.5, y: 0.5, w: 8, h: 3, fontSize: 15, color: "334155",
paraSpaceAfter: 8, // item spacing (never lineSpacing)
margin: 0, // align glyph to x
});
indent matters most — cut the default ~27pt to 10–16pt to kill the "floating dot" (indent = glyph→text gap in pt; try 10–16).2022(•), 25AA(▪), 2013(–), 25B8(▸) read more designed than a fat dot; mute the color (e.g. 94A3B8) to keep it subtle.slide.addShape(pres.shapes.RECTANGLE, {
x: 0.5, y: 0.8, w: 1.5, h: 3.0,
fill: { color: "FF0000" }, line: { color: "000000", width: 2 }
});
slide.addShape(pres.shapes.OVAL, { x: 4, y: 1, w: 2, h: 2, fill: { color: "0000FF" } });
slide.addShape(pres.shapes.LINE, {
x: 1, y: 3, w: 5, h: 0, line: { color: "FF0000", width: 3, dashType: "dash" }
});
// Transparency
slide.addShape(pres.shapes.RECTANGLE, {
x: 1, y: 1, w: 3, h: 2, fill: { color: "0088CC", transparency: 50 }
});
// Rounded rectangle (rectRadius works only on ROUNDED_RECTANGLE, not RECTANGLE)
slide.addShape(pres.shapes.ROUNDED_RECTANGLE, {
x: 1, y: 1, w: 3, h: 2, fill: { color: "FFFFFF" }, rectRadius: 0.1
});
// Shadow (to make a card stand out — use this, not an edge stripe)
slide.addShape(pres.shapes.RECTANGLE, {
x: 1, y: 1, w: 3, h: 2, fill: { color: "FFFFFF" },
shadow: { type: "outer", color: "000000", blur: 6, offset: 2, angle: 45, opacity: 0.15 }
});
Shadow options:
| Property | Range / notes |
|---|---|
type | "outer" / "inner" |
color | 6-char hex (no#, no 8-char hex) |
blur | 0–100 pt |
offset | 0–200 pt,must be non-negative (negatives corrupt the file) |
angle | 0–359°, clockwise from 3 o'clock (45 = bottom-right, 135 = bottom-left, 270 = upward) |
opacity | 0.0–1.0 (use this for transparency, never encode it incolor) |
To cast a shadow upward (e.g., a card near the bottom), use
angle: 270+ a positive offset — not a negative offset.
Gradient fills are not natively supported — use a gradient image as the background instead.
// Three sources
slide.addImage({ path: "images/photo.jpg", x: 1, y: 1, w: 5, h: 3 }); // local
slide.addImage({ path: "https://example.com/img.jpg", x: 1, y: 1, w: 5, h: 3 }); // URL
slide.addImage({ data: "image/png;base64,iVBORw0KGgo...", x: 1, y: 1, w: 5, h: 3 }); // base64 (faster)
// Options
slide.addImage({
path: "image.png", x: 1, y: 1, w: 5, h: 3,
rotate: 45, rounding: true /*circular crop*/, transparency: 50,
flipH: true, flipV: false, altText: "Description",
hyperlink: { url: "https://example.com" }
});
// Sizing modes
{ sizing: { type: 'contain', w: 4, h: 3 } } // fit inside, preserve ratio
{ sizing: { type: 'cover', w: 4, h: 3 } } // fill area, preserve ratio (may crop)
{ sizing: { type: 'crop', x: 0.5, y: 0.5, w: 2, h: 2 } } // cut a specific portion
// Compute size from aspect ratio and center it (W = the canvas-width constant, never a literal)
const origW = 1978, origH = 923, maxH = 3.0;
const calcW = maxH * (origW / origH);
const centerX = (W - calcW) / 2;
slide.addImage({ path: "image.png", x: centerX, y: 1.2, w: calcW, h: maxH });
Supports PNG / JPG / GIF / SVG (SVG works in modern PowerPoint / Microsoft 365).
outEnd labels on stacked bar chartsOOXML restricts c:dLblPos by grouping: stacked / percentStacked only allow
ctr / inBase / inEnd — outEnd is valid only for clustered (line: ctr/l/r/t/b;
pie/doughnut: bestFit/ctr/inEnd/outEnd).
pptxgenjs won't stop you from writing barGrouping: "stacked" +
dataLabelPosition: "outEnd", and LibreOffice renders it fine — only PowerPoint rejects it,
triggers "repair", and drops the chart.
To highlight a single bar, use per-point colors instead:
slide.addChart(p.charts.BAR, [{
name: "series",
labels: ["A", "B", "C"],
values: [10, 20, 30],
}], {
barDir: "col",
varyColors: true, // per-point coloring
chartColors: ["17457E", "17457E", "E8590C"], // highlight the 3rd bar
showValue: true,
dataLabelPosition: "outEnd", // valid under clustered grouping
});
slide.background = { color: "F1F1F1" }; // solid
slide.background = { color: "FF3399", transparency: 50 }; // with transparency
slide.background = { path: "https://example.com/bg.jpg" }; // image URL
slide.background = { data: "image/png;base64,iVBORw0KGgo..." };// image base64
slide.addTable([
["Header 1", "Header 2"],
["Cell 1", "Cell 2"]
], { x: 1, y: 1, w: 8, h: 2, border: { pt: 1, color: "999999" }, fill: { color: "F1F1F1" } });
// Merged cells
let tableData = [
[{ text: "Header", options: { fill: { color: "6699CC" }, color: "FFFFFF", bold: true } }, "Cell"],
[{ text: "Merged", options: { colspan: 2 } }]
];
slide.addTable(tableData, { x: 1, y: 3.5, w: 8, colW: [4, 4] });
Principle: keep charts native and editable. Choose your approach by what PowerPoint can represent, not by what's quickest to code:
addChart(); never render to an image.addImage().// Bar
slide.addChart(pres.charts.BAR, [{
name: "Sales", labels: ["Q1","Q2","Q3","Q4"], values: [4500,5500,6200,7100]
}], { x: 0.5, y: 0.6, w: 6, h: 3, barDir: 'col', showTitle: true, title: 'Quarterly Sales' });
// Line
slide.addChart(pres.charts.LINE, [{
name: "Temp", labels: ["Jan","Feb","Mar"], values: [32,35,42]
}], { x: 0.5, y: 2.5, w: 6, h: 2.5, lineSize: 3, lineSmooth: true });
// Pie
slide.addChart(pres.charts.PIE, [{
name: "Share", labels: ["A","B","Other"], values: [35,45,20]
}], { x: 6.5, y: 1, w: 3, h: 3, showPercent: true });
Make charts look modern (defaults look dated):
slide.addChart(pres.charts.BAR, chartData, {
x: 0.5, y: 1, w: 9, h: 4, barDir: "col",
chartColors: ["0D9488", "14B8A6", "5EEAD4"], // match your palette
chartArea: { fill: { color: "FFFFFF" }, roundedCorners: true },
catAxisLabelColor: "64748B", valAxisLabelColor: "64748B", // muted axis labels
valGridLine: { color: "E2E8F0", size: 0.5 }, // subtle grid, value axis only
catGridLine: { style: "none" },
showValue: true, dataLabelPosition: "outEnd", dataLabelColor: "1E293B", // data labels
showLegend: false, // hide legend for single series
});
// Master
pres.defineSlideMaster({
title: 'TITLE_SLIDE', background: { color: '283A5E' },
objects: [{ placeholder: { options: { name: 'title', type: 'title', x: 1, y: 2, w: 8, h: 2 } } }]
});
let titleSlide = pres.addSlide({ masterName: "TITLE_SLIDE" });
titleSlide.addText("My Title", { placeholder: "title" });
// Speaker notes (visible only in Presenter View, not on the slide)
slide.addNotes("Open with the FY25 revenue headline; pause after the number. If asked about the Q3 dip: supply chain, resolved in Q4.");
# with hex — corrupts the file: color: "FF0000" ✅ / "#FF0000" ❌"00000020") corrupts the file; use the opacity propertybullet: true — never unicode • (double bullets)breakLine: true between array itemslineSpacing with bullets (excessive gaps) — use paraSpaceAfter insteadpptxgen() objectconst makeShadow = () => ({ type:"outer", blur:6, offset:2, color:"000000", opacity:0.15 });
slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... }); // ✅
fill tint or shadow to set them apart<a:pPr> per run — two or more runs in a single paragraph produce duplicate, schema-invalid paragraph properties that only PowerPoint chokes on; see the note under "Text & formatting"{type, data, options})"left" / "center" / "right""outEnd" / "inEnd" / "center"Route here when the user supplies a .pptx and wants a NEW deck built on it. Working from a template, you can infer the deck's design system — its layouts, typography, spacing, colors, and recurring content patterns, including the rules embedded in the Slide Master — and apply those conventions consistently to new material. Two non-negotiables: study the template BEFORE writing any content, and verify AFTER building — most template failures come from skipping one of the two.
Template inheritance (mandatory) — If the user provides an existing PPT, a corporate template, or a reference file, you must follow the "template inheritance" flow rather than recreating a look-alike from scratch:
- Analyze fonts, color scheme, spacing, footers, page numbers, placeholders, and brand elements.
- Build a mapping between source pages and new pages.
- Inherit existing layouts as much as possible instead of recreating a new set.
- Only modify elements that are allowed to be modified.
- Preserve the original template's visual language unless the user asks for a redesign.
- If no page in the original template can carry the target content, state the limitation explicitly and propose the closest alternative.
1. Decide the mode — "use as template" hides two different jobs:
| Mode | User signal | Build |
|---|---|---|
| Clone & fill | new outline or free structure — "做成这个风格" / "用这个模板做一份 X" | clone template pages and fill them; pick which pages to reuse by role and shape your outline around what the template offers |
| Fill-in | new content maps ≈1:1 onto the template's pages (换数据 / 换客户) | in-place replacement — Approach A of the Editing section |
Both modes edit the .pptx natively and output an editable PowerPoint — never route a template job through the from-scratch HTML pipeline; a foreign HTML-rendered page next to the template's real pages is instantly visible and loses the theme. Clone & fill builds a new deck on the template; Fill-in is the lighter in-place path. Everything below applies to both.
2. Study the template (before generating any content) — programmatic first, vision only to break ties:
• Inventory every text shape with python-pptx (recurse into GROUPs): position, size, font, current text length. The original text length is the budget ceiling — the "Budget every shape" rules of the Editing section apply verbatim. Collect this first; it also drives the role classification below.
• Classify each page's role (cover / section / content / stats / quote / closing…) — this role map is your layout catalog for planning. Derive the role from the inventory, not from an image: cover = few shapes + an outsized title (≥36pt) and/or a full-page background image; section = only 1–2 short text shapes; stats = a 60pt+ numeric shape; quote = a single large centered long-text box; content = several body boxes. Derive every page's role from the inventory alone — never fall back to rendering an image.
• Answer the one decisive structural question: does the design live in slideLayouts with real placeholders (→ clone & fill can add_slide + fill), or is it drawn on slides with free text boxes while layouts sit empty (common in downloaded templates; → clone & fill must clone slides at XML level)?
• Read the design's source of truth: presentation.xml for slide size (never assume 16:9); theme1.xml for colors and fonts — slide XML uses schemeClr indirection, the real hex lives in the theme (remapped by the master's clrMap), and CJK text renders in the <a:ea> font.
3. Plan, then build — always on a COPY of the user's file:
• Decide the output path BEFORE you modify anything. By default save to a NEW sibling file
(<stem>_updated.pptx), never touch the input; overwrite in place ONLY if the user explicitly
asks to edit their own file — then first copy it to <stem>_backup.pptx next to it (never
/tmp). Presentation.save(path) clobbers whatever path you name, so never pass it the user's
original.
• Write a short plan first: final page order; each page's template source (slide index to clone, or layout name) chosen by ROLE; replacement text written WITHIN the budget. If the user's outline and the template's structure conflict, surface the trade-off instead of improvising. (Clone & fill: since structure is free, let the template's available page roles drive the outline rather than forcing a shape the template can't carry.)
• Fill-in: follow the Editing section's Approach A as written.
• Clone & fill: materialize pages first — add_slide + fill placeholders when layouts are real; otherwise clone the slide at XML level (copy the slide part + its .rels, re-register media rIds, add to sldIdLst and [Content_Types].xml). No helper scripts ship with this skill — write the clone yourself, either in-process (copy.deepcopy(src.slides[i]._element) + prs.part.relate_to(...) to rebuild each rel) or by unpacking the file with zipfile (ZipFile.extractall → edit the XML → repack: write [Content_Types].xml as the first entry, use ZIP_DEFLATED, and add no directory entries). Then replace content page by page (scope mappings per slide — clones share identical source text), swap images by replacing the image part bytes (keep the shape and rId), and delete unused template pages LAST, high-index first. Never hand-build a from-scratch page next to template pages — a foreign page is instantly visible.
4. Verify (mandatory before reporting done) — programmatic checks, no rendering:
The whole checklist is decidable without an image; run these and fix until clean:
• Page order & count — count sldIdLst against the plan.
• Leftover placeholders — grep the slide XML for Click to add, xxx, lorem, TODO, [insert.
• Broken images — confirm every image rId on a cloned page resolves to a real media part (empty frames come from dangling rels): for each slide walk slide.part.rels and assert every non-external rel has a target_part with a non-empty .blob.
• File still opens — reopen the saved copy with Presentation(path) and walk every slide's shapes. A dangling rel or malformed slide XML raises here; this load-and-walk is the XML validity check (there is no validate.py in this skill).
• Fonts & colors unchanged — diff the run/theme font+color against the original rather than eyeballing it.
• Overflow / collision — re-check the "Budget every shape" rule (len ≤ orig_len × 1.1) on the final text and compare each left+width against its neighbor. A passing budget is the overflow guard here — treat any shape over budget as a real defect and fix it (trim, widen, or shrink per the Editing rules).
For in-place edits to an existing deck (Fill-in mode, and small fixes like typos or updating numbers), work on a COPY and pick the approach by what the edit touches:
Output first, modify second. By default save edits to a NEW sibling file (
<stem>_updated.pptx),
never touch the input; overwrite in place ONLY if the user explicitly asks to edit their own file —
then first copy it to<stem>_backup.pptxnext to it (never/tmp). "Work on a copy" means the
original stays byte-identical at its original path, not merely that you opened it read-only.
python-pptx script — preferred for text replacement, deleting/reordering slides, and any edit that should preserve fonts/colors/layout. Simpler and safer than raw XML for content swaps.python-pptx can't reach.python-pptx text replacement (preferred for text edits)Workflow
Inventory the deck — walk every slide, recurse into GROUP shapes (shape_type == 6), print(repr(para.text)). Use the inventory as the source of truth for replacement keys; rendered text often contains hidden chars that won't survive copy-paste.
Helpers — keep the build script short:
from pptx import Presentation
from pptx.enum.text import MSO_AUTO_SIZE
from pptx.oxml.ns import qn
from pptx.util import Emu, Pt
def iter_text_frames(shapes):
for s in shapes:
if s.shape_type == 6: # GROUP → recurse
yield from iter_text_frames(s.shapes)
elif s.has_text_frame:
yield s, s.text_frame
def _norm(s): # strip soft breaks before matching
return s.replace("\x0b", "").replace("\r", "").strip()
def replace_in_paragraph(p, new_text): # first-run replace preserves formatting
runs = p.runs
if not runs:
p.add_run().text = new_text; return
runs[0].text = new_text
for r in runs[1:]:
r._r.getparent().remove(r._r)
def apply_replacements(tf, mapping): # full-frame match, then per-paragraph
m = {_norm(k): v for k, v in mapping.items()}
full = "\n".join(p.text for p in tf.paragraphs)
if _norm(full) in m:
parts = m[_norm(full)].split("\n")
for i, p in enumerate(tf.paragraphs):
replace_in_paragraph(p, parts[i] if i < len(parts) else "")
return
for p in tf.paragraphs:
if _norm(p.text) in m:
replace_in_paragraph(p, m[_norm(p.text)])
def delete_slide(prs, idx): # call high-index first
sld = list(prs.slides._sldIdLst)[idx]
prs.part.drop_rel(sld.get(qn("r:id")))
prs.slides._sldIdLst.remove(sld)
Budget every shape BEFORE generating replacement text (do this first)
Most overflow bugs come from generating copy without knowing the target box's capacity. Before drafting any replacement, walk the deck once and emit a capacity manifest — then feed it to the content step as a hard constraint.
For each text-bearing shape collect: slide_idx, shape_id, w_cm, h_cm, font_pt, orig_text, orig_len. Then:
budget = orig_len × 1.1. The template designer already tuned orig_len for this box — treat it as the ceiling, not a starting point. This one rule is the actual overflow guard; don't over-engineer it with width/line-height estimates (glyph advance widths vary by font and by CJK-vs-Latin mix, so any chars_per_line formula is a rough guess that the orig_len cap already subsumes).role = "label" if h_cm < 1.5 OR orig_len ≤ 8 OR font_pt ≥ 20; else "body".Rules the generation step MUST obey:
max(orig_len, 8). SWOT tiles, timeline tags, KPI labels all fall here.budget. Font size is inherited from the template; shrinking is a last resort, not plan A.widen_to_fit(shape, Emu(...)) — see below) rather than shrinking the font. Check first that left + width won't collide with the next shape.Handling long replacement / unwanted wrapping after replacement
When a longer replacement wraps to a new line, apply remedies in this order (cheapest first):
def widen_to_fit(shape, max_grow_emu=Emu(0)):
"""Let PowerPoint size the shape to its text. Pass max_grow_emu>0 to also
grow the explicit width (centered on the original position) before sizing."""
if max_grow_emu:
shape.left -= max_grow_emu // 2
shape.width += max_grow_emu
shape.text_frame.word_wrap = True
shape.text_frame.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
def shrink_text_to_fit(shape):
"""Keep the box fixed; let PowerPoint shrink the font to fit."""
shape.text_frame.word_wrap = True
shape.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
⚠️ Both helpers only write the autofit flag into the XML — python-pptx does not compute the resized shape or the shrunk font-scale itself. The actual fit is applied by the viewer (PowerPoint / LibreOffice) when the file is opened, so your programmatic overflow check can't see the result. Prefer trimming to
budget(below), which is verifiable without rendering.
shape.width × font_size from inventory and trim the replacement so it fits the original visual budget. Numeric badges / small label boxes (width ≤ 0.7", font_size ≥ 16pt) hold ~3–4 chars max.widen_to_fit(shape, Emu(...)) when the content is genuinely longer and there's free space next to it. Always check the shape isn't going to collide with a neighbor first (compare left+width against the next shape's left).shrink_text_to_fit(shape) only for tight-layout boxes (table cells, numeric badges) where widening would break the grid. Last resort — it visibly breaks the typographic rhythm.Skip word_wrap = False: it makes text overflow the box invisibly in PowerPoint and looks broken when exported.
Critical gotchas
\x0b) silently break exact-match. Always _norm() both keys and lookups.shape_type == 6) hide text frames — recurse.paragraph.text = ... destroys it."01", "%", "18" recur across slides — keep identity mappings or scope per slide index, never global cross-mappings like "18": "12".IMPORTANT: When generating code for PPTX operations:
Required dependencies (should already be installed):
pip install "markitdown[pptx]" (text extraction)npm install -g pptxgenjs (creating presentations)npm install -g playwright@1.50.0 (HTML rendering)npm install -g sharp (SVG rasterization and image processing)https://mirrors.tuna.tsinghua.edu.cn/libreoffice/libreoffice/stable/26.8.0/, see the hard
requirement below); sudo apt-get install libreoffice only as a fallbacksudo apt-get install poppler-utils (pdftoppm)pip install defusedxml (secure XML parsing)Every PPTX → PDF conversion and every rendered preview/thumbnail step depends on LibreOffice.
If LibreOffice is not installed, you MUST install it. This is not a choice.
FORBIDDEN — do not do any of these:
REQUIRED sequence — no steps skipped, in this order:
Explain the necessity to the user. LibreOffice is what renders the deck's real layout — master slides, themes, placeholder geometry — so it is the only way to verify slides actually fit before delivery. Substituting it means shipping an unverified deck.
Tell them the download is large (roughly several hundred MB) and may take a while. This is information for the user, not a decision point for you — state it and proceed to install.
Install it now. Preferred source — Tsinghua TUNA mirror (fast in China, current build):
https://mirrors.tuna.tsinghua.edu.cn/libreoffice/libreoffice/stable/26.8.0/. Pick the
subdirectory matching the OS and CPU arch (uname -m / $env:PROCESSOR_ARCHITECTURE) and
download the main package from it:
| Platform | Path | Main package |
|---|---|---|
| Linux x86_64 (deb) | deb/x86_64/ | LibreOffice_26.8.0_Linux_x86-64_deb.tar.gz |
| Linux ARM64 (deb) | deb/aarch64/ | LibreOffice_26.8.0_Linux_aarch64_deb.tar.gz |
| Linux x86_64 (rpm) | rpm/x86_64/ | LibreOffice_26.8.0_Linux_x86-64_rpm.tar.gz |
| macOS Apple Silicon | mac/aarch64/ | LibreOffice_26.8.0_MacOS_aarch64.dmg |
| macOS Intel | mac/x86_64/ | LibreOffice_26.8.0_MacOS_x86-64.dmg |
| Windows x64 | win/x86_64/ | LibreOffice_26.8.0_Win_x86-64.msi |
| Windows ARM64 | win/aarch64/ | LibreOffice_26.8.0_Win_aarch64.msi |
Linux (deb): tar -xzf <pkg>.tar.gz && sudo dpkg -i LibreOffice_26.8.0*/DEBS/*.deb && sudo apt-get install -f -y
macOS: hdiutil attach <pkg>.dmg && cp -R /Volumes/LibreOffice/LibreOffice.app /Applications/ && hdiutil detach /Volumes/LibreOffice
Windows: msiexec /i <pkg>.msi /qn (needs admin). Verify with soffice --version.
If 26.8.0 is gone (the mirror keeps only a few releases), list
https://mirrors.tuna.tsinghua.edu.cn/libreoffice/libreoffice/stable/ and use the newest version
directory, substituting that version number above.
Package-manager fallback, only if the mirror is unreachable:
brew install --cask libreofficesudo apt-get install libreofficewinget install TheDocumentFoundation.LibreOfficeRegister the binary on PATH, then re-verify. Do this as part of the install, not as an
afterthought: an install that leaves soffice off the executable search path is
indistinguishable from "not installed" to the next command -v soffice probe — which re-triggers
this whole block and makes you download several hundred MB you already have. The mechanism
differs per OS — use the one for the platform you are on:
| Platform | Register it | Then verify |
|---|---|---|
| Linux | sudo ln -sf /opt/libreoffice*/program/soffice /usr/local/bin/soffice | soffice --version |
| macOS | sudo ln -sf /Applications/LibreOffice.app/Contents/MacOS/soffice /usr/local/bin/soffice | soffice --version |
| Windows | setx PATH "$([Environment]::GetEnvironmentVariable('PATH','User'));C:\Program Files\LibreOffice\program" | & "C:\Program Files\LibreOffice\program\soffice.exe" --version |
Notes: on Apple Silicon /usr/local/bin is not always on PATH — if the verify still fails, link
into /opt/homebrew/bin instead. On Windows setx only affects newly launched shells, so for
the rest of the current session call soffice.exe by its full path. Only continue once the verify
prints a version. Corollary: probe before installing — if the binary already exists on disk
but off-PATH (check /opt/libreoffice*/program/soffice,
/Applications/LibreOffice.app/Contents/MacOS/soffice,
C:\Program Files\LibreOffice\program\soffice.exe), register it instead of reinstalling.
Only after that install has actually been attempted AND failed may you use software already on the user's machine (Keynote, PowerPoint, WPS). Report the install failure and what you switched to, and warn that the rendering may differ from this skill's normal checks.
The gate is simple: no install attempt = no substitute program. If you have not run the
install command and seen it fail, using Keynote/PowerPoint/WPS is a violation of this skill.
Place ::zcode-file-citation{...} inline in prose, not in a trailing list. Use purpose="source" for Q&A/no-op and purpose="output" for create/edit.
Created ::zcode-file-citation{path="/abs/path/launch-deck.pptx" purpose="output"}, highlighting the rollout and owners.inspect the complete relevant slide, including callouts, question wording, chart/table titles, totals/sample sizes, and source/methodology footers. Answer directly, group same-slide claims, and cite that slide once. For concrete chart/table/image/diagram/callout evidence, include exact inspected slide_id, object_id, and a useful label when available.
For non-in-place edits, preserve the source and export a copy; if unchanged, cite the source plainly.
Use only locators verified against the latest render/inspection:
::zcode-file-citation{path="/abs/path/deck.pptx" purpose="source" artifact_kind="presentation" slide_number=3}
::zcode-file-citation{path="/abs/path/deck.pptx" purpose="source" artifact_kind="presentation" slide_number=1 slide_id="sl/gs5z1kshq0xv" object_id="ch/pz9t1r3ka8vn" label="ARR by segment chart"}
If IDs are not exact, stop at slide_number; never guess or cite intermediates unless asked.
まだレビューはありません。使ってみた感想をお寄せください。
ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。
まだコメントはありません。最初のコメントを書いてみませんか。
立体物をthree.jsで作成し、あらゆる角度から確認できるビューアーに表示します。OBJ + MTLやGLBで保存し、他の3D制作ソフトへ持ち込めます。
Chrome DevTools MCPでWebページのアクセシビリティを監査し、見出し構造、ラベル、キーボード操作、フォーカス、タップ領域、色のコントラストに関する問題を調べます。
個人用のタスク一覧に、やることや期限、返答待ち、作業が止まる理由を記録します。進捗や依存関係を更新し、重複や完了済みの項目も整理するスキルです。
作業の目標と完了条件を整理し、必要に応じて複数のエージェントへ分担します。進捗報告、成果の検証、PRのマージまで、プロジェクトとして進行を管理します。
Antigravityのカスタマイズの種類や読み込み順、検出場所を説明し、スキル、ルール、プラグイン、フック、MCPサーバーの作成方法を案内します。
再生・一時停止や時間位置の指定ができるアニメーションをHTMLページとして制作します。場面の長さや速度を調整し、横長・縦長の動画書き出しに備えます。