@json-to-office/core-pptx 1.4.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2235,99 +2235,6 @@ function applyFontWeight(params) {
2235
2235
  return { fontFace: synth.family, bold: synth.bold, italic: synth.italic };
2236
2236
  }
2237
2237
 
2238
- // src/utils/componentDefaults.ts
2239
- import { mergeWithDefaults } from "@json-to-office/shared";
2240
- function getComponentDefaults(theme) {
2241
- return theme.componentDefaults || {};
2242
- }
2243
- function getTextDefaults(theme) {
2244
- return getComponentDefaults(theme).text || {};
2245
- }
2246
- function getImageDefaults(theme) {
2247
- return getComponentDefaults(theme).image || {};
2248
- }
2249
- function getShapeDefaults(theme) {
2250
- return getComponentDefaults(theme).shape || {};
2251
- }
2252
- function getTableDefaults(theme) {
2253
- return getComponentDefaults(theme).table || {};
2254
- }
2255
- function getHighchartsDefaults(theme) {
2256
- return getComponentDefaults(theme).highcharts || {};
2257
- }
2258
- function getChartDefaults(theme) {
2259
- return getComponentDefaults(theme).chart || {};
2260
- }
2261
- function getCustomComponentDefaults(theme, componentName) {
2262
- const defaults = getComponentDefaults(theme);
2263
- return defaults?.[componentName] || {};
2264
- }
2265
- function resolveTextProps(props, theme) {
2266
- return mergeWithDefaults(props, getTextDefaults(theme));
2267
- }
2268
- function resolveImageProps(props, theme) {
2269
- return mergeWithDefaults(props, getImageDefaults(theme));
2270
- }
2271
- function resolveShapeProps(props, theme) {
2272
- return mergeWithDefaults(props, getShapeDefaults(theme));
2273
- }
2274
- function resolveTableProps(props, theme) {
2275
- return mergeWithDefaults(props, getTableDefaults(theme));
2276
- }
2277
- function resolveHighchartsProps(props, theme) {
2278
- return mergeWithDefaults(props, getHighchartsDefaults(theme));
2279
- }
2280
- function resolveChartProps(props, theme) {
2281
- return mergeWithDefaults(props, getChartDefaults(theme));
2282
- }
2283
- function resolveCustomComponentProps(props, theme, componentName) {
2284
- const defaults = getCustomComponentDefaults(theme, componentName);
2285
- return mergeWithDefaults(props, defaults);
2286
- }
2287
- var TYPE_GETTERS = {
2288
- text: getTextDefaults,
2289
- image: getImageDefaults,
2290
- shape: getShapeDefaults,
2291
- table: getTableDefaults,
2292
- highcharts: getHighchartsDefaults,
2293
- chart: getChartDefaults
2294
- };
2295
- function getDefaultsForType(componentName, theme) {
2296
- const getter = TYPE_GETTERS[componentName];
2297
- return getter ? getter(theme) : getCustomComponentDefaults(theme, componentName);
2298
- }
2299
-
2300
- // src/utils/resolveComponentTree.ts
2301
- var RESOLVER_MAP = {
2302
- text: resolveTextProps,
2303
- image: resolveImageProps,
2304
- shape: resolveShapeProps,
2305
- table: resolveTableProps,
2306
- highcharts: resolveHighchartsProps,
2307
- chart: resolveChartProps
2308
- };
2309
- function resolveComponentDefaults(component, theme) {
2310
- const resolver = RESOLVER_MAP[component.name];
2311
- const resolvedProps = resolver ? resolver(component.props, theme) : resolveCustomComponentProps(
2312
- component.props,
2313
- theme,
2314
- component.name
2315
- );
2316
- return { ...component, props: resolvedProps };
2317
- }
2318
- function resolveComponentTree(components, theme) {
2319
- return components.map((component) => {
2320
- const resolved = resolveComponentDefaults(component, theme);
2321
- if (resolved.children && resolved.children.length > 0) {
2322
- return {
2323
- ...resolved,
2324
- children: resolveComponentTree(resolved.children, theme)
2325
- };
2326
- }
2327
- return resolved;
2328
- });
2329
- }
2330
-
2331
2238
  // src/utils/imageSource.ts
2332
2239
  import path from "path";
2333
2240
  var hasValue = (v) => typeof v === "string" && v.trim().length > 0;
@@ -2400,7 +2307,8 @@ var DEFAULT_GRID_CONFIG = {
2400
2307
  };
2401
2308
  function resolveMargin(margin) {
2402
2309
  if (margin == null) return DEFAULT_GRID_CONFIG.margin;
2403
- if (typeof margin === "number") return { top: margin, right: margin, bottom: margin, left: margin };
2310
+ if (typeof margin === "number")
2311
+ return { top: margin, right: margin, bottom: margin, left: margin };
2404
2312
  return margin;
2405
2313
  }
