From 32af7f0e96a3e8140e883fa57b5b31c1fb658f09 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 29 May 2026 14:55:21 -0400 Subject: [PATCH] PPTX import P1.5: slide backgrounds (direct solid fills) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quick win on top of Phase 1: pull each slide's direct background color out of p:bg/p:bgPr/a:solidFill/a:srgbClr and set it on the Artstr slide instead of always leaving the default white. Scoped narrowly: - Direct slide backgrounds only. p:bgRef (layout/master inheritance) emits LAYOUT_INHERITANCE_PARTIAL and stays white — Phase 6. - solidFill + srgbClr only. Gradient, picture, and scheme-color fills emit THEME_COLOR_UNRESOLVED and stay white — Phase 6. - Parse failures on a per-slide basis emit SLIDE_PARSE_FAILED and fall through to white; the deck shell still imports. Counts successful backgrounds in report.imported.backgrounds so the import report can show "16 backgrounds imported". readSlideBackground is exposed via _internals for future-phase tests. Verified end-to-end against the user's gallery_template_layout_set deck (16 slides, design-template set heavy on background colors): all 16 slides now import with their actual cream / themed colors (e.g. slide 1 = #fbf8f0) instead of plain white, with zero warnings. Synthetic-pptx smoke test (no p:bg) still produces white slides. --- src/pptx-importer.js | 77 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/pptx-importer.js b/src/pptx-importer.js index f5c338c..4df0b4e 100644 --- a/src/pptx-importer.js +++ b/src/pptx-importer.js @@ -158,6 +158,58 @@ window.ArtstrPptxImporter = (function () { return refs; } + // ---- Slide-level parsing --------------------------------------------- + // Read the slide's direct background. PPTX backgrounds can come from + // four places (slide, layout, master, theme). This handler covers only + // the direct slide background with a single solidFill — the common case + // for design-template decks. Layout / master / theme inheritance and + // gradient / picture fills are Phase 6. + // + // Returns either a CSS hex string like '#fbf8f0' or null if nothing + // usable was found (caller leaves slide.background at the default). + function readSlideBackground(slideDoc, slideIndex, report) { + const bg = slideDoc.getElementsByTagName('p:bg')[0] + || slideDoc.getElementsByTagNameNS('*', 'bg')[0]; + if (!bg) return null; + + // Direct background properties live in . means the + // slide inherits from its layout/master via a theme scheme — defer + // to Phase 6 with a warning. + const bgRef = bg.getElementsByTagName('p:bgRef')[0] + || bg.getElementsByTagNameNS('*', 'bgRef')[0]; + if (bgRef) { + _warn(report, slideIndex, 'LAYOUT_INHERITANCE_PARTIAL', + `Slide ${slideIndex + 1}: background inherited from layout/master — not resolved in Phase 1.`); + return null; + } + + const bgPr = bg.getElementsByTagName('p:bgPr')[0] + || bg.getElementsByTagNameNS('*', 'bgPr')[0]; + if (!bgPr) return null; + + // Only solidFill + srgbClr in Phase 1. gradFill, blipFill (picture), + // pattFill, and schemeClr-based fills land in Phase 6 with the rest + // of theme resolution. + const solid = bgPr.getElementsByTagName('a:solidFill')[0] + || bgPr.getElementsByTagNameNS('*', 'solidFill')[0]; + if (!solid) { + _warn(report, slideIndex, 'THEME_COLOR_UNRESOLVED', + `Slide ${slideIndex + 1}: non-solid background fill (gradient / picture / scheme color) — left as default for now.`); + return null; + } + + const srgb = solid.getElementsByTagName('a:srgbClr')[0] + || solid.getElementsByTagNameNS('*', 'srgbClr')[0]; + if (!srgb) { + _warn(report, slideIndex, 'THEME_COLOR_UNRESOLVED', + `Slide ${slideIndex + 1}: background uses a scheme color — theme resolution is Phase 6.`); + return null; + } + const hex = (srgb.getAttribute('val') || '').trim().toLowerCase(); + if (!/^[0-9a-f]{6}$/.test(hex)) return null; + return '#' + hex; + } + function readPresentationInfo(files, report) { const presText = readZipText(files, 'ppt/presentation.xml'); if (!presText) { @@ -197,12 +249,34 @@ window.ArtstrPptxImporter = (function () { if (i && i % 10 === 0) { await new Promise((r) => requestAnimationFrame(r)); } + + // Try to read the slide's direct background. Anything we can't + // resolve (inherited, gradient, scheme color) leaves the default + // white — the user can edit it in the Deck Builder. Read failures + // are non-fatal: the slide still gets a shell. + let background = '#ffffff'; + const slidePath = presentation.slideRefs[i].path; + const slideText = slidePath ? readZipText(files, slidePath) : null; + if (slideText) { + try { + const slideDoc = parseXml(slideText); + const bg = readSlideBackground(slideDoc, i, report); + if (bg) { + background = bg; + report.imported.backgrounds += 1; + } + } catch (err) { + _warn(report, i, 'SLIDE_PARSE_FAILED', + `Slide ${i + 1}: could not parse slide XML — left blank.`); + } + } + slides.push({ name: 'Slide ' + (i + 1), slide: { width: TARGET_W, height: TARGET_H, - background: '#ffffff', + background, notes: '', }, layers: [], @@ -245,6 +319,7 @@ window.ArtstrPptxImporter = (function () { readSlideSize, readSlideRefsInOrder, readPresentationInfo, + readSlideBackground, }, }; })();