From 467fbcf5e492fcb8e3799929f12ea88e1c0585bc Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 29 May 2026 11:21:54 -0400 Subject: [PATCH] Draft PPTX import spec Adds docs/PPTX_IMPORT_FEATURE.md as the canonical plan for the next big arc: a phased editable .pptx importer that lands PowerPoint decks as editable Artstr deck JSON. Key decisions baked in: - Images and unsupported objects (charts / SmartArt / tables / video) import as placeholder image layers pointing at a hardcoded URL (postimg.cc for now). Original PowerPoint media filenames carry over into the layer name so the user can correlate placeholders with their source deck. - fflate (~10 KB) vendored as src/vendor/fflate.min.js. JSZip is too heavy for the single-file ethos. - Importer code lives in src/pptx-importer.js as an IIFE that exposes window.ArtstrPptxImporter. The Import PPTX button dynamically injects both scripts on first click, so the main index.html bundle does not grow for the 90 % of users who never import a PPTX. - Notes folded into Phase 2 alongside text (single extra relationship lookup, Presenter Mode already renders slide.notes). - No Blossom / NIP-96 prereq, no base64 bloat, no publish-size guard. Imported decks are normal Artstr deck payloads with remote URLs. - Import-only for this arc. PPTX export (Artstr -> .pptx) is a separate later arc with its own complexity. TODO.md gets a Drafted-but-unbuilt entry pointing at the new spec. --- TODO.md | 8 + docs/PPTX_IMPORT_FEATURE.md | 1052 +++++++++++++++++++++++++++++++++++ 2 files changed, 1060 insertions(+) create mode 100644 docs/PPTX_IMPORT_FEATURE.md diff --git a/TODO.md b/TODO.md index 86bd14d..3f29f1b 100644 --- a/TODO.md +++ b/TODO.md @@ -66,6 +66,14 @@ toggle that fits the early-web aesthetic. Add new editor-UX items below. Each has a full spec in `docs/`. +- **PPTX import** — `docs/PPTX_IMPORT_FEATURE.md`. Phased editable + importer that converts a `.pptx` package into an Artstr deck. + Images and unsupported objects (charts, SmartArt, tables, video) + land as placeholder image layers pointing at a hardcoded URL; + user replaces each placeholder by editing the layer's src. No + Blossom prereq, no base64 bloat, decks publish to Nostr normally. + fflate vendored bundle + a lazy-loaded `src/pptx-importer.js` + module so the main app weight doesn't grow. - **Artstr Stacks** — `docs/STACKS_FEATURE.md`. Interactive fullscreen page/card stacks (HyperCard for Nostr) — the Slide Deck extended with layer actions and an interactive viewer. diff --git a/docs/PPTX_IMPORT_FEATURE.md b/docs/PPTX_IMPORT_FEATURE.md new file mode 100644 index 0000000..d3252bf --- /dev/null +++ b/docs/PPTX_IMPORT_FEATURE.md @@ -0,0 +1,1052 @@ +# Artstr Studio PPTX Import + +## Editable Import of PowerPoint Decks + +### Status + +Spec draft. Not yet implemented. This document is the canonical plan; +when work begins it follows this doc, and updates land here first. + +This is **import only** for the initial arc. PPTX export (Artstr → `.pptx`) +is a separate later feature with different complexity tradeoffs (OOXML +emission, package-validity testing) and is not in scope here. + +--- + +## 1. Feature Summary + +Add a PowerPoint `.pptx` import pathway to Artstr Studio that converts a +user-supplied presentation into an editable Artstr slide deck. + +The importer runs entirely in the browser. It reads the `.pptx` zip +package, extracts slide geometry, text boxes, vector shapes, fills, +strokes, colors, notes, and slide metadata where possible, and converts +the result into Artstr's existing deck JSON format. + +**Images are not embedded.** Every imported `p:pic` becomes an Artstr +image layer that points at a shared placeholder URL. The user manually +replaces each placeholder with their own image URL after import. This +keeps imported decks small, Nostr-publishable out of the box, and +faithful to Artstr's "remote URLs only" image model. + +The first version prioritizes a **reliable editable import** over +PowerPoint visual fidelity. Unsupported or ambiguous content is skipped, +approximated, flattened, or reported to the user — never silently +dropped. + +--- + +## 2. Product Goal + +A user uploads a `.pptx` file and Artstr creates a new editable slide +deck from it. + +- Imported slides are editable using existing Artstr canvas tools. +- Text becomes Artstr text layers. +- Images become Artstr image layers pointing at the shared placeholder. +- Basic PowerPoint shapes become Artstr shape layers. +- Each PowerPoint slide becomes one Artstr deck slide. +- Speaker notes become Artstr slide notes. + +Artstr becomes a lightweight PowerPoint-to-Nostr-presentation bridge. + +--- + +## 3. Non-Goals for Initial Release + +The initial release does not attempt full PowerPoint compatibility. +These are **not** supported as editable objects in MVP: + +- Animations +- Transitions +- Embedded video / audio playback +- SmartArt as fully editable objects +- Charts as fully editable objects +- Tables as fully editable objects +- Equations as editable math +- Comments / review metadata +- Complex placeholder behavior +- Exact text layout fidelity +- Full master / layout / theme inheritance +- Password-protected or encrypted presentations +- Legacy `.ppt` binary PowerPoint files + +Unsupported content lands as a generic placeholder layer plus a warning +in the import report. + +--- + +## 4. Existing Artstr Fit + +Artstr already has the pieces required for this feature: + +- `templateMode: 'deck'` for slide decks. +- Deck payloads embed every slide inline. +- Each deck slide carries a `slide` object and `layers` array. +- Standalone slides and custom art use the same flat canvas model. +- Canvas layers support text, images, shapes, fills, strokes, + gradients, opacity, rotation, clipping, and z-order. +- `loadProjectFromText` understands deck payloads and routes through the + same path as JSON load-from-file. + +The importer produces a normal Artstr deck payload and feeds it into +the existing project-loading path. + +--- + +## 5. Target JSON Shape + +```js +{ + version: SCHEMA_VERSION, + templateMode: 'deck', + templateType: 'deck', + meta: { + title: 'Imported deck title', + imdbId: '', + upc: '', + tvdbId: '', + musicbrainzDiscId: '', + customTag: '', + category: 'presentation', + language: 'en' + }, + deck: { + theme: { + fontFamily: '', + background: '' + }, + slides: [ + { + name: 'Slide 1', + slide: { + width: 1920, + height: 1080, + background: '#ffffff', + notes: '' + }, + layers: [ + // Artstr canvas layers + ], + ignoreDeckTheme: true + } + ] + } +} +``` + +Imported slides default to `ignoreDeckTheme: true` so an Artstr deck +theme does not unexpectedly override imported PowerPoint styling. + +--- + +## 6. UX + +### 6.1 Entry Point + +Add an **Import PPTX** action in the Deck Builder UI, distinct from +"Load from file" (which is the JSON path). + +```html + + +``` + +### 6.2 Flow + +1. User clicks **Import PPTX**. +2. If this is the first click of the session, lazy-load the importer + module (see §7.3). Show a one-time inline spinner: "Loading PPTX + importer…" +3. Browser file picker opens. +4. User chooses a `.pptx` file. +5. Artstr parses the file in-browser. +6. Artstr creates a new deck payload. +7. Artstr loads the payload into the existing editor. +8. Artstr shows the import report modal. + +### 6.3 Replace Current Project Confirmation + +If the current project has unsaved content, reuse the existing +close/save confirmation pattern: + +> Importing a PowerPoint file will replace the current deck. +> +> [Cancel] [Save JSON, then Import] [Import Anyway] + +### 6.4 Import Report Modal + +After import, show a summary: + +``` +Imported: presentation.pptx +Slides: 18 +Text boxes: 142 +Image placeholders: 24 +Shapes: 61 +Skipped (placeholders shown): 3 charts, 1 video, 2 SmartArt objects +Warnings: 12 style approximations + +Note: 27 images and unsupported objects were imported as placeholders. +Open each placeholder layer and paste an image URL to restore. +``` + +Each warning is human-readable, not technical: + +- "Slide 4: chart imported as placeholder." +- "Slide 7: unsupported shape geometry approximated as a rectangle." +- "Slide 9: some text spacing may differ from PowerPoint." + +### 6.5 Placeholder Visibility in the Layer List + +Every placeholder layer's `name` carries the original PowerPoint object +identifier so the user can match it back to the source deck: + +- Picture from `ppt/media/image3.png` → `Image: image3.png` +- Chart → `Chart placeholder: chart1` +- SmartArt → `SmartArt placeholder: diagram1` +- Table → `Table placeholder: 4×3` +- Video → `Video placeholder: clip1.mp4` + +--- + +## 7. Technical Architecture + +### 7.1 Importer Pipeline + +``` +File input + → ArrayBuffer + → unzip PPTX package + → parse presentation.xml + → resolve slide order and slide size + → parse theme / layout / master data where available + → parse each slide XML + → resolve each slide's relationships + → convert PowerPoint objects to Artstr layers + → create Artstr deck payload + → load deck into Artstr + → show import report +``` + +### 7.2 Zip Library + +Use **fflate** vendored as `src/vendor/fflate.min.js` (~10 KB +minified). It is the smallest browser-compatible zip library with a +straightforward `unzipSync` / `unzip` API. JSZip would be ~10× larger +and is not justified given the single-file-app ethos. Same vendoring +pattern as `src/noble-bundle.min.js`. + +### 7.3 Lazy Module Loading + +PPTX import is rare relative to overall app usage. Loading the +importer + fflate eagerly in `index.html` would inflate first-paint +weight for 90 % of users who never click Import. + +Approach: + +- Extract the importer + the conversion functions into + `src/pptx-importer.js` (a single file, IIFE-exported as + `window.ArtstrPptxImporter`). +- Vendor `fflate.min.js` alongside `noble-bundle.min.js`. +- The Import PPTX button handler dynamically injects both scripts on + first click: + +```js +async function ensurePptxImporterLoaded() { + if (window.ArtstrPptxImporter) return; + await loadScriptOnce('./vendor/fflate.min.js'); + await loadScriptOnce('./pptx-importer.js'); +} +``` + +- Show a small loading indicator next to the button while the scripts + load. On slow connections this is a few hundred ms; on fast it is + imperceptible. + +### 7.4 XML Parsing + +Use native `DOMParser`: + +```js +const parser = new DOMParser(); +const doc = parser.parseFromString(xmlText, 'application/xml'); +``` + +No external XML library. PPTX XML is well-behaved enough for the +browser parser to handle without special namespace gymnastics. + +--- + +## 8. PPTX Package Files Read + +A `.pptx` is a zip package of XML files and media assets. + +MVP files (Phases 1–3): + +``` +ppt/presentation.xml +ppt/_rels/presentation.xml.rels +ppt/slides/slideN.xml +ppt/slides/_rels/slideN.xml.rels +ppt/notesSlides/notesSlideN.xml (Phase 2) +ppt/notesSlides/_rels/notesSlideN.xml.rels +``` + +The `ppt/media/*` files are **not read** — we never decode the actual +image bytes; every picture becomes a placeholder URL. We only read the +relationship metadata to confirm a picture existed and to extract the +original filename for the layer `name`. + +Phase 6 files (theme / layout / master): + +``` +ppt/theme/themeN.xml +ppt/slideLayouts/slideLayoutN.xml +ppt/slideLayouts/_rels/slideLayoutN.xml.rels +ppt/slideMasters/slideMasterN.xml +ppt/slideMasters/_rels/slideMasterN.xml.rels +``` + +--- + +## 9. Coordinate System + +PowerPoint uses EMUs. Artstr slide canvases are pixel-like. + +Normalize all imports to a 1920 × 1080 canvas. + +```js +const EMU_PER_IN = 914400; +const PT_PER_IN = 72; +const EMU_PER_PT = 12700; +``` + +Read slide size from `p:sldSz`: + +```xml + +``` + +Scale + xfrm conversion: + +```js +function makePptxScale(slideSize, targetW = 1920, targetH = 1080) { + return { + x: targetW / slideSize.cx, + y: targetH / slideSize.cy + }; +} + +function convertXfrm(xfrm, scale) { + const off = xfrm.off || { x: 0, y: 0 }; + const ext = xfrm.ext || { cx: 0, cy: 0 }; + return { + x: off.x * scale.x, + y: off.y * scale.y, + w: ext.cx * scale.x, + h: ext.cy * scale.y, + rotate: pptxRotationToDegrees(xfrm.rot || 0) + }; +} + +function pptxRotationToDegrees(rot) { + return (Number(rot) || 0) / 60000; +} +``` + +--- + +## 10. Object Conversion Matrix + +| PowerPoint XML | Meaning | Artstr conversion | MVP? | +| ------------------------ | ------------------ | ------------------------------------ | ------- | +| `p:sp` with `p:txBody` | Text box | `type: 'text'` | Yes | +| `p:sp` with `a:prstGeom` | Preset shape | `type: 'shape'` | Yes | +| `p:pic` | Image | `type: 'image'` with placeholder src | Yes | +| `p:grpSp` | Group | Flatten children with parent xfrm | Yes | +| `a:solidFill` | Solid fill | `fill: { type: 'solid' }` | Yes | +| `a:ln` | Stroke | `stroke: { type: 'solid' }` | Yes | +| `a:gradFill` | Gradient fill | Artstr gradient fill | Phase 6 | +| `a:custGeom` | Custom geometry | Custom path shape | Phase 6 | +| `p:graphicFrame` chart | Chart | Image-layer placeholder | Yes | +| `p:graphicFrame` table | Table | Image-layer placeholder | Yes | +| SmartArt | Diagram | Image-layer placeholder | Yes | +| Video / audio | Media | Image-layer placeholder | Yes | +| Animations | Animation | Ignore | Later | + +--- + +## 11. Layer Mapping Details + +### 11.1 Text Layers + +PowerPoint source: + +```xml + + ... + ... + +``` + +Artstr target: + +```js +{ + id: makeLayerId(), + type: 'text', + name: 'Text', + target: 'canvas', + html: 'Imported text', + x, y, w, h, rotate, opacity, z, + fontFamily, fontSize, color, align, + bold, italic +} +``` + +MVP text behavior: + +- Extract plain text from paragraphs / runs. +- Preserve paragraph breaks as `
` or separate block text. +- Use the first run's dominant style for the whole Artstr text box. +- Preserve rough alignment. +- Preserve bold / italic if dominant. +- Preserve explicit font size and color. + +Per-run rich-text spans (mid-line bold, color changes, etc.) are not +attempted in MVP. The import report warns: "Slide N: some text styling +may have been simplified." + +### 11.2 Image Placeholder Layers + +PowerPoint source: + +```xml + + + + + + + + + ... + + +``` + +We resolve `rId5` through `ppt/slides/_rels/slideN.xml.rels` only to +extract the original media filename (e.g. `media/image3.png`). We do +**not** read the media file itself. + +Artstr target: + +```js +{ + id: makeLayerId(), + type: 'image', + name: 'Image: image3.png', + src: PPTX_IMAGE_PLACEHOLDER_URL, + target: 'canvas', + x, y, w, h, rotate, opacity, z +} +``` + +Where: + +```js +const PPTX_IMAGE_PLACEHOLDER_URL = + 'https://i.postimg.cc/9fY3n60q/example-image-replace-me.png'; +``` + +The placeholder URL is hardcoded. If we ever want to host our own copy +or upload to Blossom later, swapping the constant is a one-line change. + +This is the load-bearing architectural choice for the whole arc: +zero base64 bloat, decks remain Nostr-publishable, the importer never +reads media bytes from the zip package. + +### 11.3 Shape Layers + +PowerPoint source: + +```xml + + + ... + + ... + ... + + +``` + +Artstr target: + +```js +{ + id: makeLayerId(), + type: 'shape', + name: 'Rectangle', + target: 'canvas', + x, y, w, h, rotate, opacity, z, + shape: { kind: 'rect' }, + fill: { type: 'solid', color: '#ffffff' }, + stroke: { type: 'solid', color: '#000000', width: 2, dash: 'solid' } +} +``` + +Preset shape mapping: + +| PPTX `prst` | Artstr shape kind | +| -------------------- | ---------------------- | +| `rect` | `rect` | +| `roundRect` | `rounded-rect` | +| `ellipse` | `ellipse` | +| `triangle` | `triangle` | +| `line` | `line` | +| `straightConnector1` | `line` | +| `star5` | `star` | +| `hexagon` | `polygon` with sides 6 | +| `pentagon` | `polygon` with sides 5 | + +Unsupported presets: + +- MVP: convert to a generic rectangle + warn + `UNSUPPORTED_SHAPE_APPROXIMATED`. +- Phase 6: convert to custom SVG paths via `a:custGeom`. + +### 11.4 Unsupported-Object Placeholder Layers + +Charts (`p:graphicFrame` with chart payload), tables (`p:graphicFrame` +with table payload), SmartArt (`p:graphicFrame` with diagram payload), +and video / audio (`p:pic` with video relationship) all become image +layers pointing at the same placeholder URL, with their `name` +describing the original object: + +```js +{ + id: makeLayerId(), + type: 'image', + name: 'Chart placeholder: chart1', // or 'SmartArt placeholder: …', etc. + src: PPTX_IMAGE_PLACEHOLDER_URL, + target: 'canvas', + x, y, w, h, rotate, opacity, z +} +``` + +This keeps the slide visually populated and editable, and the user can +replace each placeholder with a real image, screenshot, or recreate the +content using Artstr tools. + +### 11.5 Backgrounds + +PowerPoint slide backgrounds may come from the slide, layout, master, +or theme. + +- MVP: read direct slide background only; default to `#ffffff`. +- Phase 6: resolve layout / master inherited background. + +Artstr target: + +```js +slide: { + width: 1920, + height: 1080, + background: '#ffffff', + notes: '' +} +``` + +### 11.6 Notes + +PowerPoint notes live in `ppt/notesSlides/notesSlideN.xml`. The slide's +relationships file points at the corresponding notes slide. + +MVP (Phase 2, alongside text): + +- Follow the slide's relationship to its notes slide. +- Extract plain text from the notes slide's text body. +- Store in `slide.notes`. + +Artstr Presenter Mode already renders `slide.notes`, so this lights up +the feature with no other wiring. + +--- + +## 12. Theme and Color Resolution + +PowerPoint color sources: + +- Direct RGB. +- Scheme colors (`accent1`, `tx1`, `bg1`, etc). +- Preset colors. +- System colors. +- Tint / shade / luminance modifiers. + +MVP color support: + +- `a:srgbClr val="RRGGBB"` → `#RRGGBB`. +- `a:solidFill` with direct colors only. +- Unknown colors → black or transparent depending on context, with a + `THEME_COLOR_UNRESOLVED` warning. + +Phase 6 color support: + +- Parse `ppt/theme/themeN.xml`. +- Resolve `a:schemeClr` against the theme color scheme. +- Apply tint / shade / luminance modifiers approximately. + +```js +function resolvePptxColor(node, context) { + // 1. srgbClr + // 2. schemeClr via theme map (Phase 6) + // 3. presetClr + // 4. sysClr fallback + // 5. default +} +``` + +--- + +## 13. Import Report Data Model + +```js +const report = { + fileName: '', + slideCount: 0, + imported: { + text: 0, + images: 0, // image placeholders + shapes: 0, + groups: 0, + backgrounds: 0, + notes: 0 + }, + placeholders: { + charts: 0, + smartArt: 0, + tables: 0, + media: 0, + unknown: 0 + }, + warnings: [ + { + slideIndex: 3, + code: 'UNSUPPORTED_CHART_PLACEHOLDER', + message: 'Slide 4: chart was imported as a placeholder image.' + } + ] +}; +``` + +Warning codes: + +``` +UNSUPPORTED_CHART_PLACEHOLDER +UNSUPPORTED_SMART_ART_PLACEHOLDER +UNSUPPORTED_TABLE_PLACEHOLDER +UNSUPPORTED_MEDIA_PLACEHOLDER +UNSUPPORTED_ANIMATION_IGNORED +UNSUPPORTED_SHAPE_APPROXIMATED +MISSING_IMAGE_RELATIONSHIP +TEXT_STYLE_APPROXIMATED +THEME_COLOR_UNRESOLVED +LAYOUT_INHERITANCE_PARTIAL +MASTER_INHERITANCE_PARTIAL +``` + +--- + +## 14. Implementation Plan + +### Phase 0: Prep + +- Vendor `fflate.min.js` at `src/vendor/fflate.min.js`. +- Add empty `src/pptx-importer.js` exporting + `window.ArtstrPptxImporter`. +- Wire `loadScriptOnce` helper + `ensurePptxImporterLoaded` in + `index.html`. +- Add Import PPTX button + hidden file input in the Deck Builder UI. +- Add import report modal shell. + +Deliverable: User can click Import PPTX, scripts load, file picker +opens, file extension is validated, importer stub returns "not +implemented yet." + +### Phase 1: Deck Shell + +- Read `.pptx` as ArrayBuffer. +- Unzip with fflate. +- Parse `ppt/presentation.xml` and `ppt/_rels/presentation.xml.rels`. +- Read slide size from `p:sldSz`. +- Resolve slide order. +- Create one Artstr deck slide per PowerPoint slide. +- Default `#ffffff` background. No layers yet. + +Acceptance: Importing a PPTX creates a deck with the correct number of +slides; deck loads in the Deck Builder; each slide is editable. + +### Phase 2: Text + Notes + +- Parse `p:sp` with `p:txBody`. +- Extract text content (paragraphs / runs). +- Extract bounds from `a:xfrm`. +- Convert to text layers with basic style (font size, color, bold, + italic, alignment). +- Follow notes-slide relationship per slide and extract notes text into + `slide.notes`. + +Acceptance: Text appears at roughly correct positions, editable in +Artstr; multi-line text imports with line breaks; speaker notes appear +in Presenter Mode. + +### Phase 3: Image Placeholders + Unsupported-Object Placeholders + +- Parse `p:pic`. +- Read the embed-relationship to extract the original media filename + (for the layer `name`); do **not** read media bytes. +- Create image layers with `src = PPTX_IMAGE_PLACEHOLDER_URL`. +- Detect `p:graphicFrame` chart / table / SmartArt and create + placeholder image layers with descriptive names. +- Detect video / audio in `p:pic` (via relationship type) and create + placeholder image layers. +- Emit appropriate warnings. + +Acceptance: Image-bearing decks import with placeholder layers at +correct positions; chart / SmartArt / table / video objects show as +labeled placeholders; nothing silently disappears. + +### Phase 4: Basic Shape Import + +- Parse `p:sp` with `a:prstGeom` and no text body. +- Map common preset shapes (rect, roundRect, ellipse, triangle, line, + star5, hexagon, pentagon, straightConnector1). +- Read solid fills and strokes. +- Preserve opacity and rotation. + +Acceptance: Basic preset shapes import as editable shape layers; fill +and stroke colors roughly match the PowerPoint file. + +### Phase 5: Group Flattening + +- Parse `p:grpSp` recursively. +- Apply parent transform to child transforms. +- Flatten children into normal Artstr layers. + +Acceptance: Objects inside PowerPoint groups appear in the right +approximate position; the import does not need to preserve grouping +semantics. + +### Phase 6: Theme / Layout / Master Improvements + +- Parse slide-layout and slide-master relationships. +- Parse theme XML. +- Resolve theme colors (`a:schemeClr`). +- Resolve inherited slide backgrounds. + +Acceptance: Decks that lean on theme colors import with closer visual +fidelity; theme colors resolve to real hex. + +### Phase 7: Polish + +- Tune the import report wording. +- Improve text-style fidelity (per-run spans if Artstr's rich-text + layer can support them cleanly). +- Add a per-slide warning summary in the report modal. + +--- + +## 15. Function Layout + +All importer code lives in `src/pptx-importer.js`, exposed as +`window.ArtstrPptxImporter` (mirrors the noble-bundle pattern). + +```js +window.ArtstrPptxImporter = (function () { + async function importPptxFile(file) {} + async function readPptxPackage(file) {} + function parseXml(xmlText) {} + function readPresentationInfo(zip, parser, report) {} + function readPresentationRels(zip, parser) {} + function readSlideRefsInOrder(presDoc, presRelsDoc) {} + function readSlideSize(presDoc) {} + async function convertPptxSlide(zip, slideRef, context) {} + function convertSlideShape(spNode, context) {} + function convertTextShape(spNode, context) {} + function convertPicture(picNode, context) {} + function convertPresetShape(spNode, context) {} + function convertGroupShape(grpSpNode, context) {} + function convertGraphicFrame(gfNode, context) {} // chart / table / smart-art + function readXfrm(node) {} + function readFill(node, context) {} + function readStroke(node, context) {} + function resolvePptxColor(node, context) {} + function resolveRelationship(relsDoc, rId) {} + function pptxTargetToZipPath(basePath, target) {} + function makePptxImportReport(fileName) {} + + return { importPptxFile, makePptxImportReport }; +})(); +``` + +The report-modal render lives in `index.html` (uses existing modal +styling), called as `showPptxImportReport(report)`. + +--- + +## 16. Pseudocode + +```js +async function onImportPptxClick() { + await ensurePptxImporterLoaded(); + document.getElementById('pptxFileInput').click(); +} + +async function onPptxFileChosen(file) { + if (!file || !/\.pptx$/i.test(file.name)) { + toastError('Please choose a .pptx file.'); + return; + } + if (hasUnsavedWork() && !(await confirmReplaceProject())) return; + + const report = ArtstrPptxImporter.makePptxImportReport(file.name); + try { + const payload = await ArtstrPptxImporter.importPptxFile(file, report); + loadProjectFromText(JSON.stringify(payload)); + showPptxImportReport(report); + } catch (err) { + console.error(err); + toastError(`Could not import PPTX: ${err?.message || err}`); + } +} +``` + +`importPptxFile` returns the Artstr deck payload (does not call +`loadProjectFromText` itself, so it is easier to unit-test). + +--- + +## 17. Security and Privacy + +The importer runs locally in the browser. The PPTX file is never +uploaded. + +- Do not execute embedded scripts. +- Do not follow external relationships. +- Do not fetch external media. (Media is never decoded at all; only + filenames are read for layer naming.) +- Catch parser errors and fail gracefully. +- Sanitize imported text before assigning to `html` (use the same + sanitizer Artstr's text layer already uses on paste). + +Suggested limits: + +```js +const PPTX_MAX_FILE_BYTES = 100 * 1024 * 1024; +const PPTX_MAX_SLIDES_WARN = 100; +``` + +There is no max data-URL warning (no data URLs are produced). + +--- + +## 18. Performance + +- Show progress text while importing (`Slide 12 / 48…`). +- Yield to the browser between slides with + `await new Promise(requestAnimationFrame)`. +- XML parsing of a typical 50-slide deck is fast (sub-second on + desktop). A Web Worker is not necessary for MVP. + +Memory footprint is small because no media bytes are decoded. + +--- + +## 19. Testing Plan + +### 19.1 Test Decks + +Build a small library of `.pptx` files under `test/pptx/`: + +1. Blank 16:9 deck. +2. Text-only deck. +3. Text with bold / italic / colors / alignment. +4. Basic shapes (rect, rounded rect, ellipse, line, triangle). +5. Image-heavy deck (PNG + JPEG). +6. Grouped objects. +7. Theme colors and master background. +8. Speaker notes. +9. Unsupported content (chart, SmartArt, table, video). +10. Large deck (50+ slides). + +### 19.2 Unit-ish Tests + +Build a debug harness page (`test/pptx/harness.html`) that runs parser +functions against known XML strings. + +Helpers to test in isolation: + +- `readSlideSize` +- `resolveRelationship` +- `pptxTargetToZipPath` +- `readXfrm` +- `resolvePptxColor` +- `convertTextShape` +- `convertPresetShape` +- `convertPicture` (verify placeholder URL + filename in name) +- `convertGraphicFrame` (verify placeholder for chart / table / + SmartArt) + +### 19.3 Manual Acceptance + +For each test deck: + +- Import completes without crashing. +- Correct slide count. +- Layers appear in expected z-order. +- Text is editable. +- Image / chart / SmartArt / table / video placeholders appear at + correct positions. +- Basic shapes are editable. +- Save JSON and reload round-trips. +- Presenter Mode works (and shows notes). +- Import report accurately describes skipped content. + +--- + +## 20. Acceptance Criteria for MVP Release + +- A user can select a `.pptx` file from the browser. +- Artstr creates a new `templateMode: 'deck'` project. +- Slide count matches the PowerPoint file. +- Slide dimensions normalize to Artstr's 16:9 canvas. +- Text boxes import as editable text layers (with notes). +- Images import as placeholder image layers pointing at + `PPTX_IMAGE_PLACEHOLDER_URL`, with the original media filename in the + layer name. +- Charts / SmartArt / tables / video import as placeholder image + layers with descriptive names. +- Basic rectangles, ellipses, lines, and triangles import as editable + shape layers. +- Solid fills and strokes are imported where explicit. +- Rotation, opacity, z-order, and rough position are preserved. +- Unsupported objects produce warnings; nothing silently disappears. +- Imported deck saves as Artstr JSON and reloads cleanly. +- Imported deck opens in Presenter Mode. +- Imported deck publishes to Nostr without size warnings (because no + base64 bloat). + +--- + +## 21. Publishing Considerations + +Imported decks are normal Artstr deck payloads with remote image URLs. +They publish to Nostr like any other deck. No special warnings, no +externalization step, no size guard. + +The placeholder URL is a third-party host (postimg.cc). Trade-offs: + +- If postimg.cc goes away, every placeholder layer 404s. The deck + itself still loads; the placeholder image just doesn't render. +- We may want to host our own copy long-term. Swapping + `PPTX_IMAGE_PLACEHOLDER_URL` is a one-line change; old imported + decks keep their existing URL until the user edits the layer. + +--- + +## 22. Main Risks + +### Risk: PowerPoint fidelity expectations are too high + +Mitigation: ship copy that frames this as "Import editable PPTX," not +"Perfect PowerPoint renderer." Import report makes approximations and +placeholders visible. + +### Risk: Theme / master inheritance is complex + +Mitigation: deferred to Phase 6. MVP reads direct slide objects only. + +### Risk: Placeholder URL host disappears + +Mitigation: noted in §21. The constant is trivial to swap. Long-term +we may self-host or upload to Blossom. + +### Risk: Text reflow differences + +Mitigation: preserve box size and explicit font size. Warn that some +text layout may differ. Per-run rich-text is a Phase 7 enhancement. + +### Risk: Single-file code size grows too much + +Mitigation: importer lives in its own file (`src/pptx-importer.js`) +and is lazy-loaded. The main `index.html` bundle does not grow. + +--- + +## 23. First PR Scope (Phase 0 + Phase 1) + +- Vendor `fflate.min.js`. +- Stand up `src/pptx-importer.js` with the IIFE shell. +- Add `loadScriptOnce` + `ensurePptxImporterLoaded` in `index.html`. +- Add Import PPTX button, hidden file input, and report-modal shell in + the Deck Builder UI. +- Implement the deck-shell import: file → unzip → presentation.xml → + slide order → empty slides. +- Show a minimal import report (slide count only). + +Do not include text, images, shapes, themes, or notes in the first PR. + +This gives a working vertical slice and proves the architecture. + +--- + +## 24. Second PR Scope (Phase 2) + +- Text-box import with basic style. +- Speaker notes import. +- Real warnings populating the report. + +--- + +## 25. Third PR Scope (Phase 3 + Phase 4) + +- Image placeholder layers (with original filename in `name`). +- Chart / SmartArt / table / video placeholder layers. +- Basic shape import with fill + stroke. + +--- + +## 26. Long-Term Enhancements + +- Per-run rich-text spans for higher text fidelity. +- Convert unsupported preset shapes to custom SVG paths + (`a:custGeom`). +- Self-hosted placeholder image (or rotate through several variants + per object type). +- Optional "Replace all placeholders…" panel that lists every + placeholder layer in the deck and lets the user paste URLs in batch. +- Side-by-side visual diff against a rasterized reference render. +- PPTX **export**: Artstr deck → `.pptx` package. A separate later + arc. + +--- + +## 27. Final Recommendation + +Build this as a phased editable importer, not a PowerPoint renderer. +The placeholder strategy is what makes the architecture clean: no +embedded binaries, no externalization prereq, no publish-size guard, +and Artstr's "remote URLs only" image model is preserved end to end. + +The product promise: + +> "Import a PowerPoint deck into Artstr as editable slides. Images and +> a few advanced PowerPoint object types come in as placeholders — +> open each layer and paste your own image URL to restore them."