2406
2314
  function resolveGutter(gutter) {
@@ -2464,24 +2372,198 @@ function resolveGridPosition(gridPos, gridConfig, slideWidth, slideHeight, warni
2464
2372
  function resolveComponentGridPosition(component, gridConfig, slideWidth, slideHeight, warnings) {
2465
2373
  const gridPos = component.props.grid;
2466
2374
  if (!gridPos) return component;
2467
- const resolved = resolveGridPosition(gridPos, gridConfig, slideWidth, slideHeight, warnings);
2375
+ const resolved = resolveGridPosition(
2376
+ gridPos,
2377
+ gridConfig,
2378
+ slideWidth,
2379
+ slideHeight,
2380
+ warnings
2381
+ );
2468
2382
  const { grid: _grid, ...restProps } = component.props;
2469
2383
  const newProps = { ...restProps };
2470
2384
  const hasPercentX = typeof newProps.x === "string" || typeof newProps.w === "string";
2471
2385
  const hasPercentY = typeof newProps.y === "string" || typeof newProps.h === "string";
2472
2386
  const toPercX = (v) => `${+(v / slideWidth * 100).toFixed(2)}%`;
2473
2387
  const toPercY = (v) => `${+(v / slideHeight * 100).toFixed(2)}%`;
2474
- if (newProps.x == null) newProps.x = hasPercentX ? toPercX(resolved.x) : resolved.x;
2475
- if (newProps.y == null) newProps.y = hasPercentY ? toPercY(resolved.y) : resolved.y;
2476
- if (newProps.w == null) newProps.w = hasPercentX ? toPercX(resolved.w) : resolved.w;
2477
- if (newProps.h == null) newProps.h = hasPercentY ? toPercY(resolved.h) : resolved.h;
2388
+ if (newProps.x == null)
2389
+ newProps.x = hasPercentX ? toPercX(resolved.x) : resolved.x;
2390
+ if (newProps.y == null)
2391
+ newProps.y = hasPercentY ? toPercY(resolved.y) : resolved.y;
2392
+ if (newProps.w == null)
2393
+ newProps.w = hasPercentX ? toPercX(resolved.w) : resolved.w;
2394
+ if (newProps.h == null)
2395
+ newProps.h = hasPercentY ? toPercY(resolved.h) : resolved.h;
2478
2396
  return { ...component, props: newProps };
2479
2397
  }
2480
2398
 
2481
2399
  // src/ir/compiler.ts
2482
2400
  init_warn();
2401
+
2402
+ // src/core/placeholders.ts
2483
2403
  import { mergeWithDefaults as mergeWithDefaults2 } from "@json-to-office/shared";
2484
2404
 
2405
+ // src/utils/componentDefaults.ts
2406
+ import { mergeWithDefaults } from "@json-to-office/shared";
2407
+ function getComponentDefaults(theme) {
2408
+ return theme.componentDefaults || {};
2409
+ }
2410
+ function getTextDefaults(theme) {
2411
+ return getComponentDefaults(theme).text || {};
2412
+ }
2413
+ function getImageDefaults(theme) {
2414
+ return getComponentDefaults(theme).image || {};
2415
+ }
2416
+ function getShapeDefaults(theme) {
2417
+ return getComponentDefaults(theme).shape || {};
2418
+ }
2419
+ function getTableDefaults(theme) {
2420
+ return getComponentDefaults(theme).table || {};
2421
+ }
2422
+ function getHighchartsDefaults(theme) {
2423
+ return getComponentDefaults(theme).highcharts || {};
2424
+ }
2425
+ function getChartDefaults(theme) {
2426
+ return getComponentDefaults(theme).chart || {};
2427
+ }
2428
+ function getCustomComponentDefaults(theme, componentName) {
2429
+ const defaults = getComponentDefaults(theme);
2430
+ return defaults?.[componentName] || {};
2431
+ }
2432
+ function resolveTextProps(props, theme) {
2433
+ return mergeWithDefaults(props, getTextDefaults(theme));
2434
+ }
2435
+ function resolveImageProps(props, theme) {
2436
+ return mergeWithDefaults(props, getImageDefaults(theme));
2437
+ }
2438
+ function resolveShapeProps(props, theme) {
2439
+ return mergeWithDefaults(props, getShapeDefaults(theme));
2440
+ }
2441
+ function resolveTableProps(props, theme) {
2442
+ return mergeWithDefaults(props, getTableDefaults(theme));
2443
+ }
2444
+ function resolveHighchartsProps(props, theme) {
2445
+ return mergeWithDefaults(props, getHighchartsDefaults(theme));
2446
+ }
2447
+ function resolveChartProps(props, theme) {
2448
+ return mergeWithDefaults(props, getChartDefaults(theme));
2449
+ }
2450
+ function resolveCustomComponentProps(props, theme, componentName) {
2451
+ const defaults = getCustomComponentDefaults(theme, componentName);
2452
+ return mergeWithDefaults(props, defaults);
2453
+ }
2454
+ var TYPE_GETTERS = {
2455
+ text: getTextDefaults,
2456
+ image: getImageDefaults,
2457
+ shape: getShapeDefaults,
2458
+ table: getTableDefaults,
2459
+ highcharts: getHighchartsDefaults,
2460
+ chart: getChartDefaults
2461
+ };
2462
+ function getDefaultsForType(componentName, theme) {
2463
+ const getter = TYPE_GETTERS[componentName];
2464
+ return getter ? getter(theme) : getCustomComponentDefaults(theme, componentName);
2465
+ }
2466
+
2467
+ // src/utils/resolveComponentTree.ts
2468
+ var RESOLVER_MAP = {
2469
+ text: resolveTextProps,
2470
+ image: resolveImageProps,
2471
+ shape: resolveShapeProps,
2472
+ table: resolveTableProps,
2473
+ highcharts: resolveHighchartsProps,
2474
+ chart: resolveChartProps
2475
+ };
2476
+ function resolveComponentDefaults(component, theme) {
2477
+ const resolver = RESOLVER_MAP[component.name];
2478
+ const resolvedProps = resolver ? resolver(component.props, theme) : resolveCustomComponentProps(
2479
+ component.props,
2480
+ theme,
2481
+ component.name
2482
+ );
2483
+ return { ...component, props: resolvedProps };
2484
+ }
2485
+ function resolveComponentTree(components, theme) {
2486
+ return components.map((component) => {
2487
+ const resolved = resolveComponentDefaults(component, theme);
2488
+ if (resolved.children && resolved.children.length > 0) {
2489
+ return {
2490
+ ...resolved,
2491
+ children: resolveComponentTree(resolved.children, theme)
2492
+ };
2493
+ }
2494
+ return resolved;
2495
+ });
2496
+ }
2497
+
2498
+ // src/core/placeholders.ts
2499
+ init_warn();
2500
+ function resolvePlaceholderComponents(slide, template, effectiveGrid, options) {
2501
+ if (!slide.placeholders) return [];
2502
+ const out = [];
2503
+ if (!template) {
2504
+ for (const [name, component] of Object.entries(slide.placeholders)) {
2505
+ const defaulted = resolveComponentDefaults(component, options.theme);
2506
+ const positioned = defaulted.props.x != null || defaulted.props.y != null || defaulted.props.grid;
2507
+ if (!positioned) {
2508
+ warn(
2509
+ options.warnings,
2510
+ W.PLACEHOLDER_NO_POSITION,
2511
+ `Placeholder "${name}" has no template and no explicit position \u2014 skipped`,
2512
+ { slide: options.slideIndex }
2513
+ );
2514
+ continue;
2515
+ }
2516
+ out.push({
2517
+ name,
2518
+ component: resolveComponentGridPosition(
2519
+ defaulted,
2520
+ effectiveGrid,
2521
+ options.slideWidth,
2522
+ options.slideHeight,
2523
+ options.warnings
2524
+ )
2525
+ });
2526
+ }
2527
+ return out;
2528
+ }
2529
+ const declared = new Map(
2530
+ (template.placeholders ?? []).map((placeholder) => [
2531
+ placeholder.name,
2532
+ placeholder
2533
+ ])
2534
+ );
2535
+ for (const [name, component] of Object.entries(slide.placeholders)) {
2536
+ const definition = declared.get(name);
2537
+ if (!definition) {
2538
+ warn(
2539
+ options.warnings,
2540
+ W.UNKNOWN_PLACEHOLDER,
2541
+ `Unknown placeholder "${name}" in template "${slide.template}". Available: ${[...declared.keys()].join(", ")}`,
2542
+ { slide: options.slideIndex }
2543
+ );
2544
+ continue;
2545
+ }
2546
+ const gridResolved = resolveComponentGridPosition(
2547
+ component,
2548
+ effectiveGrid,
2549
+ options.slideWidth,
2550
+ options.slideHeight,
2551
+ options.warnings
2552
+ );
2553
+ const typeDefaults = getDefaultsForType(component.name, options.theme);
2554
+ const positionDefaults = {};
2555
+ if (definition.x != null) positionDefaults.x = definition.x;
2556
+ if (definition.y != null) positionDefaults.y = definition.y;
2557
+ if (definition.w != null) positionDefaults.w = definition.w;
2558
+ if (definition.h != null) positionDefaults.h = definition.h;
2559
+ let props = mergeWithDefaults2(positionDefaults, typeDefaults);
2560
+ props = mergeWithDefaults2(definition.defaults?.props ?? {}, props);
2561
+ props = mergeWithDefaults2(gridResolved.props, props);
2562
+ out.push({ name, component: { ...gridResolved, props } });
2563
+ }
2564
+ return out;
2565
+ }
2566
+
2485
2567
  // src/ir/resources.ts
2486
2568
  init_units();
2487
2569
  var ResourceTable = class {
@@ -2759,12 +2841,17 @@ function compileSlide(slide, slideIndex, processed, ctx) {
2759
2841
  })
2760
2842
  );
2761
2843
  }
