--- name: pptx description: "幻灯片设计规范与文件级制作:版式、配色、字体、图表、图文叠放的成稿标准,以及用 pptxgenjs / python-pptx 创建与编辑 .pptx(含套用用户模板、改写既有演示)。用于做 PPT 文件、改版既有演示、按视觉规范把内容排成一页一到两个主张的幻灯片。" --- # Part 1 · Slide Design Best Practices 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. ## 1. Decide three things before you start **① 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** - "Sandwich" structure: **dark backgrounds** for the title and closing slides, **light backgrounds** for content slides. Or commit to dark throughout for a premium feel. - Pick **one** signature motif and repeat it on every slide: rounded image frames, oversized numbers, a consistent card treatment, etc. - ⚠️ **Do NOT** use a "color bar / accent stripe / sidebar strip" as your motif — that's a hallmark of AI-generated slides (see the avoid list). ## 2. Color Build the palette on the **BACKGROUND → PRIMARY → ACCENT** model, and reuse those exact same three roles on every single slide: - **BACKGROUND** — a neutral or near-neutral surface (off-white, warm grey, deep navy, near-black) that carries 60–70% of the slide - **PRIMARY** — the brand/topic color used for headers, key shapes, chart bars and structural elements - **ACCENT** — ONE saturated color, used sparingly (roughly 5–10% of the slide) on the single most important element: a headline number, a highlighted bar, an underline - **Define the palette once as constants at the top of the script** (e.g. `BG`, `PRIMARY`, `ACCENT`, plus `TEXT` and `MUTED`) and reference only those constants — never hand-pick ad-hoc colors slide by slide. - Be **restrained and consistent**: no loud, garish, neon or clashing colors, no rainbow palettes, no slide that introduces a color the rest of the deck never uses. Slide-to-slide color shifts should be limited to the background/text inversion of the sandwich structure. - Prefer **tints and shades of the primary** (lighter/darker variants) over adding new hues when you need more differentiation — including in charts. - High contrast between text and background: dark text on light backgrounds **or** light text on dark backgrounds. ## 3. Layout for each 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: - **Hairline separators** (one 1px rule between items, never thickened, never shadowed) / **row list** (full-width rows, no container, separated by generous line spacing and a bold lead-in phrase) / **outline box** (1px stroke, no fill) / **tinted band** (a horizontal band of very light tint bleeding to both slide edges — no rounded corners, no border, no shadow) - Icon + text rows (icon at the row's left, bold header, description below — set the icon on the bare background or in a square/rounded-square holder, not a circle) **Pick layouts that serve each slide's purpose:** - Opening / closing → bold title layouts - Processes / workflows → numbered step visuals - Comparisons → side-by-side or table layouts - Data / metrics → chart-centric layouts with oversized figures - Key insights → single-statement emphasis layouts - Team / people → side-by-side card row (parallel cards in one row, not a grid) **Data display:** (point sizes here and throughout assume the 13.33 × 7.5" canvas — see §6) - Large stat callouts (numbers 60–72pt with a small label below) - Comparison columns (before/after, pros/cons, side-by-side options) - Timeline / process flow (numbered steps, arrows) - Keep metrics short and punchy — "$87B TAM" beats "Total Addressable Market is $87 billion" **Density & rhythm:** - **One key message per slide** — never overcrowd. Visual hierarchy: title → subtitle → body → supporting detail. - **Control the amount of text per slide.** Too much text is the most common cause of overflow. Keep bullets concise (a short phrase, not a full sentence), avoid dense paragraphs, and if a slide is getting text-heavy, trim it or split it across two slides. The goal is comfortable density, not empty slides — just don't overpack. - **Leave breathing room** — give each text box enough width/height that the text fits comfortably at its font size with margin to spare, rather than filling the box edge-to-edge. - **Vary the rhythm** — alternate between text-driven, image-driven, and data-driven slides. - **No large blank areas.** Don't leave a big region of the canvas empty. Resolve it with CONTENT — enlarge the type or the focal element, widen the chart or image, tighten the grid and margins, or promote real content (a pull quote, a caption, a supporting figure) into that space. - **Never fill space with content-free decoration** — no filler bars, strips, rules, or flat color blocks whose only purpose is to occupy emptiness. A graphic element must separate, group, or emphasize real content; otherwise leave the space alone. - A region occupied by a **photo is NOT empty**. If a photo area reads as a dark void, the overlay is too heavy or the photo too dark — lighten the overlay or pick a brighter image rather than stacking decoration on top of it. - **Sanity check per slide:** is there an obvious focal point, and is nothing overflowing? ## 4. Diagrams, flowcharts & data charts (strongly preferred) - **Favor visual explanation over prose.** Whenever content can be shown as a diagram, show it as a diagram — a bulleted list is the fallback, not the default. - Reach for these first: schematic/concept diagrams (boxes + connectors for architecture or relationships), flowcharts and numbered process chains (chevrons, arrows, timelines), comparison matrices, and data charts (bar, column, line, stacked, donut) for anything quantitative. - Aim for a diagram, flowchart, or chart on a substantial share of the content slides — **a deck that is all text blocks is a failed deck**. - Build them **natively** (`addChart` / `add_chart` with `CategoryChartData`, or composed shapes + connectors + text boxes) so they inherit the palette and cost zero image calls. - **Label everything**: axis titles, units, categories, and direct value labels on bars/points. Drop chart junk — no 3D, no gradients, no unnecessary gridlines or legends when direct labels suffice. - **Cite the source for every key figure and every chart**: a small source line (10–12pt, muted) at the bottom of the slide, e.g. "Source: IDC Worldwide Tracker, 2025" or "Source: company 10-K, FY2024". - Never present an invented number as sourced. If a figure is an estimate or illustrative, label it as such ("illustrative", "est."). ## 5. Image + text layering (critical) - **NEVER place text directly on a full-bleed image** — it will be unreadable. - When using a background image, ALWAYS add a semi-transparent overlay between the image and the text: 1. Add the image (full-bleed, send to back) 2. Add a dark overlay rectangle (full-bleed, ~60–80% opacity) on top of the image 3. Then add text on top of the overlay - **Alternative (preferred):** instead of full-bleed images, place the image in a bounded region (e.g. the right half) and keep text in the other region on a solid background. - If an image is decorative, keep it small and positioned where it won't collide with text. - **Never stretch an image to fit** — when its aspect ratio doesn't match the target box, crop it (`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. - Test mentally: *"if I printed this slide in grayscale, could I still read every word?"* ## 6. Typography - **Deliberately CHOOSE a font pairing that suits the scenario, and name the choice in your plan** — a geometric/neo-grotesque sans for corporate, tech and data decks; a serif for editorial, academic, legal or heritage topics; a high-contrast display face for the cover only. **Never leave the library's default font in place.** - **Set the font explicitly on every run/paragraph** (`run.font.name` in python-pptx, `fontFace` in pptxgenjs) — do not rely on inherited theme fonts. - **Cover/title type:** large (44–72pt), bold, impactful. **Content slides:** readable body text, ~24pt preferred. (All sizes in this skill assume the default **13.33 × 7.5"** canvas — set it explicitly, and if you deliberately target a 10"-wide layout scale every size down by ×0.75.) - The **smallest text on any slide** (captions, axis labels, footnotes, source lines) must still be legible: never below 12pt, and keep it high-contrast against its background. - **Legibility beats decoration:** no thin/light weights on colored or image backgrounds, no all-caps for long strings, no letter-spacing so tight that glyphs collide. - **Limit to 2 font families maximum**, and create hierarchy with **size and weight**, not by swapping faces. - **No emoji in slide content.** > **Preview caveat:** the font names you write into the `.pptx` are 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. ## 7. Spacing - Minimum margins **0.5"** - **0.3–0.5"** between content blocks - Consistent margins and spacing across all slides - Leave breathing room — don't fill every inch ## 8. CJK fonts - **Name a face the viewer's PowerPoint actually ships**: 微软雅黑 / 等线 (Windows), 苹方 PingFang SC (macOS). A face that only exists on the build machine (Noto Sans SC, 思源黑体, LXGW 文楷 …) silently substitutes on the user's machine — use it only as the fallback, not the only name. - **Sans for the deck, serif for editorial weight**: 微软雅黑 / 苹方 for corporate, tech and data decks; 思源宋体 / 宋体 only for cultural, academic or heritage topics, and mainly on titles. **Never** use 楷体 / 行楷 / 隶书 / 艺术字体 for body text. - **Avoid Light/Thin CJK weights** — Chinese glyphs have far more strokes than Latin, so hairline weights turn to mud on a projector. Regular for body, Bold/Semibold for titles; build hierarchy with size and weight, not with a third face. - Chinese text has no spaces to break on, so a long run wraps mid-phrase — keep lines short and give CJK boxes ~15% more width than the Latin equivalent. Upside: CJK glyphs are full-width, so overflow checks on Chinese text are fairly trustworthy. - Default to 宋体 or the library default and the deck instantly reads as a Word document — pick the face deliberately, same as §6 requires. ## 9. Avoid list (sources of an "AI-generated" look) - ❌ **Don't reuse the same layout on every slide** — vary between columns, cards, and callouts - ❌ **Don't overuse grid layouts** — card/tile grids (2×2, 3×2, 4-up …) are a strong AI-slide tell when repeated. Cap them at roughly **1 in 5 content slides**, never on consecutive slides, and only when the content is genuinely a set of peer items (team, feature matrix). For everything else use a focal-point, column, timeline, or diagram layout instead - ❌ **Don't center body text** — left-align paragraphs and lists; center only titles - ❌ **Make size contrast big enough** — titles need 36pt+ to stand out from body text - ❌ **Don't default to blue** — choose colors that reflect the topic - ❌ **Don't mix spacing randomly** — pick 0.3" or 0.5" and use it consistently - ❌ **Don't style one slide and leave the rest plain** — commit fully or keep it simple throughout - ❌ **Don't create text-only slides** — add images, charts, diagrams, or shapes - ❌ **Mind text-box padding** — to align text with shapes/lines, set the text box `margin` to 0 (or offset the shape to compensate) - ❌ **Don't use low contrast** — text and graphics both need strong contrast against the background; avoid light-on-light or dark-on-dark - 🚫 **Never add a decorative underline under titles** — a classic AI-slide tell; use whitespace or background color instead - 🚫 **Never add decorative color bars / accent stripes** — including full-width header/footer bands, vertical sidebar strips, thin colored strips along a card edge, and "single-side borders" on rectangles. To set a card apart, use a **subtle background tint or shadow**, not an edge stripe. In particular, never run the same edge-bar treatment on several consecutive slides - ❌ **Don't default to cream/beige backgrounds** — when unspecified, use white `FFFFFF` or your brand color; avoid warm-neutral defaults like `F5F5DC`, `FAF0E6`, `FAEBD7`, `FFF8E1` - ❌ **Don't let text overflow its shape** — if it doesn't fit, reduce the font, split across slides, or enlarge the container; never leave content cut off or spilling out - ❌ **Don't extra postprocess the east asia font if not needed** ## 10. QA (recommended) **Content 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: use pdftoppm to convert pptx2image and use judge subagent(if not exist,check it yourself) to check when neccesary. Do not call external vlm to check slides for visual QA --- # Part 2 · pptxgenjs in Depth pptxgenjs generates `.pptx` files in **JavaScript / Node.js**. Coordinates are in **inches**. ## Setup & basic structure ```bash npm install -g pptxgenjs ``` ```javascript 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 dimensions | 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_WIDE` explicitly on every deck.** pptxgenjs defaults to `LAYOUT_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). ## Text & formatting ```javascript // 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 `` per run, not per paragraph.** Two or more consecutive items *without* `breakLine` land in the same ``, each carrying its own `` — which violates the `pPr? (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 item `breakLine: true` (one run per paragraph), or, when you genuinely need mixed formatting inline, post-process the slide XML after `writeFile()` and drop every `` after the first one inside each ``. 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. ## Lists & bullets ```javascript // ✅ 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 } } ``` ### Make bullets look good (default `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: ```javascript // 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 (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). - **Refined glyphs** — `2022`(•), `25AA`(▪), `2013`(–), `25B8`(▸) read more designed than a fat dot; mute the color (e.g. `94A3B8`) to keep it subtle. - **For short card lists** (3–4 items), skip native bullets: draw a small colored dot/square shape + a text box per row for full control. ## Shapes ```javascript 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 in`color`) | > 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. ## Images ```javascript // 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). ## No `outEnd` labels on stacked bar charts OOXML 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:** ```js 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 }); ``` ## Backgrounds ```javascript 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 ``` ## Tables ```javascript 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] }); ``` ## Charts **Principle: keep charts native and editable.** Choose your approach by what PowerPoint can represent, not by what's quickest to code: 1. **Library-native** (bar/column/line/pie/area/scatter/bubble/radar/doughnut/combo) → use `addChart()`; **never** render to an image. 2. **PowerPoint-native but not exposed by the library** (trendlines, error bars) → stay native: compute the extra series yourself (e.g., a regression line as a second LINE/SCATTER series) or inject the OOXML. **Don't** fall back to a matplotlib PNG — you lose editability. 3. **Genuinely no native representation** (Sankey, network/graph, chord, complex statistical plots) → only here render to an image and insert via `addImage()`. ```javascript // 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):** ```javascript 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 }); ``` ## Slide masters & speaker notes ```javascript // 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."); ``` ## Common pitfalls (file corruption / visual bugs / AI look) 1. **Never use `#` with hex** — corrupts the file: `color: "FF0000"` ✅ / `"#FF0000"` ❌ 2. **Never encode opacity in hex** — 8-char hex (e.g., `"00000020"`) corrupts the file; use the `opacity` property 3. **Use `bullet: true`** — never unicode `•` (double bullets) 4. **Use `breakLine: true`** between array items 5. **Avoid `lineSpacing` with bullets** (excessive gaps) — use `paraSpaceAfter` instead 6. **Fresh instance per presentation** — don't reuse the `pptxgen()` object 7. **Don't reuse option objects across calls** — pptxgenjs **mutates objects in place** (e.g., converts shadow values to EMU), so sharing corrupts the second shape. Use a factory that returns a fresh object: ```javascript const makeShadow = () => ({ type:"outer", blur:6, offset:2, color:"000000", opacity:0.15 }); slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... }); // ✅ ``` 8. **Don't add edge accent bars to cards** — use a `fill` tint or `shadow` to set them apart 9. **Rich text arrays emit one `` 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" ## Quick reference - **Shapes**: RECTANGLE / OVAL / LINE / ROUNDED_RECTANGLE - **Charts**: BAR / COLUMN / LINE / AREA / PIE / DOUGHNUT / SCATTER / BUBBLE / RADAR / combo (array of `{type, data, options}`) - **Alignment**: `"left"` / `"center"` / `"right"` - **Data-label position**: `"outEnd"` / `"inEnd"` / `"center"` --- ## Creating a presentation FROM a user-provided template (.pptx) 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 `` font. **3. Plan, then build — always on a COPY of the user's file:** • 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). ## Editing an existing PowerPoint presentation (.pptx) 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: - **Approach A — `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. - **Approach B — raw OOXML** — required for animations, transitions, comments, speaker notes XML, theme tweaks, custom layout edits — anything `python-pptx` can't reach. ### Approach A — `python-pptx` text replacement (preferred for text edits) **Workflow** 1. **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. 2. **Helpers** — keep the build script short: ```python 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: - **Label boxes**: short phrase only. No full sentences, no trailing punctuation, no "term + explanation" expansion. Hard cap = `max(orig_len, 8)`. SWOT tiles, timeline tags, KPI labels all fall here. - **Body boxes**: stay within `budget`. Font size is inherited from the template; shrinking is a last resort, not plan A. - If the content is genuinely longer and the layout permits, **grow the box itself** (`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): ```python 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. 1. **Budget first (preferred).** Check `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. 2. **Widen the shape** with `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`). 3. **Shrink the font** with `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** - **Soft line breaks (`\x0b`)** silently break exact-match. Always `_norm()` both keys and lookups. - **GROUP shapes** (`shape_type == 6`) hide text frames — recurse. - **First-run replace** preserves formatting; `paragraph.text = ...` destroys it. - **Short tokens collide.** `"01"`, `"%"`, `"18"` recur across slides — keep identity mappings or scope per slide index, never global cross-mappings like `"18": "12"`. - **Delete slides high-index first** — deleting index 5 first shifts every later index down by one. ## Code Style Guidelines **IMPORTANT**: When generating code for PPTX operations: - Write concise code - Avoid verbose variable names and redundant operations - Avoid unnecessary print statements ## Dependencies Required dependencies (should already be installed): - **markitdown**: `pip install "markitdown[pptx]"` (text extraction) - **pptxgenjs**: `npm install -g pptxgenjs` (creating presentations) - **playwright**: `npm install -g playwright@1.50.0` (HTML rendering) - **sharp**: `npm install -g sharp` (SVG rasterization and image processing) - **LibreOffice**: `sudo apt-get install libreoffice` (PDF conversion) - **Poppler**: `sudo apt-get install poppler-utils` (pdftoppm) - **defusedxml**: `pip install defusedxml` (secure XML parsing) ---