Files
Artstr-Studio/docs/PPTX_IMPORT_FEATURE.md

30 KiB
Raw Permalink Blame History

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

{
  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).

<button type="button" id="importPptxBtn">Import PPTX</button>
<input
  id="pptxFileInput"
  type="file"
  accept=".pptx,application/vnd.openxmlformats-officedocument.presentationml.presentation"
  hidden
/>

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:
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:

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.

const EMU_PER_IN = 914400;
const PT_PER_IN  = 72;
const EMU_PER_PT = 12700;

Read slide size from p:sldSz:

<p:sldSz cx="12192000" cy="6858000" type="wide"/>

Scale + xfrm conversion:

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:

<p:sp>
  <p:spPr>...</p:spPr>
  <p:txBody>...</p:txBody>
</p:sp>

Artstr target:

{
  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 <br> 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:

<p:pic>
  <p:nvPicPr>
    <p:cNvPr id="..." name="..."/>
  </p:nvPicPr>
  <p:blipFill>
    <a:blip r:embed="rId5" />
  </p:blipFill>
  <p:spPr>
    <a:xfrm>...</a:xfrm>
  </p:spPr>
</p:pic>

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:

{
  id: makeLayerId(),
  type: 'image',
  name: 'Image: image3.png',
  src: PPTX_IMAGE_PLACEHOLDER_URL,
  target: 'canvas',
  x, y, w, h, rotate, opacity, z
}

Where:

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:

<p:sp>
  <p:spPr>
    <a:xfrm>...</a:xfrm>
    <a:prstGeom prst="rect" />
    <a:solidFill>...</a:solidFill>
    <a:ln>...</a:ln>
  </p:spPr>
</p:sp>

Artstr target:

{
  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:

{
  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:

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.
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

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 — SHIPPED on pptx-import (commit d710b84)

  • 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."

Status: shipped. Phase 1+ paused while we work on a purchase-vault fix on main. Branch pptx-import retains the Phase 0 commit so work can resume here without setup.

Phase 1: Deck Shell — SHIPPED on pptx-import (commit 2cc2b85)

  • 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.

Status: shipped. Verified via CDP smoke test against synthetic 3-slide and 5-slide .pptx files built in-browser with fflate.zipSync. Garbage bytes throw a clean "invalid zip data" error.

Phase 2: Text + Notes — SHIPPED on pptx-import (commit 49d6697)

  • 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 — SHIPPED on pptx-import (commit 3237a69)

  • 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 — SHIPPED on pptx-import (commit 3def41b)

  • 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.

Status: shipped (intentionally landed before Phase 2 / Phase 3 because the user's test deck is a design-template set, heavy on backgrounds + shapes and light on text). Verified against gallery_template_layout_set.pptx (16 slides → 64 shape layers with correct source colors and positions, zero warnings).

Phase 5: Group Flattening — SHIPPED on pptx-import (commit 6a731d5)

  • 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 — SHIPPED on pptx-import (commit a448afc)

  • 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).

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

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:

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."