2762
- for (const component of compilePlaceholderComponents(
2844
+ for (const { component } of resolvePlaceholderComponents(
2763
2845
  slide,
2764
2846
  template,
2765
2847
  effectiveGrid,
2766
- slideIndex,
2767
- ctx
2848
+ {
2849
+ theme: ctx.theme,
2850
+ slideWidth: ctx.slideWidthInches,
2851
+ slideHeight: ctx.slideHeightInches,
2852
+ slideIndex,
2853
+ warnings: ctx.warnings
2854
+ }
2768
2855
  )) {
2769
2856
  push(
2770
2857
  compileComponent(component, {
@@ -2825,71 +2912,6 @@ function slideContextFor(slideIndex, processed) {
2825
2912
  language: processed.language
2826
2913
  };
2827
2914
  }
2828
- function compilePlaceholderComponents(slide, template, effectiveGrid, slideIndex, ctx) {
2829
- if (!slide.placeholders) return [];
2830
- const out = [];
2831
- if (!template) {
2832
- for (const [name, component] of Object.entries(slide.placeholders)) {
2833
- const defaulted = resolveComponentDefaults(component, ctx.theme);
2834
- const positioned = defaulted.props.x != null || defaulted.props.y != null || defaulted.props.grid;
2835
- if (!positioned) {
2836
- warn(
2837
- ctx.warnings,
2838
- W.PLACEHOLDER_NO_POSITION,
2839
- `Placeholder "${name}" has no template and no explicit position \u2014 skipped`,
2840
- { slide: slideIndex }
2841
- );
2842
- continue;
2843
- }
2844
- out.push(
2845
- resolveComponentGridPosition(
2846
- defaulted,
2847
- effectiveGrid,
2848
- ctx.slideWidthInches,
2849
- ctx.slideHeightInches,
2850
- ctx.warnings
2851
- )
2852
- );
2853
- }
2854
- return out;
2855
- }
2856
- const declared = new Map(
2857
- (template.placeholders ?? []).map((placeholder) => [
2858
- placeholder.name,
2859
- placeholder
2860
- ])
2861
- );
2862
- for (const [name, component] of Object.entries(slide.placeholders)) {
2863
- const definition = declared.get(name);
2864
- if (!definition) {
2865
- warn(
2866
- ctx.warnings,
2867
- W.UNKNOWN_PLACEHOLDER,
2868
- `Unknown placeholder "${name}" in template "${slide.template}". Available: ${[...declared.keys()].join(", ")}`,
2869
- { slide: slideIndex }
2870
- );
2871
- continue;
2872
- }
2873
- const gridResolved = resolveComponentGridPosition(
2874
- component,
2875
- effectiveGrid,
2876
- ctx.slideWidthInches,
2877
- ctx.slideHeightInches,
2878
- ctx.warnings
2879
- );
2880
- const typeDefaults = getDefaultsForType(component.name, ctx.theme);
2881
- const positionDefaults = {};
2882
- if (definition.x != null) positionDefaults.x = definition.x;
2883
- if (definition.y != null) positionDefaults.y = definition.y;
2884
- if (definition.w != null) positionDefaults.w = definition.w;
2885
- if (definition.h != null) positionDefaults.h = definition.h;
2886
- let props = mergeWithDefaults2(positionDefaults, typeDefaults);
2887
- props = mergeWithDefaults2(definition.defaults?.props ?? {}, props);
2888
- props = mergeWithDefaults2(gridResolved.props, props);
2889
- out.push({ ...gridResolved, props });
2890
- }
2891
- return out;
2892
- }
2893
2915
  function compileComponent(component, scope) {
2894
2916
  if (component.enabled === false) return void 0;
2895
2917
  switch (component.name) {
@@ -4124,141 +4146,6 @@ async function resolveDocumentFonts(document, theme, warnings, fonts) {
4124
4146
  return resolved;
4125
4147
  }
4126
4148
 
4127
- // src/core/generationContext.ts
4128
- import { applyExportMode } from "@json-to-office/shared";
4129
-
4130
- // src/themes/defaults.ts
4131
- var DEFAULT_TABLE = {
4132
- border: { type: "solid", pt: 1, color: "background2" },
4133
- headerRow: true
4134
- };
4135
- var DEFAULT_STYLES = {
4136
- title: { fontSize: 36, bold: true, fontColor: "text", align: "center" },
4137
- subtitle: { fontSize: 20, italic: true, fontColor: "text2", align: "center" },
4138
- heading1: { fontSize: 28, bold: true, fontColor: "primary" },
4139
- heading2: { fontSize: 22, bold: true, fontColor: "primary" },
4140
- heading3: { fontSize: 18, bold: true, fontColor: "text" },
4141
- body: { fontSize: 14 },
4142
- caption: { fontSize: 10, italic: true, fontColor: "text2" }
4143
- };
4144
- var DEFAULT_PPTX_THEME = {
4145
- name: "default",
4146
- colors: {
4147
- primary: "#4472C4",
4148
- secondary: "#ED7D31",
4149
- accent: "#70AD47",
4150
- background: "#FFFFFF",
4151
- text: "#333333",
4152
- text2: "#44546A",
4153
- background2: "#E7E6E6",
4154
- accent4: "#FFC000",
4155
- accent5: "#5B9BD5",
4156
- accent6: "#70AD47"
4157
- },
4158
- fonts: {
4159
- heading: "Arial",
4160
- body: "Arial"
4161
- },
4162
- defaults: {
4163
- fontSize: 18,
4164
- fontColor: "#333333"
4165
- },
4166
- styles: DEFAULT_STYLES,
4167
- componentDefaults: { table: DEFAULT_TABLE }
4168
- };
4169
- var PPTX_THEMES = {
4170
- default: DEFAULT_PPTX_THEME,
4171
- dark: {
4172
- name: "dark",
4173
- colors: {
4174
- primary: "#5B9BD5",
4175
- secondary: "#FF6F61",
4176
- accent: "#6BCB77",
4177
- background: "#2D2D2D",
4178
- text: "#FFFFFF",
4179
- text2: "#CCCCCC",
4180
- background2: "#3D3D3D",
4181
- accent4: "#FFB347",
4182
- accent5: "#77DD77",
4183
- accent6: "#AEC6CF"
4184
- },
4185
- fonts: {
4186
- heading: "Arial",
4187
- body: "Arial"
4188
- },
4189
- defaults: {
4190
- fontSize: 18,
4191
- fontColor: "#FFFFFF"
4192
- },
4193
- styles: DEFAULT_STYLES,
4194
- componentDefaults: { table: DEFAULT_TABLE }
4195
- },
4196
- minimal: {
4197
- name: "minimal",
4198
- colors: {
4199
- primary: "#000000",
4200
- secondary: "#666666",
4201
- accent: "#999999",
4202
- background: "#FFFFFF",
4203
- text: "#000000",
4204
- text2: "#444444",
4205
- background2: "#F5F5F5",
4206
- accent4: "#BBBBBB",
4207
- accent5: "#DDDDDD",
4208
- accent6: "#888888"
4209
- },
4210
- fonts: {
4211
- heading: "Helvetica",
4212
- body: "Helvetica"
4213
- },
4214
- defaults: {
4215
- fontSize: 18,
4216
- fontColor: "#000000"
4217
- },
4218
- styles: DEFAULT_STYLES,
4219
- componentDefaults: { table: DEFAULT_TABLE }
4220
- }
4221
- };
4222
- function getPptxTheme(name) {
4223
- return PPTX_THEMES[name] || DEFAULT_PPTX_THEME;
4224
- }
4225
- function hasPptxTheme(name) {
4226
- return Object.prototype.hasOwnProperty.call(PPTX_THEMES, name);
4227
- }
4228
- var pptxThemes = PPTX_THEMES;
4229
-
4230
- // src/core/generationContext.ts
4231
- function resolveThemeContext(documentIn, options = {}) {
4232
- const { customThemes, fonts, warnings, defaultThemeName, resolveNamedTheme } = options;
4233
- if (documentIn.props === null) {
4234
- throw new Error(
4235
- "Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
4236
- );
4237
- }
4238
- let document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
4239
- let inlineTheme;
4240
- if (typeof document.props.theme === "object" && document.props.theme !== null) {
4241
- inlineTheme = document.props.theme;
4242
- }
4243
- const authoredThemeName = typeof document.props.theme === "string" ? document.props.theme : void 0;
4244
- const baseThemeName = inlineTheme ? inlineTheme.name || "inline-theme" : authoredThemeName ?? defaultThemeName ?? "default";
4245
- let theme = inlineTheme ?? (resolveNamedTheme ? resolveNamedTheme(baseThemeName, authoredThemeName !== void 0) : customThemes?.[baseThemeName] ?? getPptxTheme(baseThemeName));
4246
- const mode = applyExportMode({ doc: document, theme, fonts });
4247
- document = mode.doc;
4248
- theme = mode.theme;
4249
- for (const w of mode.warnings) {
4250
- warnings?.push({
4251
- code: w.code,
4252
- message: w.message,
4253
- component: "fontRegistry"
4254
- });
4255
- }
4256
- return {
4257
- document,
4258
- theme
4259
- };
4260
- }
4261
-
4262
4149
  // src/core/generationOptions.ts
4263
4150
  import {
4264
4151
  collectImageSourceConflicts,
@@ -4618,6 +4505,144 @@ async function probeIntrinsicSize(source, warnings) {
4618
4505
  }
4619
4506
  }
4620
4507
 
4508
+ // src/quality/facts.ts
4509
+ import { DEFAULT_PPTX_RENDERER_ID as DEFAULT_PPTX_RENDERER_ID2 } from "@json-to-office/shared-pptx";
4510
+
4511
+ // src/core/generationContext.ts
4512
+ import { applyExportMode } from "@json-to-office/shared";
4513
+
4514
+ // src/themes/defaults.ts
4515
+ var DEFAULT_TABLE = {
4516
+ border: { type: "solid", pt: 1, color: "background2" },
4517
+ headerRow: true
4518
+ };
4519
+ var DEFAULT_STYLES = {
4520
+ title: { fontSize: 36, bold: true, fontColor: "text", align: "center" },
4521
+ subtitle: { fontSize: 20, italic: true, fontColor: "text2", align: "center" },
4522
+ heading1: { fontSize: 28, bold: true, fontColor: "primary" },
4523
+ heading2: { fontSize: 22, bold: true, fontColor: "primary" },
4524
+ heading3: { fontSize: 18, bold: true, fontColor: "text" },
4525
+ body: { fontSize: 14 },
4526
+ caption: { fontSize: 10, italic: true, fontColor: "text2" }
4527
+ };
4528
+ var DEFAULT_PPTX_THEME = {
4529
+ name: "default",
4530
+ colors: {
4531
+ primary: "#4472C4",
4532
+ secondary: "#ED7D31",
4533
+ accent: "#70AD47",
4534
+ background: "#FFFFFF",
4535
+ text: "#333333",
4536
+ text2: "#44546A",
4537
+ background2: "#E7E6E6",
4538
+ accent4: "#FFC000",
4539
+ accent5: "#5B9BD5",
4540
+ accent6: "#70AD47"
4541
+ },
4542
+ fonts: {
4543
+ heading: "Arial",
4544
+ body: "Arial"
4545
+ },
4546
+ defaults: {
4547
+ fontSize: 18,
4548
+ fontColor: "#333333"
4549
+ },
4550
+ styles: DEFAULT_STYLES,
4551
+ componentDefaults: { table: DEFAULT_TABLE }
4552
+ };
4553
+ var PPTX_THEMES = {
4554
+ default: DEFAULT_PPTX_THEME,
4555
+ dark: {
4556
+ name: "dark",
4557
+ colors: {
4558
+ primary: "#5B9BD5",
4559
+ secondary: "#FF6F61",
4560
+ accent: "#6BCB77",
4561
+ background: "#2D2D2D",
4562
+ text: "#FFFFFF",
4563
+ text2: "#CCCCCC",
4564
+ background2: "#3D3D3D",
4565
+ accent4: "#FFB347",
4566
+ accent5: "#77DD77",
4567
+ accent6: "#AEC6CF"
4568
+ },
4569
+ fonts: {
4570
+ heading: "Arial",
4571
+ body: "Arial"
4572
+ },
4573
+ defaults: {
4574
+ fontSize: 18,
4575
+ fontColor: "#FFFFFF"
4576
+ },
4577
+ styles: DEFAULT_STYLES,
4578
+ componentDefaults: { table: DEFAULT_TABLE }
4579
+ },
4580
+ minimal: {
4581
+ name: "minimal",
4582
+ colors: {
4583
+ primary: "#000000",
4584
+ secondary: "#666666",
4585
+ accent: "#999999",
4586
+ background: "#FFFFFF",
4587
+ text: "#000000",
4588
+ text2: "#444444",
4589
+ background2: "#F5F5F5",
4590
+ accent4: "#BBBBBB",
4591
+ accent5: "#DDDDDD",
4592
+ accent6: "#888888"
4593
+ },
4594
+ fonts: {
4595
+ heading: "Helvetica",
4596
+ body: "Helvetica"
4597
+ },
4598
+ defaults: {
4599
+ fontSize: 18,
4600
+ fontColor: "#000000"
4601
+ },
4602
+ styles: DEFAULT_STYLES,
4603
+ componentDefaults: { table: DEFAULT_TABLE }
4604
+ }
4605
+ };
4606
+ function getPptxTheme(name) {
4607
+ return PPTX_THEMES[name] || DEFAULT_PPTX_THEME;
4608
+ }
4609
+ function hasPptxTheme(name) {
4610
+ return Object.prototype.hasOwnProperty.call(PPTX_THEMES, name);
4611
+ }
4612
+ var pptxThemes = PPTX_THEMES;
4613
+
4614
+ // src/core/generationContext.ts
4615
+ function resolveThemeContext(documentIn, options = {}) {
4616
+ const { customThemes, fonts, warnings, defaultThemeName, resolveNamedTheme } = options;
4617
+ if (documentIn.props === null) {
4618
+ throw new Error(
4619
+ "Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
4620
+ );
4621
+ }
4622
+ let document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
4623
+ let inlineTheme;
4624
+ if (typeof document.props.theme === "object" && document.props.theme !== null) {
4625
+ inlineTheme = document.props.theme;
4626
+ }
4627
+ const authoredThemeName = typeof document.props.theme === "string" ? document.props.theme : void 0;
4628
+ const baseThemeName = inlineTheme ? inlineTheme.name || "inline-theme" : authoredThemeName ?? defaultThemeName ?? "default";
4629
+ let theme = inlineTheme ?? (resolveNamedTheme ? resolveNamedTheme(baseThemeName, authoredThemeName !== void 0) : customThemes?.[baseThemeName] ?? getPptxTheme(baseThemeName));
4630
+ const mode = applyExportMode({ doc: document, theme, fonts });
4631
+ document = mode.doc;
4632
+ theme = mode.theme;
4633
+ for (const w of mode.warnings) {
4634
+ warnings?.push({
4635
+ code: w.code,
4636
+ message: w.message,
4637
+ component: "fontRegistry"
4638
+ });
4639
+ }
4640
+ return {
4641
+ document,
4642
+ theme
4643
+ };
4644
+ }
4645
+
4621
4646
  // src/core/structure.ts
4622
4647
  import { mergeWithDefaults as mergeWithDefaults3 } from "@json-to-office/shared";
4623
4648
  function isSlideEnabled(child) {
@@ -4740,6 +4765,458 @@ function processPresentation(document, options) {
4740
4765
  };
4741
4766
  }
4742
4767
 
4768
+ // src/quality/facts.ts
4769
+ function asRecord(value) {
4770
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4771
+ }
4772
+ function asNumber(value) {
4773
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4774
+ }
4775
+ function dimToPt(value, axisIn) {
4776
+ if (typeof value === "number" && Number.isFinite(value)) return value * 72;
4777
+ if (typeof value === "string") {
4778
+ const trimmed = value.trim();
4779
+ if (trimmed.endsWith("%")) {
4780
+ const pct = Number(trimmed.slice(0, -1));
4781
+ return Number.isFinite(pct) ? pct / 100 * axisIn * 72 : void 0;
4782
+ }
4783
+ const inches = Number(trimmed);
4784
+ return Number.isFinite(inches) ? inches * 72 : void 0;
4785
+ }
4786
+ return void 0;
4787
+ }
4788
+ function resolveBox(props, grid, slideWidthIn, slideHeightIn) {
4789
+ let xPt = dimToPt(props.x, slideWidthIn);
4790
+ let yPt = dimToPt(props.y, slideHeightIn);
4791
+ let widthPt = dimToPt(props.w, slideWidthIn);
4792
+ let heightPt = dimToPt(props.h, slideHeightIn);
4793
+ const gridPos = asRecord(props.grid);
4794
+ if (gridPos !== void 0 && asNumber(gridPos.column) !== void 0 && asNumber(gridPos.row) !== void 0 && (xPt === void 0 || yPt === void 0 || widthPt === void 0 || heightPt === void 0)) {
4795
+ const resolved = resolveGridPosition(
4796
+ gridPos,
4797
+ grid,
4798
+ slideWidthIn,
4799
+ slideHeightIn
4800
+ );
4801
+ xPt ??= resolved.x * 72;
4802
+ yPt ??= resolved.y * 72;
4803
+ widthPt ??= resolved.w * 72;
4804
+ heightPt ??= resolved.h * 72;
4805
+ }
4806
+ return { xPt, yPt, widthPt, heightPt };
4807
+ }
4808
+ function isCompleteBox(box) {
4809
+ return box.xPt !== void 0 && box.yPt !== void 0 && box.widthPt !== void 0 && box.heightPt !== void 0 && box.widthPt > 0 && box.heightPt > 0;
4810
+ }
4811
+ function containsCentre(surface, text) {
4812
+ const cx = text.xPt + text.widthPt / 2;
4813
+ const cy = text.yPt + text.heightPt / 2;
4814
+ return cx >= surface.xPt && cx <= surface.xPt + surface.widthPt && cy >= surface.yPt && cy <= surface.yPt + surface.heightPt;
4815
+ }
4816
+ function themeContext(theme) {
4817
+ return {
4818
+ styles: theme.styles ?? {},
4819
+ defaultFontSize: asNumber(theme.defaults?.fontSize) ?? 18
4820
+ };
4821
+ }
4822
+ function fillColorHexes(fill2, theme) {
4823
+ const found = [];
4824
+ const visit = (node) => {
4825
+ if (Array.isArray(node)) {
4826
+ node.forEach(visit);
4827
+ return;
4828
+ }
4829
+ const rec = asRecord(node);
4830
+ if (!rec) return;
4831
+ if (typeof rec.color === "string") {
4832
+ const hex = resolveColor(rec.color, theme);
4833
+ if (hex) found.push(hex.toUpperCase());
4834
+ }
4835
+ for (const value of Object.values(rec)) {
4836
+ if (typeof value === "object" && value !== null) visit(value);
4837
+ }
4838
+ };
4839
+ visit(fill2);
4840
+ return [...new Set(found)];
4841
+ }
4842
+ var FOCUS_CORNERS = {
4843
+ topLeft: [0, 0],
4844
+ topRight: [1, 0],
4845
+ bottomLeft: [0, 1],
4846
+ bottomRight: [1, 1]
4847
+ };
4848
+ function lerpHex(a, b, t) {
4849
+ const pa = parseInt(a, 16);
4850
+ const pb = parseInt(b, 16);
4851
+ const mix = (shift) => Math.round(
4852
+ (pa >> shift & 255) + ((pb >> shift & 255) - (pa >> shift & 255)) * t
4853
+ );
4854
+ return [mix(16), mix(8), mix(0)].map((c) => c.toString(16).padStart(2, "0")).join("").toUpperCase();
4855
+ }
4856
+ function gradientPosition(gradient, fx, fy, widthUnits, heightUnits) {
4857
+ if (gradient.type === "radial") {
4858
+ const focus = FOCUS_CORNERS[typeof gradient.focus === "string" ? gradient.focus : "topLeft"] ?? FOCUS_CORNERS.topLeft;
4859
+ const radius = Math.hypot(widthUnits, heightUnits) / 2;
4860
+ if (radius === 0) return 0;
4861
+ const distance = Math.hypot(
4862
+ (fx - focus[0]) * widthUnits,
4863
+ (fy - focus[1]) * heightUnits
4864
+ );
4865
+ return Math.min(1, distance / radius);
4866
+ }
4867
+ const angle = (asNumber(gradient.angle) ?? 0) * Math.PI / 180;
4868
+ const dx = Math.cos(angle);
4869
+ const dy = Math.sin(angle);
4870
+ const min = Math.min(0, dx) + Math.min(0, dy);
4871
+ const max = Math.max(0, dx) + Math.max(0, dy);
4872
+ if (max === min) return 0;
4873
+ return Math.min(1, Math.max(0, (fx * dx + fy * dy - min) / (max - min)));
4874
+ }
4875
+ function paintedColorHexes(fill2, theme, fx, fy, widthUnits, heightUnits) {
4876
+ const rec = asRecord(fill2);
4877
+ const gradient = asRecord(rec?.gradient);
4878
+ const stops = Array.isArray(gradient?.stops) ? gradient.stops : void 0;
4879
+ if (gradient && stops && stops.length > 0) {
4880
+ const parsed = stops.flatMap((stop) => {
4881
+ const entry = asRecord(stop);
4882
+ const color2 = typeof entry?.color === "string" ? entry.color : void 0;
4883
+ if (!color2) return [];
4884
+ const hex = resolveColor(color2, theme);
4885
+ if (!hex) return [];
4886
+ return [{ pos: asNumber(entry?.pos) ?? 0, hex: hex.toUpperCase() }];
4887
+ }).sort((a, b) => a.pos - b.pos);
4888
+ if (parsed.length === 0) return [];
4889
+ const target = gradientPosition(gradient, fx, fy, widthUnits, heightUnits) * 100;
4890
+ if (target <= parsed[0].pos) return [parsed[0].hex];
4891
+ const last = parsed[parsed.length - 1];
4892
+ if (target >= last.pos) return [last.hex];
4893
+ for (let i = 1; i < parsed.length; i += 1) {
4894
+ const previous = parsed[i - 1];
4895
+ const next = parsed[i];
4896
+ if (target <= next.pos) {
4897
+ const span = next.pos - previous.pos;
4898
+ const t = span === 0 ? 0 : (target - previous.pos) / span;
4899
+ return [lerpHex(previous.hex, next.hex, t)];
4900
+ }
4901
+ }
4902
+ return [last.hex];
4903
+ }
4904
+ return fillColorHexes(fill2, theme);
4905
+ }
4906
+ function defaultLineHeightPt(fontSize) {
4907
+ if (fontSize >= 60) return fontSize * 1.05;
4908
+ if (fontSize >= 28) return fontSize * 1.15;
4909
+ return fontSize * 1.25;
4910
+ }
4911
+ function resolveTypography(props, ctx) {
4912
+ const styleName = typeof props.style === "string" ? props.style : void 0;
4913
+ const style = styleName !== void 0 ? ctx.styles[styleName] : void 0;
4914
+ const fontSize = asNumber(props.fontSize) ?? style?.fontSize ?? ctx.defaultFontSize;
4915
+ const multiple = asNumber(props.lineSpacingMultiple);
4916
+ const lineSpacing = multiple !== void 0 ? fontSize * multiple : asNumber(props.lineSpacing) ?? style?.lineSpacing ?? defaultLineHeightPt(fontSize);
4917
+ const weight = asNumber(props.fontWeight) ?? style?.fontWeight;
4918
+ const bold = weight !== void 0 ? weight >= 600 : typeof props.bold === "boolean" ? props.bold : style?.bold ?? false;
4919
+ return {
4920
+ fontSize,
4921
+ lineSpacing,
4922
+ paraSpaceBefore: asNumber(props.paraSpaceBefore) ?? 0,
4923
+ paraSpaceAfter: asNumber(props.paraSpaceAfter) ?? style?.paraSpaceAfter ?? 0,
4924
+ bold,
4925
+ ...styleName && { styleName }
4926
+ };
4927
+ }
4928
+ function collectSlideNodes(component, path4, text, surfaces, counter) {
4929
+ const rec = asRecord(component);
4930
+ if (!rec || rec.enabled === false) return;
4931
+ const props = asRecord(rec.props) ?? {};
4932
+ const order = counter.next++;
4933
+ if (rec.name === "image" || rec.name === "visual" || rec.name === "chart") {
4934
+ surfaces.push({ order, props, isImage: true });
4935
+ } else if (rec.name === "shape" && props.fill !== void 0) {
4936
+ surfaces.push({ order, props, isImage: false });
4937
+ }
4938
+ const content = typeof props.text === "string" ? props.text : void 0;
4939
+ if (content !== void 0 && content.trim() !== "" && props.runs === void 0) {
4940
+ if (rec.name === "text" || rec.name === "shape") {
4941
+ text.push({ props, path: path4, text: content, order });
4942
+ }
4943
+ }
4944
+ const children = Array.isArray(rec.children) ? rec.children : [];
4945
+ children.forEach(
4946
+ (child, index) => collectSlideNodes(
4947
+ child,
4948
+ `${path4}/children/${index}`,
4949
+ text,
4950
+ surfaces,
4951
+ counter
4952
+ )
4953
+ );
4954
+ }
4955
+ function pointerSegment(value) {
4956
+ return value.replace(/~/g, "~0").replace(/\//g, "~1");
4957
+ }
4958
+ function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slideHeightIn, ctx, theme, slideBackground, analyzedTextPaths, addFact) {
4959
+ const nodes = [];
4960
+ const surfaces = [];
4961
+ const counter = { next: 0 };
4962
+ for (const root of roots) {
4963
+ collectSlideNodes(root.component, root.path, nodes, surfaces, counter);
4964
+ }
4965
+ const surfaceBoxes = surfaces.flatMap((surface) => {
4966
+ const box = resolveBox(surface.props, grid, slideWidthIn, slideHeightIn);
4967
+ return isCompleteBox(box) ? [{ ...surface, box }] : [];
4968
+ });
4969
+ let bodyWords = 0;
4970
+ nodes.forEach((node, nodeIndex) => {
4971
+ const typography = resolveTypography(node.props, ctx);
4972
+ if (typography.styleName !== "title" && typography.styleName !== "subtitle") {
4973
+ bodyWords += node.text.split(/\s+/).filter(Boolean).length;
4974
+ }
4975
+ if (analyzedTextPaths.has(node.path)) return;
4976
+ analyzedTextPaths.add(node.path);
4977
+ const gridPos = asRecord(node.props.grid);
4978
+ const nodeBox = resolveBox(node.props, grid, slideWidthIn, slideHeightIn);
4979
+ const {
4980
+ xPt: boxXPt,
4981
+ yPt: boxYPt,
4982
+ widthPt: boxWidthPt,
4983
+ heightPt: boxHeightPt
4984
+ } = nodeBox;
4985
+ const sampleFractions = (box, originX, originY, widthPt, heightPt) => {
4986
+ if (!isCompleteBox(box) || widthPt <= 0 || heightPt <= 0) {
4987
+ return [[0.5, 0.5]];
4988
+ }
4989
+ const x0 = (box.xPt - originX) / widthPt;
4990
+ const x1 = (box.xPt + box.widthPt - originX) / widthPt;
4991
+ const y0 = (box.yPt - originY) / heightPt;
4992
+ const y1 = (box.yPt + box.heightPt - originY) / heightPt;
4993
+ return [
4994
+ [(x0 + x1) / 2, (y0 + y1) / 2],
4995
+ [x0, y0],
4996
+ [x1, y0],
4997
+ [x0, y1],
4998
+ [x1, y1]
4999
+ ];
5000
+ };
5001
+ const slideSamples = sampleFractions(
5002
+ nodeBox,
5003
+ 0,
5004
+ 0,
5005
+ slideWidthIn * 72,
5006
+ slideHeightIn * 72
5007
+ );
5008
+ const ownFill = [
5009
+ ...new Set(
5010
+ sampleFractions(
5011
+ nodeBox,
5012
+ boxXPt ?? 0,
5013
+ boxYPt ?? 0,
5014
+ boxWidthPt ?? 0,
5015
+ boxHeightPt ?? 0
5016
+ ).flatMap(
5017
+ ([fx, fy]) => paintedColorHexes(
5018
+ node.props.fill,
5019
+ theme,
5020
+ fx,
5021
+ fy,
5022
+ (boxWidthPt ?? 0) / 72,
5023
+ (boxHeightPt ?? 0) / 72
5024
+ )
5025
+ )
5026
+ )
5027
+ ];
5028
+ let backgroundHexes = [
5029
+ ...new Set(slideSamples.flatMap(([fx, fy]) => slideBackground(fx, fy)))
5030
+ ];
5031
+ let backgroundUnknown = false;
5032
+ if (ownFill.length > 0) {
5033
+ backgroundHexes = ownFill;
5034
+ } else if (isCompleteBox(nodeBox)) {
5035
+ const covering = surfaceBoxes.filter(
5036
+ (surface) => surface.order < node.order && containsCentre(surface.box, nodeBox)
5037
+ ).pop();
5038
+ if (covering?.isImage) {
5039
+ backgroundUnknown = true;
5040
+ } else if (covering) {
5041
+ const fill2 = [
5042
+ ...new Set(
5043
+ sampleFractions(
5044
+ nodeBox,
5045
+ covering.box.xPt,
5046
+ covering.box.yPt,
5047
+ covering.box.widthPt,
5048
+ covering.box.heightPt
5049
+ ).flatMap(
5050
+ ([fx, fy]) => paintedColorHexes(
5051
+ covering.props.fill,
5052
+ theme,
5053
+ fx,
5054
+ fy,
5055
+ covering.box.widthPt / 72,
5056
+ covering.box.heightPt / 72
5057
+ )
5058
+ )
5059
+ )
5060
+ ];
5061
+ if (fill2.length > 0) backgroundHexes = fill2;
5062
+ else backgroundUnknown = true;
5063
+ }
5064
+ }
5065
+ const colorHex = typeof node.props.color === "string" ? resolveColor(node.props.color, theme)?.toUpperCase() : void 0;
5066
+ addFact({
5067
+ id: `pptx:text:${renderedIndex}:${nodeIndex}:${node.path}`,
5068
+ kind: "pptx/text",
5069
+ path: node.path,
5070
+ slidePath,
5071
+ text: node.text,
5072
+ fontSizePt: typography.fontSize,
5073
+ lineSpacingPt: typography.lineSpacing,
5074
+ paraSpaceBeforePt: typography.paraSpaceBefore,
5075
+ paraSpaceAfterPt: typography.paraSpaceAfter,
5076
+ ...typography.styleName && { styleName: typography.styleName },
5077
+ ...boxXPt !== void 0 && { boxXPt },
5078
+ ...boxYPt !== void 0 && { boxYPt },
5079
+ ...boxWidthPt !== void 0 && boxWidthPt > 0 && { boxWidthPt },
5080
+ ...boxHeightPt !== void 0 && boxHeightPt > 0 && { boxHeightPt },
5081
+ verticalAlign: node.props.valign === "middle" || node.props.valign === "bottom" ? node.props.valign : "top",
5082
+ rotationDeg: asNumber(node.props.rotate) ?? 0,
5083
+ bold: typography.bold,
5084
+ autoFit: node.props.h === void 0 && gridPos === void 0,
5085
+ ...colorHex !== void 0 && { colorHex },
5086
+ ...!backgroundUnknown && backgroundHexes.length > 0 && { backgroundHexes }
5087
+ });
5088
+ });
5089
+ addFact({
5090
+ id: `pptx:slide:${renderedIndex}:${slidePath}`,
5091
+ kind: "pptx/slide",
5092
+ path: slidePath,
5093
+ bodyWords
5094
+ });
5095
+ }
5096
+ function preparePptxQualityDocument(document, options = {}) {
5097
+ const facts = [];
5098
+ const provenance = {};
5099
+ const addFact = (fact) => {
5100
+ facts.push(fact);
5101
+ provenance[fact.id] = {
5102
+ path: fact.path,
5103
+ ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
5104
+ };
5105
+ };
5106
+ const props = asRecord(document.props) ?? {};
5107
+ addFact({
5108
+ id: "pptx:canvas",
5109
+ kind: "pptx/canvas",
5110
+ path: "/props",
5111
+ ...asNumber(props.slideWidth) !== void 0 && {
5112
+ widthIn: asNumber(props.slideWidth)
5113
+ },
5114
+ ...asNumber(props.slideHeight) !== void 0 && {
5115
+ heightIn: asNumber(props.slideHeight)
5116
+ }
5117
+ });
5118
+ const warnings = options.warnings ?? [];
5119
+ const context = resolveThemeContext(document, {
5120
+ customThemes: options.customThemes,
5121
+ fonts: options.fonts,
5122
+ warnings
5123
+ });
5124
+ const processed = processPresentation(context.document, {
5125
+ theme: context.theme,
5126
+ customThemes: options.customThemes,
5127
+ services: options.services
5128
+ });
5129
+ const ctx = themeContext(processed.theme);
5130
+ const authoredChildren = Array.isArray(document.children) ? document.children : [];
5131
+ const slideIndexes = authoredChildren.flatMap((child, index) => {
5132
+ const slide = asRecord(child);
5133
+ return slide?.name === "slide" && slide.enabled !== false ? [index] : [];
5134
+ });
5135
+ const templateIndexes = /* @__PURE__ */ new Map();
5136
+ const templates = new Map(
5137
+ (processed.templates ?? []).map((template, index) => {
5138
+ templateIndexes.set(template.name, index);
5139
+ return [template.name, template];
5140
+ })
5141
+ );
5142
+ const analyzedTextPaths = /* @__PURE__ */ new Set();
5143
+ processed.slides.forEach((slide, renderedIndex) => {
5144
+ const authoredIndex = slideIndexes[renderedIndex];
5145
+ if (authoredIndex === void 0) return;
5146
+ const slidePath = `/children/${authoredIndex}`;
5147
+ const authoredSlide = asRecord(authoredChildren[authoredIndex]);
5148
+ const authoredComponents = Array.isArray(authoredSlide?.children) ? authoredSlide.children : [];
5149
+ const template = slide.template ? templates.get(slide.template) : void 0;
5150
+ const effectiveGrid = mergeGridConfigs(processed.grid, template?.grid);
5151
+ const roots = [];
5152
+ const templateIndex = template ? templateIndexes.get(template.name) : void 0;
5153
+ if (template && templateIndex !== void 0) {
5154
+ template.objects?.forEach((component, index) => {
5155
+ roots.push({
5156
+ component,
5157
+ path: `/props/templates/${templateIndex}/objects/${index}`
5158
+ });
5159
+ });
5160
+ }
5161
+ slide.components.forEach((component, index) => {
5162
+ if (authoredComponents[index] === void 0) return;
5163
+ roots.push({
5164
+ component,
5165
+ path: `${slidePath}/children/${index}`
5166
+ });
5167
+ });
5168
+ for (const resolved of resolvePlaceholderComponents(
5169
+ slide,
5170
+ template,
5171
+ effectiveGrid,
5172
+ {
5173
+ theme: processed.theme,
5174
+ slideWidth: processed.slideWidth,
5175
+ slideHeight: processed.slideHeight,
5176
+ slideIndex: renderedIndex,
5177
+ warnings
5178
+ }
5179
+ )) {
5180
+ roots.push({
5181
+ component: resolved.component,
5182
+ path: `${slidePath}/props/placeholders/${pointerSegment(resolved.name)}`
5183
+ });
5184
+ }
5185
+ addSlideFacts(
5186
+ roots,
5187
+ slidePath,
5188
+ renderedIndex,
5189
+ effectiveGrid,
5190
+ processed.slideWidth,
5191
+ processed.slideHeight,
5192
+ ctx,
5193
+ processed.theme,
5194
+ (fx, fy) => paintedColorHexes(
5195
+ slide.background ?? template?.background ?? props.background,
5196
+ processed.theme,
5197
+ fx,
5198
+ fy,
5199
+ processed.slideWidth,
5200
+ processed.slideHeight
5201
+ ),
5202
+ analyzedTextPaths,
5203
+ addFact
5204
+ );
5205
+ });
5206
+ return {
5207
+ format: "pptx",
5208
+ model: {
5209
+ authored: document,
5210
+ document: context.document,
5211
+ theme: context.theme,
5212
+ processed
5213
+ },
5214
+ facts,
5215
+ provenance,
5216
+ renderer: options.renderer ?? DEFAULT_PPTX_RENDERER_ID2
5217
+ };
5218
+ }
5219
+
4743
5220
  // src/core/generateFromIr.ts
4744
5221
  var UncompiledComponentError = class extends Error {
4745
5222
  code = "UNCOMPILED_COMPONENT";
@@ -4762,29 +5239,24 @@ async function generateBufferViaIr(jsonConfig, options) {
4762
5239
  selectedRenderer && PPTX_RENDERER_IDS.includes(selectedRenderer) ? { ...component, renderer: selectedRenderer } : component,
4763
5240
  options?.validation
4764
5241
  );
5242
+ assertNoContentConflicts(component);
4765
5243
  const warnings = [];
4766
- const context = resolveThemeContext(component, {
5244
+ const prepared = options?.prepared ?? preparePptxQualityDocument(component, {
4767
5245
  customThemes: options?.customThemes,
4768
5246
  fonts: options?.fonts,
4769
- warnings
5247
+ warnings,
5248
+ services: options?.services,
5249
+ renderer: selectedRenderer
4770
5250
  });
4771
- assertNoContentConflicts(context.document);
4772
5251
  await resolveDocumentFonts(
4773
- context.document,
4774
- context.theme,
5252
+ prepared.model.document,
5253
+ prepared.model.theme,
4775
5254
  warnings,
4776
5255
  options?.fonts
4777
5256
  );
4778
5257
  const buffer = await runWithBaseDir(
4779
5258
  options?.baseDir,
4780
- () => renderProcessedViaIr(
4781
- processPresentation(context.document, {
4782
- ...effectiveOptions,
4783
- theme: context.theme
4784
- }),
4785
- warnings,
4786
- effectiveOptions
4787
- )
5259
+ () => renderProcessedViaIr(prepared.model.processed, warnings, effectiveOptions)
4788
5260
  );
4789
5261
  return { buffer, warnings };
4790
5262
  }
@@ -4853,6 +5325,465 @@ var PresentationGenerator = {
4853
5325
  init_finalizePackage();
4854
5326
  init_warn();
4855
5327
 
5328
+ // src/quality/preflight.ts
5329
+ import {
5330
+ assertValidQualityPolicy,
5331
+ assertValidQualityProfile
5332
+ } from "@json-to-office/quality";
5333
+
5334
+ // src/quality/rules.ts
5335
+ import {
5336
+ mergeQualityProfiles,
5337
+ QUALITY_CODES,
5338
+ QualityEngine,
5339
+ resolveRuleConfiguration
5340
+ } from "@json-to-office/quality";
5341
+ var RENDERER_DEFAULT_WIDTH_IN = 10;
5342
+ var RENDERER_DEFAULT_HEIGHT_IN = 7.5;
5343
+ var DEFAULT_CHAR_WIDTH_FACTOR = 0.46;
5344
+ var DEFAULT_SAFETY_BUFFER_PT = 8;
5345
+ var DEFAULT_MIN_READABLE_FONT_PT = 7;
5346
+ var DEFAULT_MAX_BODY_WORDS_PER_SLIDE = 130;
5347
+ var KNOWN_CANVASES = [
5348
+ { w: 13.333, h: 7.5, label: "16:9 standard" },
5349
+ { w: 10, h: 5.625, label: "16:9 small" },
5350
+ { w: 7.5, h: 7.5, label: "1:1 carousel" },
5351
+ { w: 7.5, h: 9.375, label: "4:5 vertical" },
5352
+ { w: 4.5, h: 8, label: "9:16 story" },
5353
+ { w: 10, h: 7.5, label: "4:3 legacy", legacy: true }
5354
+ ];
5355
+ function numberParameter(parameters, name, fallback) {
5356
+ const value = parameters[name];
5357
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
5358
+ }
5359
+ function textFacts(facts) {
5360
+ return facts.filter(
5361
+ (fact) => fact.kind === "pptx/text"
5362
+ );
5363
+ }
5364
+ function estimateTextHeightPt(fact, charWidthFactor) {
5365
+ if (fact.boxWidthPt === void 0 || fact.boxHeightPt === void 0) {
5366
+ return void 0;
5367
+ }
5368
+ const paragraphs = fact.text.split("\n");
5369
+ const charsPerLine = Math.max(
5370
+ 1,
5371
+ Math.floor(fact.boxWidthPt / (fact.fontSizePt * charWidthFactor))
5372
+ );
5373
+ let lines = 0;
5374
+ for (const paragraph of paragraphs) {
5375
+ const measured = paragraph.trimEnd();
5376
+ lines += measured === "" ? 1 : Math.max(1, Math.ceil(measured.length / charsPerLine));
5377
+ }
5378
+ let heightPt = fact.fontSizePt + Math.max(0, lines - 1) * fact.lineSpacingPt;
5379
+ if (paragraphs.length > 1) {
5380
+ heightPt += (paragraphs.length - 1) * (fact.paraSpaceBeforePt + fact.paraSpaceAfterPt);
5381
+ }
5382
+ return { heightPt, lines };
5383
+ }
5384
+ var pptxCanvasRule = {
5385
+ id: "pptx/canvas",
5386
+ code: QUALITY_CODES.CANVAS_UNSPECIFIED,
5387
+ category: "composition",
5388
+ defaultSeverity: "info",
5389
+ defaultCertainty: "deterministic",
5390
+ formats: ["pptx"],
5391
+ evaluate: ({ facts }) => {
5392
+ const canvas = facts.find(
5393
+ (fact) => fact.kind === "pptx/canvas"
5394
+ );
5395
+ if (!canvas) return [];
5396
+ const { widthIn: width, heightIn: height } = canvas;
5397
+ if (width === void 0 || height === void 0) {
5398
+ const missing = [
5399
+ width === void 0 ? "props.slideWidth" : void 0,
5400
+ height === void 0 ? "props.slideHeight" : void 0
5401
+ ].filter((entry) => entry !== void 0);
5402
+ const state = missing.length === 2 ? "No slide canvas declared" : `Incomplete slide canvas (${missing[0]} missing)`;
5403
+ return [
5404
+ {
5405
+ code: QUALITY_CODES.CANVAS_UNSPECIFIED,
5406
+ severity: "warning",
5407
+ category: "integrity",
5408
+ message: `${state}: the renderer falls back to 4:3 (${RENDERER_DEFAULT_WIDTH_IN}\xD7${RENDERER_DEFAULT_HEIGHT_IN}"), and 16:9 content on that canvas leaves a dead strip at the bottom.`,
5409
+ path: canvas.path,
5410
+ suggestion: "Declare props.slideWidth and props.slideHeight \u2014 13.333 \xD7 7.5 for a standard 16:9 deck.",
5411
+ context: {
5412
+ missing,
5413
+ rendererDefault: {
5414
+ slideWidth: RENDERER_DEFAULT_WIDTH_IN,
5415
+ slideHeight: RENDERER_DEFAULT_HEIGHT_IN
5416
+ }
5417
+ }
5418
+ }
5419
+ ];
5420
+ }
5421
+ const match = KNOWN_CANVASES.find(
5422
+ (known) => Math.abs(known.w - width) < 0.01 && Math.abs(known.h - height) < 0.01
5423
+ );
5424
+ if (match?.legacy) {
5425
+ return [
5426
+ {
5427
+ code: QUALITY_CODES.CANVAS_LEGACY,
5428
+ message: `Canvas is 4:3 legacy (${match.w}\xD7${match.h}") \u2014 modern screens are 16:9.`,
5429
+ path: canvas.path,
5430
+ suggestion: "If 4:3 is not deliberate, use slideWidth 13.333 and slideHeight 7.5."
5431
+ }
5432
+ ];
5433
+ }
5434
+ if (match) return [];
5435
+ return [
5436
+ {
5437
+ code: QUALITY_CODES.CANVAS_NONSTANDARD,
5438
+ message: `Canvas ${width}\xD7${height}" matches no common preset (16:9, 1:1, 4:5, 9:16).`,
5439
+ path: canvas.path,
5440
+ suggestion: "Confirm the size is deliberate; a mistyped canvas distorts every slide.",
5441
+ context: {
5442
+ knownCanvases: KNOWN_CANVASES.map(({ w, h, label }) => ({
5443
+ slideWidth: w,
5444
+ slideHeight: h,
5445
+ label
5446
+ }))
5447
+ }
5448
+ }
5449
+ ];
5450
+ }
5451
+ };
5452
+ var pptxMinimumFontRule = {
5453
+ id: "pptx/minimum-font-size",
5454
+ code: QUALITY_CODES.FONT_SIZE_MIN,
5455
+ category: "legibility",
5456
+ defaultSeverity: "warning",
5457
+ defaultCertainty: "measured",
5458
+ formats: ["pptx"],
5459
+ defaultParameters: { minimumFontPt: DEFAULT_MIN_READABLE_FONT_PT },
5460
+ evaluate: ({ facts, configuration }) => {
5461
+ const minimum = numberParameter(
5462
+ configuration.parameters,
5463
+ "minimumFontPt",
5464
+ DEFAULT_MIN_READABLE_FONT_PT
5465
+ );
5466
+ return textFacts(facts).filter((fact) => fact.fontSizePt < minimum).map((fact) => ({
5467
+ message: `Effective font size is ${fact.fontSizePt}pt \u2014 unreadable on a projected slide.`,
5468
+ path: `${fact.path}/props`,
5469
+ suggestion: `Use at least ${minimum}pt; captions rarely work below 10pt.`,
5470
+ context: { fontSize: fact.fontSizePt, threshold: minimum },
5471
+ evidence: { actual: fact.fontSizePt, expected: minimum, unit: "pt" },
5472
+ // `add` replaces an existing member, so this lifts an explicit
5473
+ // fontSize and overrides an inherited style value alike.
5474
+ fixes: [
5475
+ {
5476
+ op: "add",
5477
+ path: `${fact.path}/props/fontSize`,
5478
+ value: minimum
5479
+ }
5480
+ ]
5481
+ }));
5482
+ }
5483
+ };
5484
+ function fittingFontSizePt(fact, charWidthFactor, minimumFontPt) {
5485
+ if (fact.autoFit === true) return void 0;
5486
+ const minimumWholeSize = Math.ceil(minimumFontPt);
5487
+ for (let size = Math.floor(fact.fontSizePt) - 1; size >= minimumWholeSize; size--) {
5488
+ const estimate = estimateTextHeightPt(
5489
+ { ...fact, fontSizePt: size },
5490
+ charWidthFactor
5491
+ );
5492
+ if (estimate !== void 0 && fact.boxHeightPt !== void 0 && estimate.heightPt <= fact.boxHeightPt) {
5493
+ return size;
5494
+ }
5495
+ }
5496
+ return void 0;
5497
+ }
5498
+ var pptxTextFitRule = {
5499
+ id: "pptx/text-fit",
5500
+ code: QUALITY_CODES.TEXT_TIGHT,
5501
+ category: "integrity",
5502
+ defaultSeverity: "info",
5503
+ defaultCertainty: "estimated",
5504
+ formats: ["pptx"],
5505
+ defaultParameters: {
5506
+ characterWidthFactor: DEFAULT_CHAR_WIDTH_FACTOR,
5507
+ safetyBufferPt: DEFAULT_SAFETY_BUFFER_PT
5508
+ },
5509
+ evaluate: ({ facts, configuration, profile, policy }) => {
5510
+ const factor = numberParameter(
5511
+ configuration.parameters,
5512
+ "characterWidthFactor",
5513
+ DEFAULT_CHAR_WIDTH_FACTOR
5514
+ );
5515
+ const safetyBufferPt = numberParameter(
5516
+ configuration.parameters,
5517
+ "safetyBufferPt",
5518
+ DEFAULT_SAFETY_BUFFER_PT
5519
+ );
5520
+ const minimumFontConfiguration = resolveRuleConfiguration(
5521
+ pptxMinimumFontRule,
5522
+ profile,
5523
+ policy
5524
+ );
5525
+ const minimumFontPt = minimumFontConfiguration.enabled ? numberParameter(
5526
+ minimumFontConfiguration.parameters,
5527
+ "minimumFontPt",
5528
+ DEFAULT_MIN_READABLE_FONT_PT
5529
+ ) : DEFAULT_MIN_READABLE_FONT_PT;
5530
+ const findings = [];
5531
+ for (const fact of textFacts(facts)) {
5532
+ const estimate = estimateTextHeightPt(fact, factor);
5533
+ if (!estimate || fact.boxHeightPt === void 0) continue;
5534
+ const marginPt = fact.boxHeightPt - estimate.heightPt;
5535
+ const measured = {
5536
+ estimatedTextPt: Math.round(estimate.heightPt * 10) / 10,
5537
+ availablePt: Math.round(fact.boxHeightPt * 10) / 10,
5538
+ marginPt: Math.round(marginPt * 10) / 10,
5539
+ estimatedLines: estimate.lines,
5540
+ fontSize: fact.fontSizePt,
5541
+ boxWidthPt: Math.round(fact.boxWidthPt * 10) / 10
5542
+ };
5543
+ if (marginPt < -fact.lineSpacingPt) {
5544
+ const fittingSize = fittingFontSizePt(fact, factor, minimumFontPt);
5545
+ findings.push({
5546
+ code: QUALITY_CODES.TEXT_OVERFLOW,
5547
+ severity: "warning",
5548
+ message: `Text is estimated at ${measured.estimatedTextPt}pt tall (${estimate.lines} line${estimate.lines === 1 ? "" : "s"} of ${fact.fontSizePt}pt) in a ${measured.availablePt}pt box \u2014 it will overflow.`,
5549
+ path: fact.path,
5550
+ suggestion: "Shorten the text, reduce fontSize, or enlarge the box (h / rowSpan).",
5551
+ context: measured,
5552
+ evidence: {
5553
+ actual: measured.estimatedTextPt,
5554
+ expected: measured.availablePt,
5555
+ unit: "pt"
5556
+ },
5557
+ // A ready-made patch only when a readable size fits; shortening
5558
+ // the text or growing the box stays the author's call.
5559
+ ...fittingSize !== void 0 && {
5560
+ fixes: [
5561
+ {
5562
+ op: "add",
5563
+ path: `${fact.path}/props/fontSize`,
5564
+ value: fittingSize
5565
+ }
5566
+ ]
5567
+ }
5568
+ });
5569
+ continue;
5570
+ }
5571
+ if (marginPt >= safetyBufferPt) continue;
5572
+ findings.push({
5573
+ code: QUALITY_CODES.TEXT_TIGHT,
5574
+ message: marginPt < 0 ? `Text is estimated to exceed its ${measured.availablePt}pt box by ${-measured.marginPt}pt \u2014 within one line-height, so likely a harmless spill into the gap below.` : `Text fits its box with only ${measured.marginPt}pt to spare \u2014 renderer rounding can push it over.`,
5575
+ path: fact.path,
5576
+ suggestion: `Leave at least ${safetyBufferPt}pt of vertical margin.`,
5577
+ context: measured,
5578
+ evidence: {
5579
+ actual: measured.marginPt,
5580
+ expected: safetyBufferPt,
5581
+ unit: "pt margin"
5582
+ }
5583
+ });
5584
+ }
5585
+ return findings;
5586
+ }
5587
+ };
5588
+ var pptxSlideDensityRule = {
5589
+ id: "pptx/slide-density",
5590
+ code: QUALITY_CODES.SLIDE_DENSITY,
5591
+ category: "information-design",
5592
+ defaultSeverity: "warning",
5593
+ defaultCertainty: "estimated",
5594
+ formats: ["pptx"],
5595
+ defaultParameters: { maximumBodyWords: DEFAULT_MAX_BODY_WORDS_PER_SLIDE },
5596
+ evaluate: ({ facts, configuration }) => {
5597
+ const threshold = numberParameter(
5598
+ configuration.parameters,
5599
+ "maximumBodyWords",
5600
+ DEFAULT_MAX_BODY_WORDS_PER_SLIDE
5601
+ );
5602
+ return facts.filter((fact) => fact.kind === "pptx/slide").filter((fact) => fact.bodyWords > threshold).map((fact) => ({
5603
+ message: `${fact.bodyWords} words of body text on one slide \u2014 an audience reads a slide, it does not study one.`,
5604
+ path: fact.path,
5605
+ suggestion: "One idea per slide: split the content across more slides.",
5606
+ context: { bodyWords: fact.bodyWords, threshold },
5607
+ evidence: {
5608
+ actual: fact.bodyWords,
5609
+ expected: threshold,
5610
+ unit: "words"
5611
+ }
5612
+ }));
5613
+ }
5614
+ };
5615
+ var AA_NORMAL_RATIO = 4.5;
5616
+ var AA_LARGE_RATIO = 3;
5617
+ var LARGE_TEXT_PT = 18;
5618
+ var LARGE_BOLD_TEXT_PT = 14;
5619
+ function channelLuminance(channel) {
5620
+ const c = channel / 255;
5621
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
5622
+ }
5623
+ function relativeLuminance(hex) {
5624
+ const match = /^#?([0-9a-f]{6})$/i.exec(hex);
5625
+ if (!match) return void 0;
5626
+ const value = parseInt(match[1], 16);
5627
+ return 0.2126 * channelLuminance(value >> 16 & 255) + 0.7152 * channelLuminance(value >> 8 & 255) + 0.0722 * channelLuminance(value & 255);
5628
+ }
5629
+ function contrastRatio(a, b) {
5630
+ const la = relativeLuminance(a);
5631
+ const lb = relativeLuminance(b);
5632
+ if (la === void 0 || lb === void 0) return void 0;
5633
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
5634
+ }
5635
+ var pptxTextContrastRule = {
5636
+ id: "pptx/text-contrast",
5637
+ code: QUALITY_CODES.TEXT_CONTRAST,
5638
+ category: "accessibility",
5639
+ defaultSeverity: "warning",
5640
+ defaultCertainty: "deterministic",
5641
+ formats: ["pptx"],
5642
+ defaultParameters: {
5643
+ normalRatio: AA_NORMAL_RATIO,
5644
+ largeRatio: AA_LARGE_RATIO,
5645
+ largeTextPt: LARGE_TEXT_PT,
5646
+ largeBoldTextPt: LARGE_BOLD_TEXT_PT
5647
+ },
5648
+ evaluate: ({ facts, configuration }) => {
5649
+ const normalRatio = numberParameter(
5650
+ configuration.parameters,
5651
+ "normalRatio",
5652
+ AA_NORMAL_RATIO
5653
+ );
5654
+ const largeRatio = numberParameter(
5655
+ configuration.parameters,
5656
+ "largeRatio",
5657
+ AA_LARGE_RATIO
5658
+ );
5659
+ const largeTextPt = numberParameter(
5660
+ configuration.parameters,
5661
+ "largeTextPt",
5662
+ LARGE_TEXT_PT
5663
+ );
5664
+ const largeBoldTextPt = numberParameter(
5665
+ configuration.parameters,
5666
+ "largeBoldTextPt",
5667
+ LARGE_BOLD_TEXT_PT
5668
+ );
5669
+ return textFacts(facts).flatMap((fact) => {
5670
+ const { colorHex, backgroundHexes } = fact;
5671
+ if (!colorHex || !backgroundHexes?.length) return [];
5672
+ let worst;
5673
+ for (const background2 of backgroundHexes) {
5674
+ const ratio = contrastRatio(colorHex, background2);
5675
+ if (ratio === void 0) continue;
5676
+ if (!worst || ratio < worst.ratio) worst = { ratio, background: background2 };
5677
+ }
5678
+ if (!worst) return [];
5679
+ const isLarge = fact.fontSizePt >= largeTextPt || fact.bold && fact.fontSizePt >= largeBoldTextPt;
5680
+ const required = isLarge ? largeRatio : normalRatio;
5681
+ if (worst.ratio >= required) return [];
5682
+ const rounded = Math.round(worst.ratio * 100) / 100;
5683
+ return [
5684
+ {
5685
+ message: `Text at #${colorHex} on #${worst.background} has ${rounded}:1 contrast \u2014 below the ${required}:1 needed at ${fact.fontSizePt}pt${fact.bold ? " bold" : ""}.`,
5686
+ path: fact.path,
5687
+ suggestion: "Darken the text, lighten it further, or change the surface behind it.",
5688
+ context: {
5689
+ colorHex,
5690
+ backgroundHex: worst.background,
5691
+ ratio: rounded,
5692
+ required,
5693
+ fontSizePt: fact.fontSizePt,
5694
+ bold: fact.bold,
5695
+ backgroundHexes
5696
+ },
5697
+ evidence: {
5698
+ actual: rounded,
5699
+ expected: required,
5700
+ unit: ":1"
5701
+ }
5702
+ }
5703
+ ];
5704
+ });
5705
+ }
5706
+ };
5707
+ var PPTX_QUALITY_RULES = {
5708
+ id: "pptx/default",
5709
+ rules: [
5710
+ pptxCanvasRule,
5711
+ pptxMinimumFontRule,
5712
+ pptxTextFitRule,
5713
+ pptxSlideDensityRule,
5714
+ pptxTextContrastRule
5715
+ ]
5716
+ };
5717
+ var PPTX_QUALITY_PROFILES = {
5718
+ "executive-presentation": {
5719
+ id: "executive-presentation",
5720
+ formats: ["pptx"],
5721
+ description: "Decision deck optimized for scan speed and projection.",
5722
+ rules: {
5723
+ "pptx/minimum-font-size": { parameters: { minimumFontPt: 14 } },
5724
+ "pptx/slide-density": { parameters: { maximumBodyWords: 70 } }
5725
+ }
5726
+ },
5727
+ "technical-presentation": {
5728
+ id: "technical-presentation",
5729
+ formats: ["pptx"],
5730
+ description: "Portable professional presentation defaults."
5731
+ }
5732
+ };
5733
+ var PPTX_DEFAULT_QUALITY_PROFILE = PPTX_QUALITY_PROFILES["technical-presentation"];
5734
+ var PPTX_PROFILES_BY_ID = PPTX_QUALITY_PROFILES;
5735
+ function resolvePptxQualityProfile(requested) {
5736
+ if (!requested) return void 0;
5737
+ const registered = PPTX_PROFILES_BY_ID[requested.id];
5738
+ if (!registered) return requested;
5739
+ return mergeQualityProfiles(registered, requested);
5740
+ }
5741
+ var pptxQualityEngine = new QualityEngine(PPTX_QUALITY_RULES.rules);
5742
+
5743
+ // src/quality/preflight.ts
5744
+ var PREPARE_RULE_ID = "quality/prepare";
5745
+ function emptyAnalysis(ruleErrors = [], blocked = false) {
5746
+ return {
5747
+ diagnostics: [],
5748
+ counts: { error: 0, warning: 0, info: 0 },
5749
+ blocked,
5750
+ truncated: false,
5751
+ suppressedCount: 0,
5752
+ evaluatedRuleIds: [],
5753
+ ruleErrors
5754
+ };
5755
+ }
5756
+ function analyzePptxQuality(doc, options = {}) {
5757
+ assertValidQualityPolicy(options.policy);
5758
+ assertValidQualityProfile(options.profile);
5759
+ if (typeof doc !== "object" || doc === null || Array.isArray(doc) || doc.name !== "pptx") {
5760
+ return emptyAnalysis();
5761
+ }
5762
+ let prepared = options.prepared;
5763
+ try {
5764
+ prepared ??= preparePptxQualityDocument(
5765
+ doc,
5766
+ options
5767
+ );
5768
+ } catch (error) {
5769
+ if (options.policy?.onRuleError === "throw") throw error;
5770
+ const gate = options.policy?.gate;
5771
+ return emptyAnalysis(
5772
+ [
5773
+ {
5774
+ ruleId: PREPARE_RULE_ID,
5775
+ message: error instanceof Error ? error.message : String(error)
5776
+ }
5777
+ ],
5778
+ gate !== void 0 && gate !== "none"
5779
+ );
5780
+ }
5781
+ return pptxQualityEngine.analyzeSync(prepared, {
5782
+ profile: resolvePptxQualityProfile(options.profile) ?? PPTX_DEFAULT_QUALITY_PROFILE,
5783
+ policy: options.policy
5784
+ });
5785
+ }
5786
+
4856
5787
  // src/plugin/index.ts
4857
5788
  import {
4858
5789
  createComponent,
@@ -5360,10 +6291,14 @@ export {
5360
6291
  DEFAULT_PPTX_RENDERER_ID,
5361
6292
  DEFAULT_PPTX_THEME,
5362
6293
  DuplicateComponentError,
6294
+ PPTX_DEFAULT_QUALITY_PROFILE,
6295
+ PPTX_QUALITY_PROFILES,
6296
+ PPTX_QUALITY_RULES,
5363
6297
  PresentationGenerator,
5364
6298
  PresentationValidationError,
5365
6299
  UncompiledComponentError,
5366
6300
  W as WarningCodes,
6301
+ analyzePptxQuality,
5367
6302
  cleanComponentProps,
5368
6303
  createComponent,
5369
6304
  createPresentationGenerator,
@@ -5380,10 +6315,13 @@ export {
5380
6315
  isPresentationComponent,
5381
6316
  isPresentationComponentDefinition,
5382
6317
  isSlideComponent,
6318
+ pptxQualityEngine,
5383
6319
  pptxRendererIds,
5384
6320
  pptxRendererStatuses,
5385
6321
  pptxThemes,
6322
+ preparePptxQualityDocument,
5386
6323
  resolveComponentVersion3 as resolveComponentVersion,
6324
+ resolvePptxQualityProfile,
5387
6325
  validateComponentProps,
5388
6326
  validatePresentation
5389
6327
  };