` 等几何容器上。`box-shadow` 的精确可编辑子集仅为单层 outer shadow、非 inset、zero spread;多层、inset、blur+spread 或其他不支持形态必须 blocking。converter 可将 legacy hard ring(`0 0 0 Npx`)重写为同心可编辑 shape,但 authoring 不得依赖该 rewrite。
-- 文字标签不得有 background、border、border-radius 或 box-shadow;`text-shadow` 任何非 `none` 形态都必须 blocking,禁止静默丢失。
+- 背景、边框和圆角只放在 `
` 等几何容器上。`box-shadow` 的精确可编辑子集为单层 outer shadow、非 inset、zero spread 的原生映射;多层 shadow 只取首个可用层,负 spread 按 0 近似,inset 等其余不支持形态导出时自动移除。converter 可将 legacy hard ring(`0 0 0 Npx`)重写为同心可编辑 shape,但 authoring 不得依赖该 rewrite。
+- 文字标签不得有 background、border、border-radius 或 box-shadow;`text-shadow` 任何非 `none` 形态在导出时一律自动移除,不得依赖其呈现层次。
- `background-image` 在任何元素上都禁止。
- 数据条、色带和热力档位用多个离散纯色几何元素表达。
diff --git a/src/crates/contracts/product-domains/src/miniapp/builtin.rs b/src/crates/contracts/product-domains/src/miniapp/builtin.rs
index 156212f46..51ffac357 100644
--- a/src/crates/contracts/product-domains/src/miniapp/builtin.rs
+++ b/src/crates/contracts/product-domains/src/miniapp/builtin.rs
@@ -151,7 +151,7 @@ pub const BUILTIN_APPS: &[BuiltinMiniAppBundle] = &[
},
BuiltinMiniAppBundle {
id: "builtin-ppt-live",
- version: 231,
+ version: 232,
meta_json: include_str!("builtin/assets/ppt-live/meta.json"),
html: include_str!("builtin/assets/ppt-live/index.html"),
css: include_str!("builtin/assets/ppt-live/style.css"),
diff --git a/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/README.md b/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/README.md
index be8b6b36a..d1de8bc3f 100644
--- a/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/README.md
+++ b/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/README.md
@@ -21,7 +21,7 @@ ppt-live/
├── export-deck-host.js # 导出函数的 re-export 壳(ui.js 通过它引入导出能力)
├── export-deck-browser.js # EditableSlideScene/PDF/PNG 导出实现
├── export-slide-browser.js # 幻灯片预处理编排(挂载 DOM → sanitize → scene)
- ├── preflight-slide-audit.js # sanitizer 前可见元素、CSS paint/text-shadow fail-close
+ ├── export-degrade.js # 导出降级层(剥样式 / 移除元素 / 简化页面,代替阻断)
├── editable-slide-normalize.js # HTML/SVG/table → EditableSlideScene
├── editable-slide-scene.js # 可编辑场景契约、校验与结构化错误
├── html2pptx-dom-core.js # DOM 几何与原生元素提取辅助
@@ -109,8 +109,11 @@ PPT Live 只有一条 PPTX 导出管线:HTML 幻灯片和旧 element-model 幻
映射为 PowerPoint 原生文本、形状、线、表格和有明确用户图片意图的 picture。
`EditableSlideScene` 不接受栅格兜底、整页截图、SVG 图片层或降级成功状态。可确定重写的
-CSS/SVG 构造会转换为原生节点;无法表示的输入会携带页码、source id 和错误码阻止导出。
-表格通过 `addTable` 写为原生 `a:tbl`,生成的几何不得产生 `p:pic` 或媒体关系。
+CSS/SVG 构造会转换为原生节点;其余无法表示的输入由导出降级层(`export-degrade.js`)处理:
+不支持的样式(box-shadow、text-shadow、filter、mask、background-image、动画等)被剥离,
+无法表示的元素被移除,仍失败的页面被替换为简化可编辑场景——所有降级都会记录在导出摘要中,
+单个元素或单页问题不再阻断整个导出。表格通过 `addTable` 写为原生 `a:tbl`,生成的几何不得产生
+`p:pic` 或媒体关系。
DOM 几何来自 `getBoundingClientRect()`(border-box),以 `px / 96` 转换为英寸。
CSS padding 会成为文本框或表格单元格 inset;垂直对齐由可表示的布局属性推导。
@@ -130,10 +133,14 @@ prepareEditableSlides(slides, options)
→ loadHtmlInExportRoot(html) // 挂载到离屏 shadow-DOM div (1280×720)
→ sanitizeSlideDocumentRoot(doc) // 净化 HTML
→ waitForExportPaint() // 等待两帧渲染
- → normalizeDocumentToEditableScene(doc)
+ → normalizeWithDegradation(...) // 严格 normalize + 有界降级修复循环
+ → buildSimplifiedEditableScene() // 单页最终兜底(保留页数与文字)
→ 返回 EditableSlideScene[]
```
+`options.onDegrade(record)` 逐条收集降级记录(剥样式 / 移除元素 / 简化页面),
+由 `summarizePptxExportDiagnostics(scenes, degradations)` 合并进导出摘要展示给用户。
+
旧 element-model 页面通过 `normalizeElementSlideToEditableScene` 进入同一 scene 契约。
`exportEditablePptx(deck, scenes)` 逐页调用唯一 serializer 生成最终 PPTX;流程为顺序执行,
不插入人工延时。
diff --git a/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/bundle.json b/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/bundle.json
index 21d2ce818..b34c4c06f 100644
--- a/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/bundle.json
+++ b/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/bundle.json
@@ -1,5 +1,5 @@
{
"schemaVersion": 1,
"id": "builtin-ppt-live",
- "version": 231
+ "version": 232
}
diff --git a/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/dist/ui.bundle.js b/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/dist/ui.bundle.js
index 32a434f5e..4537c4be4 100644
--- a/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/dist/ui.bundle.js
+++ b/src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/dist/ui.bundle.js
@@ -30,9 +30,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
mod
));
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/utils/common.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/utils/common.js
var require_common = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/utils/common.js"(exports) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/utils/common.js"(exports) {
"use strict";
var TYPED_OK = typeof Uint8Array !== "undefined" && typeof Uint16Array !== "undefined" && typeof Int32Array !== "undefined";
function _has(obj, key) {
@@ -121,9 +121,9 @@ var require_common = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/trees.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/trees.js
var require_trees = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/trees.js"(exports) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/trees.js"(exports) {
"use strict";
var utils = require_common();
var Z_FIXED = 4;
@@ -763,9 +763,9 @@ var require_trees = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/adler32.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/adler32.js
var require_adler32 = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/adler32.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/adler32.js"(exports, module) {
"use strict";
function adler32(adler, buf, len, pos) {
var s1 = adler & 65535 | 0, s2 = adler >>> 16 & 65535 | 0, n = 0;
@@ -785,9 +785,9 @@ var require_adler32 = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/crc32.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/crc32.js
var require_crc32 = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/crc32.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/crc32.js"(exports, module) {
"use strict";
function makeTable() {
var c, table = [];
@@ -813,9 +813,9 @@ var require_crc32 = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/messages.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/messages.js
var require_messages = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/messages.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/messages.js"(exports, module) {
"use strict";
module.exports = {
2: "need dictionary",
@@ -840,9 +840,9 @@ var require_messages = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/deflate.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/deflate.js
var require_deflate = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/deflate.js"(exports) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/deflate.js"(exports) {
"use strict";
var utils = require_common();
var trees = require_trees();
@@ -1889,9 +1889,9 @@ var require_deflate = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/utils/strings.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/utils/strings.js
var require_strings = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/utils/strings.js"(exports) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/utils/strings.js"(exports) {
"use strict";
var utils = require_common();
var STR_APPLY_OK = true;
@@ -2031,9 +2031,9 @@ var require_strings = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/zstream.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/zstream.js
var require_zstream = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/zstream.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/zstream.js"(exports, module) {
"use strict";
function ZStream() {
this.input = null;
@@ -2053,9 +2053,9 @@ var require_zstream = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/deflate.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/deflate.js
var require_deflate2 = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/deflate.js"(exports) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/deflate.js"(exports) {
"use strict";
var zlib_deflate = require_deflate();
var utils = require_common();
@@ -2214,9 +2214,9 @@ var require_deflate2 = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inffast.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inffast.js
var require_inffast = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inffast.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inffast.js"(exports, module) {
"use strict";
var BAD = 30;
var TYPE = 12;
@@ -2443,9 +2443,9 @@ var require_inffast = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inftrees.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inftrees.js
var require_inftrees = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inftrees.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inftrees.js"(exports, module) {
"use strict";
var utils = require_common();
var MAXBITS = 15;
@@ -2759,9 +2759,9 @@ var require_inftrees = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inflate.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inflate.js
var require_inflate = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inflate.js"(exports) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/inflate.js"(exports) {
"use strict";
var utils = require_common();
var adler32 = require_adler32();
@@ -3998,9 +3998,9 @@ var require_inflate = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/constants.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/constants.js
var require_constants = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/constants.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/constants.js"(exports, module) {
"use strict";
module.exports = {
/* Allowed flush values; see deflate() and inflate() below for details */
@@ -4045,9 +4045,9 @@ var require_constants = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/gzheader.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/gzheader.js
var require_gzheader = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/gzheader.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/zlib/gzheader.js"(exports, module) {
"use strict";
function GZheader() {
this.text = 0;
@@ -4065,9 +4065,9 @@ var require_gzheader = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/inflate.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/inflate.js
var require_inflate2 = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/inflate.js"(exports) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/lib/inflate.js"(exports) {
"use strict";
var zlib_inflate = require_inflate();
var utils = require_common();
@@ -4239,9 +4239,9 @@ var require_inflate2 = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/index.js
+// node_modules/.pnpm/pako@1.0.11/node_modules/pako/index.js
var require_pako = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/pako@1.0.11/node_modules/pako/index.js"(exports, module) {
+ "node_modules/.pnpm/pako@1.0.11/node_modules/pako/index.js"(exports, module) {
"use strict";
var assign = require_common().assign;
var deflate = require_deflate2();
@@ -4253,9 +4253,9 @@ var require_pako = __commonJS({
}
});
-// ../../../../../../../../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js
+// node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js
var require_jszip_min = __commonJS({
- "../../../../../../../../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js"(exports, module) {
+ "node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/dist/jszip.min.js"(exports, module) {
!(function(e) {
if ("object" == typeof exports && "undefined" != typeof module) module.exports = e();
else if ("function" == typeof define && define.amd) define([], e);
@@ -6631,7 +6631,7 @@ var require_node_https = __commonJS({
}
});
-// src/i18n.js
+// src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/src/i18n.js
var STRINGS = {
"en-US": {
eyebrow: "AI Deck Studio",
@@ -6894,9 +6894,11 @@ var STRINGS = {
exportPptxFailed: "PPTX export failed:",
exportDiagnosticsSummary: "Exported with visual adjustments: {{counts}}. {{locations}}",
exportDiagnosticsRepaired: "{{count}} automatic repairs",
+ exportDiagnosticsDegraded: "{{count}} degraded",
exportDiagnosticsBlocking: "{{count}} blocking failures",
exportDiagnosticsLocation: "Slide {{slide}}, element {{source}} ({{phase}}): {{reason}}",
exportDiagnosticsPhaseRepair: "automatic repair",
+ exportDiagnosticsPhaseDegraded: "degraded",
exportDiagnosticsPhaseBlocking: "blocking failure",
exportPdfWorking: "Rendering PDF...",
exportPdfDone: "PDF downloaded.",
@@ -7276,9 +7278,11 @@ var STRINGS = {
exportPptxFailed: "PPTX \u5BFC\u51FA\u5931\u8D25\uFF1A",
exportDiagnosticsSummary: "\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u5E76\u8FDB\u884C\u4E86\u89C6\u89C9\u8C03\u6574\uFF1A{{counts}}\u3002{{locations}}",
exportDiagnosticsRepaired: "\u81EA\u52A8\u4FEE\u590D {{count}} \u5904",
+ exportDiagnosticsDegraded: "\u964D\u7EA7\u5904\u7406 {{count}} \u5904",
exportDiagnosticsBlocking: "\u963B\u65AD\u5931\u8D25 {{count}} \u5904",
exportDiagnosticsLocation: "\u7B2C {{slide}} \u9875\uFF0C\u5143\u7D20 {{source}}\uFF08{{phase}}\uFF09\uFF1A{{reason}}",
exportDiagnosticsPhaseRepair: "\u81EA\u52A8\u4FEE\u590D",
+ exportDiagnosticsPhaseDegraded: "\u964D\u7EA7\u5904\u7406",
exportDiagnosticsPhaseBlocking: "\u963B\u65AD\u5931\u8D25",
exportPdfWorking: "\u6B63\u5728\u6E32\u67D3 PDF...",
exportPdfDone: "PDF \u5DF2\u4E0B\u8F7D\u3002",
@@ -7413,7 +7417,7 @@ function translate(key, params = {}) {
return value2;
}
-// src/style-presets.js
+// src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/src/style-presets.js
var STYLE_PRESET_UI_KEYS = ["clean-business", "insight-report"];
var STYLE_PRESETS = {
// === 商务/极简 ===
@@ -7680,7 +7684,7 @@ function getAllStylePresets(locale) {
});
}
-// src/state.js
+// src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/src/state.js
var STORAGE_KEY = "pptLiveStudioStateV6";
var HISTORY_KEY = "pptLiveDeckHistoryV1";
var SCHEMA_VERSION = 6;
@@ -8421,7 +8425,7 @@ function pointsFor(title, index, state2) {
return picks;
}
-// src/flat-select.js
+// src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/src/flat-select.js
var OPEN_SELECTS = /* @__PURE__ */ new Set();
function closeSelect(wrap) {
const menu = wrap.querySelector(".ppt-flat-select__menu");
@@ -8602,7 +8606,7 @@ if (typeof window !== "undefined" && typeof document !== "undefined" && !window.
document.addEventListener("scroll", handleOutsideScroll, true);
}
-// src/element-model-html.js
+// src/crates/contracts/product-domains/src/miniapp/builtin/assets/ppt-live/src/element-model-html.js
var escapeHtml2 = (value2) => String(value2 ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
var DEFAULT_THEME = {
background: "#ffffff",
@@ -8745,7 +8749,7 @@ function buildElementSlideHtml(slide = {}) {