@json-to-office/core-pptx 6.6.0 → 7.0.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/blocks/document.d.ts.map +1 -1
- package/dist/core/finalizePackage.d.ts +1 -0
- package/dist/core/finalizePackage.d.ts.map +1 -1
- package/dist/core/generateFromIr.d.ts +1 -0
- package/dist/core/generateFromIr.d.ts.map +1 -1
- package/dist/core/generationContext.d.ts +3 -2
- package/dist/core/generationContext.d.ts.map +1 -1
- package/dist/core/generationOptions.d.ts +1 -0
- package/dist/core/generationOptions.d.ts.map +1 -1
- package/dist/core/generator.d.ts +1 -0
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +717 -248
- package/dist/index.js.map +1 -1
- package/dist/plugin/createPresentationGenerator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +25 -0
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/quality/facts.d.ts +101 -2
- package/dist/quality/facts.d.ts.map +1 -1
- package/dist/quality/rules.d.ts +54 -14
- package/dist/quality/rules.d.ts.map +1 -1
- package/dist/renderers/pptxgenjs/packaging.d.ts +1 -0
- package/dist/renderers/pptxgenjs/packaging.d.ts.map +1 -1
- package/dist/themes/defaults.d.ts +8 -1
- package/dist/themes/defaults.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -5163,36 +5163,8 @@ var DEFAULT_STYLES = {
|
|
|
5163
5163
|
body: { fontSize: 14 },
|
|
5164
5164
|
caption: { fontSize: 10, italic: true, fontColor: "text2" }
|
|
5165
5165
|
};
|
|
5166
|
-
var DEFAULT_PPTX_THEME =
|
|
5167
|
-
name: "default",
|
|
5168
|
-
displayName: "Office Default",
|
|
5169
|
-
description: "The stock Office look: blue primary, orange and green accents, Arial throughout, centred titles",
|
|
5170
|
-
whenToUse: "A deck that has to match a plain PowerPoint look; pick a designed theme for anything a client will see.",
|
|
5171
|
-
colors: {
|
|
5172
|
-
primary: "#4472C4",
|
|
5173
|
-
secondary: "#ED7D31",
|
|
5174
|
-
accent: "#70AD47",
|
|
5175
|
-
background: "#FFFFFF",
|
|
5176
|
-
text: "#333333",
|
|
5177
|
-
text2: "#44546A",
|
|
5178
|
-
background2: "#E7E6E6",
|
|
5179
|
-
accent4: "#FFC000",
|
|
5180
|
-
accent5: "#5B9BD5",
|
|
5181
|
-
accent6: "#70AD47"
|
|
5182
|
-
},
|
|
5183
|
-
fonts: {
|
|
5184
|
-
heading: "Arial",
|
|
5185
|
-
body: "Arial"
|
|
5186
|
-
},
|
|
5187
|
-
defaults: {
|
|
5188
|
-
fontSize: 18,
|
|
5189
|
-
fontColor: "#333333"
|
|
5190
|
-
},
|
|
5191
|
-
styles: DEFAULT_STYLES,
|
|
5192
|
-
componentDefaults: { table: DEFAULT_TABLE }
|
|
5193
|
-
};
|
|
5166
|
+
var DEFAULT_PPTX_THEME = CONSULTING_PPTX_THEME;
|
|
5194
5167
|
var PPTX_THEMES = {
|
|
5195
|
-
default: DEFAULT_PPTX_THEME,
|
|
5196
5168
|
consulting: CONSULTING_PPTX_THEME,
|
|
5197
5169
|
vermilion: VERMILION_PPTX_THEME,
|
|
5198
5170
|
devportal: DEVPORTAL_PPTX_THEME,
|
|
@@ -5332,7 +5304,7 @@ function resolveThemeContext(documentIn, options = {}) {
|
|
|
5332
5304
|
inlineTheme = document.props.theme;
|
|
5333
5305
|
}
|
|
5334
5306
|
const authoredThemeName = typeof document.props.theme === "string" ? document.props.theme : void 0;
|
|
5335
|
-
const baseThemeName = inlineTheme ? inlineTheme.name || "inline-theme" : authoredThemeName ?? defaultThemeName ?? "
|
|
5307
|
+
const baseThemeName = inlineTheme ? inlineTheme.name || "inline-theme" : authoredThemeName ?? defaultThemeName ?? "consulting";
|
|
5336
5308
|
let theme = inlineTheme ?? (resolveNamedTheme ? resolveNamedTheme(baseThemeName, authoredThemeName !== void 0) : customThemes?.[baseThemeName] ?? getPptxTheme(baseThemeName));
|
|
5337
5309
|
theme = resolvePptxDesignSystem(
|
|
5338
5310
|
theme,
|
|
@@ -5913,10 +5885,22 @@ async function expandPptxBlocksWithPlugins(document, theme, plugins, render, pre
|
|
|
5913
5885
|
preserve
|
|
5914
5886
|
});
|
|
5915
5887
|
const finished = finishPptxBlocks(composed.standard, evaluator, effects2);
|
|
5916
|
-
return {
|
|
5888
|
+
return {
|
|
5889
|
+
...finished,
|
|
5890
|
+
preserved: composed.preserved,
|
|
5891
|
+
pluginOutputs: evaluator.pluginOutputs
|
|
5892
|
+
};
|
|
5917
5893
|
}
|
|
5918
5894
|
|
|
5919
5895
|
// src/quality/facts.ts
|
|
5896
|
+
function textInsetsPt(props, componentName) {
|
|
5897
|
+
const margin = props.margin;
|
|
5898
|
+
if (typeof margin === "number" && Number.isFinite(margin))
|
|
5899
|
+
return [margin, margin, margin, margin];
|
|
5900
|
+
if (Array.isArray(margin) && margin.length === 4 && margin.every((side) => typeof side === "number" && Number.isFinite(side)))
|
|
5901
|
+
return margin;
|
|
5902
|
+
return componentName === "shape" ? [3.6, 7.2, 3.6, 7.2] : [0, 0, 0, 0];
|
|
5903
|
+
}
|
|
5920
5904
|
function asRecord(value) {
|
|
5921
5905
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
5922
5906
|
}
|
|
@@ -5972,6 +5956,37 @@ function horizontalAlign(props, ctx) {
|
|
|
5972
5956
|
const styled = typeof props.style === "string" ? ctx.styles[props.style]?.align : void 0;
|
|
5973
5957
|
return styled && HORIZONTAL_ALIGNMENTS.has(styled) ? styled : "left";
|
|
5974
5958
|
}
|
|
5959
|
+
var BLEED_TOLERANCE_PT = 2;
|
|
5960
|
+
var NOT_BODY_STYLES = /* @__PURE__ */ new Set([
|
|
5961
|
+
"title",
|
|
5962
|
+
"subtitle",
|
|
5963
|
+
"display",
|
|
5964
|
+
"tracker",
|
|
5965
|
+
"footer",
|
|
5966
|
+
"source"
|
|
5967
|
+
]);
|
|
5968
|
+
function imageBox(node, box, statedPropsAt) {
|
|
5969
|
+
if (node.componentName !== "image" || !isCompleteBox(box)) return box;
|
|
5970
|
+
const ratio = readablePptxRatio(node.props);
|
|
5971
|
+
if (ratio === void 0 || ratio <= 0) return box;
|
|
5972
|
+
const authored = statedPropsAt(node.path) ?? node.props;
|
|
5973
|
+
const statesWidth = authored.w !== void 0;
|
|
5974
|
+
const statesHeight = authored.h !== void 0;
|
|
5975
|
+
if (statesWidth && !statesHeight)
|
|
5976
|
+
return { ...box, heightPt: box.widthPt / ratio };
|
|
5977
|
+
if (statesHeight && !statesWidth)
|
|
5978
|
+
return { ...box, widthPt: box.heightPt * ratio };
|
|
5979
|
+
return box;
|
|
5980
|
+
}
|
|
5981
|
+
function slotOrNode(nodePath, authoredPath) {
|
|
5982
|
+
for (const prop of ["text", "runs"]) {
|
|
5983
|
+
const compiled = `${nodePath}/props/${prop}`;
|
|
5984
|
+
const authored = authoredPath(compiled);
|
|
5985
|
+
if (authored !== compiled && authored.includes("/props/slots/"))
|
|
5986
|
+
return authored;
|
|
5987
|
+
}
|
|
5988
|
+
return nodePath;
|
|
5989
|
+
}
|
|
5975
5990
|
function bulletItems(props) {
|
|
5976
5991
|
const runs = Array.isArray(props.runs) ? props.runs : void 0;
|
|
5977
5992
|
const whole = props.bullet !== void 0 && props.bullet !== false;
|
|
@@ -6130,7 +6145,7 @@ function isOpaqueComponent(componentName, props) {
|
|
|
6130
6145
|
const type = typeof props.type === "string" ? props.type : "rect";
|
|
6131
6146
|
return RECTANGULAR_SHAPES.has(type) && isOpaqueFill(props.fill);
|
|
6132
6147
|
}
|
|
6133
|
-
function collectSlideNodes(component, path4, text, surfaces, boxes, contentNodes, counter) {
|
|
6148
|
+
function collectSlideNodes(component, path4, text, richText, surfaces, boxes, contentNodes, counter) {
|
|
6134
6149
|
const rec = asRecord(component);
|
|
6135
6150
|
if (!rec || rec.enabled === false) return;
|
|
6136
6151
|
const props = asRecord(rec.props) ?? {};
|
|
@@ -6143,9 +6158,28 @@ function collectSlideNodes(component, path4, text, surfaces, boxes, contentNodes
|
|
|
6143
6158
|
const content = typeof props.text === "string" ? props.text : void 0;
|
|
6144
6159
|
if (content !== void 0 && content.trim() !== "" && props.runs === void 0) {
|
|
6145
6160
|
if (rec.name === "text" || rec.name === "shape") {
|
|
6146
|
-
text.push({
|
|
6161
|
+
text.push({
|
|
6162
|
+
props,
|
|
6163
|
+
path: path4,
|
|
6164
|
+
componentName: rec.name,
|
|
6165
|
+
text: content,
|
|
6166
|
+
order
|
|
6167
|
+
});
|
|
6147
6168
|
}
|
|
6148
6169
|
}
|
|
6170
|
+
if (Array.isArray(props.runs) && (rec.name === "text" || rec.name === "shape")) {
|
|
6171
|
+
const joined = props.runs.map(asRecord).map(
|
|
6172
|
+
(run) => run && typeof run.text === "string" ? `${run.text}${run.breakLine === true ? "\n" : ""}` : ""
|
|
6173
|
+
).join("");
|
|
6174
|
+
if (joined.trim() !== "")
|
|
6175
|
+
richText.push({
|
|
6176
|
+
props,
|
|
6177
|
+
path: path4,
|
|
6178
|
+
componentName: String(rec.name),
|
|
6179
|
+
text: joined,
|
|
6180
|
+
order
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
6149
6183
|
const componentName = typeof rec.name === "string" ? rec.name : "";
|
|
6150
6184
|
if (componentName === "chart" || componentName === "highcharts" || componentName === "table") {
|
|
6151
6185
|
contentNodes.push({ props, path: path4, componentName });
|
|
@@ -6165,6 +6199,7 @@ function collectSlideNodes(component, path4, text, surfaces, boxes, contentNodes
|
|
|
6165
6199
|
child,
|
|
6166
6200
|
`${path4}/children/${index}`,
|
|
6167
6201
|
text,
|
|
6202
|
+
richText,
|
|
6168
6203
|
surfaces,
|
|
6169
6204
|
boxes,
|
|
6170
6205
|
contentNodes,
|
|
@@ -6318,6 +6353,16 @@ function tableFact(node, slidePath, authoredProps) {
|
|
|
6318
6353
|
}
|
|
6319
6354
|
return { ...base, columns };
|
|
6320
6355
|
}
|
|
6356
|
+
function authoredNameAtPointer(root, pointer) {
|
|
6357
|
+
let node = root;
|
|
6358
|
+
for (const token of pointer.split("/").slice(1)) {
|
|
6359
|
+
const key = token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
6360
|
+
node = Array.isArray(node) ? node[Number(key)] : asRecord(node)?.[key];
|
|
6361
|
+
if (node === void 0) return void 0;
|
|
6362
|
+
}
|
|
6363
|
+
const name = asRecord(node)?.name;
|
|
6364
|
+
return typeof name === "string" ? name : void 0;
|
|
6365
|
+
}
|
|
6321
6366
|
function authoredPropsAtPointer(root, pointer) {
|
|
6322
6367
|
let node = root;
|
|
6323
6368
|
for (const token of pointer.split("/").slice(1)) {
|
|
@@ -6335,8 +6380,9 @@ function authoredPropsAtPointer(root, pointer) {
|
|
|
6335
6380
|
}
|
|
6336
6381
|
return asRecord(asRecord(node)?.props);
|
|
6337
6382
|
}
|
|
6338
|
-
function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slideHeightIn, ctx, theme, slideBackground, analyzedTextPaths, analyzedContentPaths, paletteTokens, authoredPropsAt, authoredPath, addFact) {
|
|
6383
|
+
function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn, slideHeightIn, ctx, theme, slideBackground, analyzedTextPaths, analyzedContentPaths, paletteTokens, authoredPropsAt, statedPropsAt, authoredPath, addFact, ownsProps = () => true) {
|
|
6339
6384
|
const nodes = [];
|
|
6385
|
+
const richNodes = [];
|
|
6340
6386
|
const surfaces = [];
|
|
6341
6387
|
const boxes = [];
|
|
6342
6388
|
const contentNodes = [];
|
|
@@ -6346,6 +6392,7 @@ function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slid
|
|
|
6346
6392
|
root.component,
|
|
6347
6393
|
root.path,
|
|
6348
6394
|
nodes,
|
|
6395
|
+
richNodes,
|
|
6349
6396
|
surfaces,
|
|
6350
6397
|
boxes,
|
|
6351
6398
|
contentNodes,
|
|
@@ -6359,20 +6406,30 @@ function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slid
|
|
|
6359
6406
|
if (fact) addFact(fact);
|
|
6360
6407
|
}
|
|
6361
6408
|
boxes.forEach((node, boxIndex) => {
|
|
6362
|
-
const box =
|
|
6409
|
+
const box = imageBox(
|
|
6410
|
+
node,
|
|
6411
|
+
resolveBox(node.props, grid, slideWidthIn, slideHeightIn),
|
|
6412
|
+
statedPropsAt
|
|
6413
|
+
);
|
|
6363
6414
|
if (!isCompleteBox(box)) return;
|
|
6415
|
+
const onSlide = /^\/children\/\d+\/children\/\d+$/.test(node.path) && authoredPath(node.path) === node.path;
|
|
6416
|
+
const authored = onSlide ? authoredPropsAt(node.path) : void 0;
|
|
6364
6417
|
addFact({
|
|
6365
6418
|
id: `pptx:box:${renderedIndex}:${boxIndex}:${node.path}`,
|
|
6366
6419
|
kind: "pptx/box",
|
|
6367
6420
|
path: node.path,
|
|
6368
6421
|
slidePath,
|
|
6422
|
+
nodePath: node.path,
|
|
6369
6423
|
componentName: node.componentName,
|
|
6370
6424
|
order: node.order,
|
|
6371
6425
|
opaque: node.opaque,
|
|
6372
6426
|
xPt: box.xPt,
|
|
6373
6427
|
yPt: box.yPt,
|
|
6374
6428
|
widthPt: box.widthPt,
|
|
6375
|
-
heightPt: box.heightPt
|
|
6429
|
+
heightPt: box.heightPt,
|
|
6430
|
+
...typeof authored?.x === "number" && typeof authored?.y === "number" && authored.grid === void 0 && {
|
|
6431
|
+
authoredPositionIn: { x: authored.x, y: authored.y }
|
|
6432
|
+
}
|
|
6376
6433
|
});
|
|
6377
6434
|
});
|
|
6378
6435
|
const surfaceBoxes = surfaces.flatMap((surface) => {
|
|
@@ -6382,7 +6439,7 @@ function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slid
|
|
|
6382
6439
|
let bodyWords = 0;
|
|
6383
6440
|
nodes.forEach((node, nodeIndex) => {
|
|
6384
6441
|
const typography = resolveTypography(node.props, ctx);
|
|
6385
|
-
if (
|
|
6442
|
+
if (!NOT_BODY_STYLES.has(typography.styleName ?? "")) {
|
|
6386
6443
|
bodyWords += node.text.split(/\s+/).filter(Boolean).length;
|
|
6387
6444
|
}
|
|
6388
6445
|
if (analyzedTextPaths.has(node.path)) return;
|
|
@@ -6476,13 +6533,18 @@ function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slid
|
|
|
6476
6533
|
}
|
|
6477
6534
|
}
|
|
6478
6535
|
const colorHex = typeof node.props.color === "string" ? resolveColor(node.props.color, theme)?.toUpperCase() : void 0;
|
|
6536
|
+
const stated = statedPropsAt(node.path);
|
|
6537
|
+
const setSize = stated && node.props.fit !== void 0 ? resolveTypography(stated, ctx).fontSize : void 0;
|
|
6479
6538
|
addFact({
|
|
6480
6539
|
id: `pptx:text:${renderedIndex}:${nodeIndex}:${node.path}`,
|
|
6481
6540
|
kind: "pptx/text",
|
|
6482
6541
|
path: node.path,
|
|
6483
6542
|
slidePath,
|
|
6543
|
+
...page === void 0 ? { slideHidden: true } : { page },
|
|
6544
|
+
nodePath: node.path,
|
|
6484
6545
|
text: node.text,
|
|
6485
6546
|
fontSizePt: typography.fontSize,
|
|
6547
|
+
...setSize !== void 0 && setSize !== typography.fontSize && { fitFromPt: setSize },
|
|
6486
6548
|
lineSpacingPt: typography.lineSpacing,
|
|
6487
6549
|
paraSpaceBeforePt: typography.paraSpaceBefore,
|
|
6488
6550
|
paraSpaceAfterPt: typography.paraSpaceAfter,
|
|
@@ -6491,19 +6553,64 @@ function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slid
|
|
|
6491
6553
|
...boxYPt !== void 0 && { boxYPt },
|
|
6492
6554
|
...boxWidthPt !== void 0 && boxWidthPt > 0 && { boxWidthPt },
|
|
6493
6555
|
...boxHeightPt !== void 0 && boxHeightPt > 0 && { boxHeightPt },
|
|
6556
|
+
insetsPt: textInsetsPt(node.props, node.componentName),
|
|
6494
6557
|
verticalAlign: node.props.valign === "middle" || node.props.valign === "bottom" ? node.props.valign : "top",
|
|
6495
6558
|
align: horizontalAlign(node.props, ctx),
|
|
6496
6559
|
rotationDeg: asNumber(node.props.rotate) ?? 0,
|
|
6497
6560
|
bold: typography.bold,
|
|
6498
|
-
|
|
6561
|
+
// Only a size the author wrote has a pointer: component defaults and
|
|
6562
|
+
// the fit pass also write `fontSize` into the processed props, onto a
|
|
6563
|
+
// member the document does not have.
|
|
6564
|
+
...asNumber(node.props.fontSize) !== void 0 && asNumber(authoredPropsAt(node.path)?.fontSize) !== void 0 && {
|
|
6499
6565
|
sizePath: `${node.path}/props/fontSize`
|
|
6500
6566
|
},
|
|
6501
6567
|
generated: authoredPath(node.path) !== node.path,
|
|
6568
|
+
ownsProps: ownsProps(node.path),
|
|
6502
6569
|
autoFit: node.props.h === void 0 && gridPos === void 0,
|
|
6503
6570
|
...colorHex !== void 0 && { colorHex },
|
|
6504
6571
|
...!backgroundUnknown && backgroundHexes.length > 0 && { backgroundHexes }
|
|
6505
6572
|
});
|
|
6506
6573
|
});
|
|
6574
|
+
richNodes.forEach((node, nodeIndex) => {
|
|
6575
|
+
if (analyzedTextPaths.has(node.path)) return;
|
|
6576
|
+
analyzedTextPaths.add(node.path);
|
|
6577
|
+
const typography = resolveTypography(node.props, ctx);
|
|
6578
|
+
const box = resolveBox(node.props, grid, slideWidthIn, slideHeightIn);
|
|
6579
|
+
const runs = node.props.runs.map(asRecord);
|
|
6580
|
+
addFact({
|
|
6581
|
+
id: `pptx:rich-text:${renderedIndex}:${nodeIndex}:${node.path}`,
|
|
6582
|
+
kind: "pptx/rich-text",
|
|
6583
|
+
path: node.path,
|
|
6584
|
+
slidePath,
|
|
6585
|
+
...page === void 0 ? { slideHidden: true } : { page },
|
|
6586
|
+
nodePath: node.path,
|
|
6587
|
+
text: node.text,
|
|
6588
|
+
...typography.styleName && { styleName: typography.styleName },
|
|
6589
|
+
runSizes: runs.flatMap((run, index) => {
|
|
6590
|
+
if (!run || typeof run.text !== "string" || run.text.trim() === "")
|
|
6591
|
+
return [];
|
|
6592
|
+
const own = asNumber(run.fontSize);
|
|
6593
|
+
return [
|
|
6594
|
+
{
|
|
6595
|
+
fontSizePt: own ?? typography.fontSize,
|
|
6596
|
+
...own !== void 0 && {
|
|
6597
|
+
sizePath: `${node.path}/props/runs/${index}/fontSize`
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
];
|
|
6601
|
+
}),
|
|
6602
|
+
generated: authoredPath(node.path) !== node.path,
|
|
6603
|
+
...box.xPt !== void 0 && { boxXPt: box.xPt },
|
|
6604
|
+
...box.yPt !== void 0 && { boxYPt: box.yPt },
|
|
6605
|
+
...box.widthPt !== void 0 && box.widthPt > 0 && {
|
|
6606
|
+
boxWidthPt: box.widthPt
|
|
6607
|
+
},
|
|
6608
|
+
...box.heightPt !== void 0 && box.heightPt > 0 && {
|
|
6609
|
+
boxHeightPt: box.heightPt
|
|
6610
|
+
},
|
|
6611
|
+
rotationDeg: asNumber(node.props.rotate) ?? 0
|
|
6612
|
+
});
|
|
6613
|
+
});
|
|
6507
6614
|
const CHROME_STYLES = /* @__PURE__ */ new Set(["footer", "tracker", "source", "caption"]);
|
|
6508
6615
|
const carriesContent = (box) => box.componentName !== "shape" || typeof box.props.text === "string" && box.props.text.trim() !== "";
|
|
6509
6616
|
addFact({
|
|
@@ -6526,23 +6633,31 @@ function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slid
|
|
|
6526
6633
|
const stretched = authored.w !== void 0 && authored.h !== void 0 && !["contain", "cover"].includes(
|
|
6527
6634
|
String(asRecord(authored.sizing)?.type ?? "")
|
|
6528
6635
|
);
|
|
6636
|
+
const widthPt = slideWidthIn * 72;
|
|
6637
|
+
const heightPt = slideHeightIn * 72;
|
|
6638
|
+
const bleed = isCompleteBox(resolved) && (resolved.xPt <= BLEED_TOLERANCE_PT && resolved.xPt + resolved.widthPt >= widthPt - BLEED_TOLERANCE_PT || resolved.yPt <= BLEED_TOLERANCE_PT && resolved.yPt + resolved.heightPt >= heightPt - BLEED_TOLERANCE_PT);
|
|
6529
6639
|
addFact({
|
|
6530
6640
|
id: `pptx:image:${renderedIndex}:${box.path}`,
|
|
6531
6641
|
kind: "pptx/image",
|
|
6532
6642
|
path: box.path,
|
|
6643
|
+
slidePath,
|
|
6644
|
+
bleed,
|
|
6533
6645
|
...typeof alt === "string" && alt.trim() !== "" && { alt },
|
|
6534
6646
|
...stretched && isCompleteBox(resolved) && resolved.heightPt > 0 && {
|
|
6535
6647
|
drawnRatio: resolved.widthPt / resolved.heightPt
|
|
6536
6648
|
},
|
|
6537
|
-
...natural !== void 0 && { naturalRatio: natural }
|
|
6649
|
+
...natural !== void 0 && { naturalRatio: natural },
|
|
6650
|
+
...stretched && typeof authored.w === "number" && typeof authored.h === "number" && {
|
|
6651
|
+
authoredSizeIn: { w: authored.w, h: authored.h }
|
|
6652
|
+
}
|
|
6538
6653
|
});
|
|
6539
6654
|
}
|
|
6540
6655
|
const bullets = bulletItems(box.props);
|
|
6541
|
-
if (bullets.length >
|
|
6656
|
+
if (bullets.length > 0)
|
|
6542
6657
|
addFact({
|
|
6543
6658
|
id: `pptx:bullets:${renderedIndex}:${box.path}`,
|
|
6544
6659
|
kind: "pptx/bullets",
|
|
6545
|
-
path: box.path,
|
|
6660
|
+
path: slotOrNode(box.path, authoredPath),
|
|
6546
6661
|
slidePath,
|
|
6547
6662
|
items: bullets.length,
|
|
6548
6663
|
longestWords: Math.max(
|
|
@@ -6555,7 +6670,7 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6555
6670
|
const facts = [];
|
|
6556
6671
|
const provenance = {};
|
|
6557
6672
|
let sourceMap = {};
|
|
6558
|
-
const authoredPath = (path4) => toAuthoredPointer(sourceMap, path4);
|
|
6673
|
+
const authoredPath = (path4) => toAuthoredPointer(sourceMap, path4, options.expanded?.pluginOutputs);
|
|
6559
6674
|
const addFact = (raw) => {
|
|
6560
6675
|
const fact = {
|
|
6561
6676
|
...raw,
|
|
@@ -6602,12 +6717,12 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6602
6717
|
});
|
|
6603
6718
|
});
|
|
6604
6719
|
const warnings = options.warnings ?? [];
|
|
6605
|
-
const context = resolveThemeContext(document, {
|
|
6720
|
+
const context = options.context ?? resolveThemeContext(document, {
|
|
6606
6721
|
customThemes: options.customThemes,
|
|
6607
6722
|
fonts: options.fonts,
|
|
6608
6723
|
warnings
|
|
6609
6724
|
});
|
|
6610
|
-
const expanded = expandPptxBlocks(context.document, context.theme);
|
|
6725
|
+
const expanded = options.expanded ?? expandPptxBlocks(context.document, context.theme);
|
|
6611
6726
|
sourceMap = expanded.sourceMap;
|
|
6612
6727
|
const processed = processPresentation(expanded.document, {
|
|
6613
6728
|
theme: context.theme,
|
|
@@ -6701,10 +6816,17 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6701
6816
|
(value) => `#${resolveColor(value, processed.theme)}`
|
|
6702
6817
|
) ?? SERIES_COLOR_TOKENS.filter((token) => paletteHexes[token] !== void 0);
|
|
6703
6818
|
const authoredPropsAt = (pointer) => authoredPropsAtPointer(document, authoredPath(pointer));
|
|
6819
|
+
const pluginOutputs = options.expanded?.pluginOutputs ?? [];
|
|
6820
|
+
const ownsProps = (pointer) => !pluginOutputs.some(
|
|
6821
|
+
(prefix) => pointer === prefix || pointer.startsWith(`${prefix}/`)
|
|
6822
|
+
) && authoredNameAtPointer(document, authoredPath(pointer)) !== "block";
|
|
6823
|
+
const statedPropsAt = (pointer) => authoredPropsAtPointer(expanded.document, pointer);
|
|
6824
|
+
let exportedPages = 0;
|
|
6704
6825
|
processed.slides.forEach((slide, renderedIndex) => {
|
|
6705
6826
|
const authoredIndex = slideIndexes[renderedIndex];
|
|
6706
6827
|
if (authoredIndex === void 0) return;
|
|
6707
6828
|
const slidePath = `/children/${authoredIndex}`;
|
|
6829
|
+
const page = slide.hidden === true ? void 0 : exportedPages++;
|
|
6708
6830
|
const roots = slide.components.map(
|
|
6709
6831
|
(component, index) => ({
|
|
6710
6832
|
component,
|
|
@@ -6715,6 +6837,7 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6715
6837
|
roots,
|
|
6716
6838
|
slidePath,
|
|
6717
6839
|
renderedIndex,
|
|
6840
|
+
page,
|
|
6718
6841
|
processed.grid,
|
|
6719
6842
|
processed.slideWidth,
|
|
6720
6843
|
processed.slideHeight,
|
|
@@ -6732,8 +6855,10 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6732
6855
|
analyzedContentPaths,
|
|
6733
6856
|
paletteTokens,
|
|
6734
6857
|
authoredPropsAt,
|
|
6858
|
+
statedPropsAt,
|
|
6735
6859
|
authoredPath,
|
|
6736
|
-
addFact
|
|
6860
|
+
addFact,
|
|
6861
|
+
ownsProps
|
|
6737
6862
|
);
|
|
6738
6863
|
});
|
|
6739
6864
|
for (const budget of blockSlotBudgets(context.document, expanded.blocks)) {
|
|
@@ -6743,10 +6868,10 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6743
6868
|
...budget
|
|
6744
6869
|
});
|
|
6745
6870
|
}
|
|
6746
|
-
const
|
|
6871
|
+
const slotNodes = nodesBySlot(processed, slideIndexes, sourceMap);
|
|
6747
6872
|
for (const role of blockSlotRoles(context.document, expanded.blocks)) {
|
|
6748
|
-
const present = role.value !== void 0 && role.value !== null && role.value !== ""
|
|
6749
|
-
const bound =
|
|
6873
|
+
const present = role.value !== void 0 && role.value !== null && role.value !== false && (typeof role.value !== "string" || role.value.trim() !== "") && (!Array.isArray(role.value) || role.value.length > 0);
|
|
6874
|
+
const bound = slotNodes.get(role.path);
|
|
6750
6875
|
let measured;
|
|
6751
6876
|
if (bound && typeof bound.props.text === "string") {
|
|
6752
6877
|
const typography = resolveTypography(bound.props, ctx);
|
|
@@ -6779,10 +6904,35 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6779
6904
|
slot: role.slot,
|
|
6780
6905
|
role: role.role,
|
|
6781
6906
|
present,
|
|
6907
|
+
...bound && { nodePath: bound.path },
|
|
6782
6908
|
...typeof role.value === "string" && { text: role.value },
|
|
6783
6909
|
...measured
|
|
6784
6910
|
});
|
|
6785
6911
|
}
|
|
6912
|
+
const footerType = typeof processed.theme.chrome?.confidentialFooter?.type === "string" ? processed.theme.chrome.confidentialFooter.type : "footer";
|
|
6913
|
+
processed.slides.forEach((slide, renderedIndex) => {
|
|
6914
|
+
const authoredIndex = slideIndexes[renderedIndex];
|
|
6915
|
+
if (authoredIndex === void 0) return;
|
|
6916
|
+
const slidePath = `/children/${authoredIndex}`;
|
|
6917
|
+
const onSlide = (fact) => "slidePath" in fact && fact.slidePath === slidePath;
|
|
6918
|
+
const texts = facts.filter(
|
|
6919
|
+
(fact) => (fact.kind === "pptx/text" || fact.kind === "pptx/rich-text") && onSlide(fact)
|
|
6920
|
+
);
|
|
6921
|
+
addFact({
|
|
6922
|
+
id: `pptx:slide-chrome:${slidePath}`,
|
|
6923
|
+
kind: "pptx/slide-chrome",
|
|
6924
|
+
path: slidePath,
|
|
6925
|
+
index: renderedIndex,
|
|
6926
|
+
...slide.hidden === true && { hidden: true },
|
|
6927
|
+
blocks: expanded.blocks.filter(
|
|
6928
|
+
(pointer) => pointer.startsWith(`${slidePath}/children/`)
|
|
6929
|
+
).length,
|
|
6930
|
+
pageNumber: texts.some((fact) => fact.text.includes("{PAGE_NUMBER}")),
|
|
6931
|
+
footer: texts.some((fact) => fact.styleName === footerType) || facts.some(
|
|
6932
|
+
(fact) => fact.kind === "pptx/chrome-slot" && fact.role === "footer" && fact.present && onSlide(fact)
|
|
6933
|
+
)
|
|
6934
|
+
});
|
|
6935
|
+
});
|
|
6786
6936
|
return {
|
|
6787
6937
|
format: "pptx",
|
|
6788
6938
|
model: {
|
|
@@ -6805,14 +6955,18 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6805
6955
|
}
|
|
6806
6956
|
};
|
|
6807
6957
|
}
|
|
6808
|
-
function
|
|
6958
|
+
function nodesBySlot(processed, slideIndexes, sourceMap) {
|
|
6809
6959
|
const found = /* @__PURE__ */ new Map();
|
|
6810
6960
|
const visit = (component, path4) => {
|
|
6811
6961
|
if (component.enabled === false) return;
|
|
6812
6962
|
if (component.name === "text") {
|
|
6813
6963
|
const origin = toAuthoredPointer(sourceMap, `${path4}/props/text`);
|
|
6814
6964
|
if (origin !== `${path4}/props/text` && !found.has(origin))
|
|
6815
|
-
found.set(origin, { props: asRecord(component.props) ?? {} });
|
|
6965
|
+
found.set(origin, { props: asRecord(component.props) ?? {}, path: path4 });
|
|
6966
|
+
} else {
|
|
6967
|
+
const origin = toAuthoredPointer(sourceMap, path4);
|
|
6968
|
+
if (origin !== path4 && origin.includes("/props/slots/") && !found.has(origin))
|
|
6969
|
+
found.set(origin, { props: asRecord(component.props) ?? {}, path: path4 });
|
|
6816
6970
|
}
|
|
6817
6971
|
(component.children ?? []).forEach(
|
|
6818
6972
|
(child, index) => visit(child, `${path4}/children/${index}`)
|
|
@@ -7571,6 +7725,8 @@ import {
|
|
|
7571
7725
|
// src/quality/rules.ts
|
|
7572
7726
|
import {
|
|
7573
7727
|
chartInfoDesignFindings,
|
|
7728
|
+
configurationLabel,
|
|
7729
|
+
configurationSource,
|
|
7574
7730
|
DEFAULT_MAXIMUM_CHART_SERIES,
|
|
7575
7731
|
DEFAULT_MAXIMUM_PIE_SLICES,
|
|
7576
7732
|
fontCountFinding,
|
|
@@ -7711,19 +7867,24 @@ var pptxMinimumFontRule = {
|
|
|
7711
7867
|
);
|
|
7712
7868
|
return textFacts(facts).filter((fact) => fact.fontSizePt < minimum).map((fact) => ({
|
|
7713
7869
|
message: `Effective font size is ${fact.fontSizePt}pt \u2014 unreadable on a projected slide.`,
|
|
7714
|
-
|
|
7870
|
+
// Text a definition or a plugin generated reports at the invocation
|
|
7871
|
+
// the author wrote, whose props are the block's or the plugin's: no
|
|
7872
|
+
// fontSize of theirs to lift, so no patch.
|
|
7873
|
+
path: fact.ownsProps ? `${fact.path}/props` : fact.path,
|
|
7715
7874
|
suggestion: `Use at least ${minimum}pt; captions rarely work below 10pt.`,
|
|
7716
7875
|
context: { fontSize: fact.fontSizePt, threshold: minimum },
|
|
7717
7876
|
evidence: { actual: fact.fontSizePt, expected: minimum, unit: "pt" },
|
|
7718
7877
|
// `add` replaces an existing member, so this lifts an explicit
|
|
7719
7878
|
// fontSize and overrides an inherited style value alike.
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7879
|
+
...fact.ownsProps && {
|
|
7880
|
+
fixes: [
|
|
7881
|
+
{
|
|
7882
|
+
op: "add",
|
|
7883
|
+
path: `${fact.path}/props/fontSize`,
|
|
7884
|
+
value: minimum
|
|
7885
|
+
}
|
|
7886
|
+
]
|
|
7887
|
+
}
|
|
7727
7888
|
}));
|
|
7728
7889
|
}
|
|
7729
7890
|
};
|
|
@@ -8225,7 +8386,8 @@ var pptxOffCanvasRule = {
|
|
|
8225
8386
|
code: QUALITY_CODES.OFF_CANVAS,
|
|
8226
8387
|
category: "integrity",
|
|
8227
8388
|
defaultSeverity: "warning",
|
|
8228
|
-
|
|
8389
|
+
// The box is measured; the ink inside it is the width model's estimate.
|
|
8390
|
+
defaultCertainty: "estimated",
|
|
8229
8391
|
formats: ["pptx"],
|
|
8230
8392
|
defaultParameters: {
|
|
8231
8393
|
tolerancePt: DEFAULT_OFF_CANVAS_TOLERANCE_PT,
|
|
@@ -8326,13 +8488,67 @@ var pptxRequiredChromeRule = {
|
|
|
8326
8488
|
if (required.length === 0) return [];
|
|
8327
8489
|
return facts.filter(
|
|
8328
8490
|
(fact) => fact.kind === "pptx/chrome-slot"
|
|
8329
|
-
).filter((fact) => required.includes(fact.role) && !fact.present).map((fact) =>
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8491
|
+
).filter((fact) => required.includes(fact.role) && !fact.present).map((fact) => {
|
|
8492
|
+
const source = configurationSource(configuration, "required");
|
|
8493
|
+
return {
|
|
8494
|
+
path: fact.path,
|
|
8495
|
+
relatedPaths: [fact.invocation],
|
|
8496
|
+
message: `${fact.block} states no ${fact.role} in its "${fact.slot}" slot; ${configurationLabel(source, profile)} expects one on every ${fact.block}.`,
|
|
8497
|
+
suggestion: `Fill the "${fact.slot}" slot. The theme already styles it.`,
|
|
8498
|
+
context: { block: fact.block, slot: fact.slot, role: fact.role },
|
|
8499
|
+
evidence: {
|
|
8500
|
+
actual: "empty",
|
|
8501
|
+
expected: fact.role,
|
|
8502
|
+
values: { source, required }
|
|
8503
|
+
}
|
|
8504
|
+
};
|
|
8505
|
+
});
|
|
8506
|
+
}
|
|
8507
|
+
};
|
|
8508
|
+
var SLIDE_CHROME_PARTS = ["pageNumber", "footer"];
|
|
8509
|
+
var pptxSlideFooterRule = {
|
|
8510
|
+
id: "pptx/slide-footer",
|
|
8511
|
+
description: "A block-built slide without the running chrome a profile or policy expects: a page number, a footer line. Off until one names parts.",
|
|
8512
|
+
code: QUALITY_CODES.CHROME_MISSING,
|
|
8513
|
+
category: "consistency",
|
|
8514
|
+
defaultSeverity: "warning",
|
|
8515
|
+
defaultCertainty: "deterministic",
|
|
8516
|
+
formats: ["pptx"],
|
|
8517
|
+
defaultParameters: { required: [], fromSlide: 1 },
|
|
8518
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
8519
|
+
const required = stringListParameter(
|
|
8520
|
+
configuration.parameters,
|
|
8521
|
+
"required"
|
|
8522
|
+
).filter(
|
|
8523
|
+
(part) => SLIDE_CHROME_PARTS.includes(part)
|
|
8524
|
+
);
|
|
8525
|
+
if (required.length === 0) return [];
|
|
8526
|
+
const from = numberParameter(configuration.parameters, "fromSlide", 1);
|
|
8527
|
+
return facts.filter(
|
|
8528
|
+
(fact) => fact.kind === "pptx/slide-chrome" && fact.blocks > 0 && fact.hidden !== true && fact.index >= from
|
|
8529
|
+
).flatMap((fact) => {
|
|
8530
|
+
const missing = required.filter((part) => !fact[part]);
|
|
8531
|
+
if (missing.length === 0) return [];
|
|
8532
|
+
const parts = missing.map((part) => part === "pageNumber" ? "page number" : "footer").join(" or ");
|
|
8533
|
+
const source = configurationSource(
|
|
8534
|
+
configuration,
|
|
8535
|
+
"required",
|
|
8536
|
+
"fromSlide"
|
|
8537
|
+
);
|
|
8538
|
+
return [
|
|
8539
|
+
{
|
|
8540
|
+
path: fact.path,
|
|
8541
|
+
message: `Slide ${fact.index + 1} carries no ${parts}; ${configurationLabel(source, profile)} expects one on every slide a block builds from slide ${from + 1} on.`,
|
|
8542
|
+
suggestion: "Build the slide with a block that draws the running footer \u2014 the house blocks set {PAGE_NUMBER} / {PAGE_COUNT} in the footer role \u2014 or add that text to the definition the slide invokes.",
|
|
8543
|
+
context: { slide: fact.index, missing },
|
|
8544
|
+
evidence: {
|
|
8545
|
+
actual: required.filter((part) => fact[part]),
|
|
8546
|
+
expected: required,
|
|
8547
|
+
values: { source, fromSlide: from }
|
|
8548
|
+
}
|
|
8549
|
+
}
|
|
8550
|
+
];
|
|
8551
|
+
});
|
|
8336
8552
|
}
|
|
8337
8553
|
};
|
|
8338
8554
|
var pptxActionTitleRule = {
|
|
@@ -8365,23 +8581,49 @@ var pptxActionTitleRule = {
|
|
|
8365
8581
|
}
|
|
8366
8582
|
};
|
|
8367
8583
|
var TITLE_DRIFT_TOLERANCE_PT = 2;
|
|
8584
|
+
var FIRST_BASELINE_EM = 0.8;
|
|
8585
|
+
var TITLE_PLACEMENT_LINES = 2;
|
|
8368
8586
|
var PPTX_TYPE_VOCABULARY = {
|
|
8369
8587
|
subject: "deck",
|
|
8370
8588
|
keepTo: "Keep to the theme styles \u2014 title, heading, body, label, statistic \u2014 and drop the ad-hoc sizes."
|
|
8371
8589
|
};
|
|
8590
|
+
var PPTX_SLIDE_TYPE_VOCABULARY = {
|
|
8591
|
+
subject: "slide",
|
|
8592
|
+
keepTo: "Give the slide fewer levels: let the title, the body and one supporting size carry it, in the theme styles."
|
|
8593
|
+
};
|
|
8372
8594
|
function pptxThemeFact(facts) {
|
|
8373
8595
|
return facts.find(
|
|
8374
8596
|
(fact) => fact.kind === "pptx/theme"
|
|
8375
8597
|
);
|
|
8376
8598
|
}
|
|
8377
8599
|
function paintedSizes(facts) {
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8600
|
+
const sizes = [];
|
|
8601
|
+
for (const fact of textFacts(facts)) {
|
|
8602
|
+
if (fact.slideHidden) continue;
|
|
8603
|
+
sizes.push({
|
|
8604
|
+
path: fact.path,
|
|
8605
|
+
slidePath: fact.slidePath,
|
|
8606
|
+
...fact.styleName !== void 0 && { role: fact.styleName },
|
|
8607
|
+
fontSizePt: fact.fontSizePt,
|
|
8608
|
+
...fact.sizePath !== void 0 && { sizePath: fact.sizePath },
|
|
8609
|
+
generated: fact.generated
|
|
8610
|
+
});
|
|
8611
|
+
}
|
|
8612
|
+
for (const fact of facts) {
|
|
8613
|
+
if (fact.kind !== "pptx/rich-text") continue;
|
|
8614
|
+
const rich = fact;
|
|
8615
|
+
if (rich.slideHidden) continue;
|
|
8616
|
+
for (const run of rich.runSizes)
|
|
8617
|
+
sizes.push({
|
|
8618
|
+
path: rich.path,
|
|
8619
|
+
slidePath: rich.slidePath,
|
|
8620
|
+
...rich.styleName !== void 0 && { role: rich.styleName },
|
|
8621
|
+
fontSizePt: run.fontSizePt,
|
|
8622
|
+
...run.sizePath !== void 0 && { sizePath: run.sizePath },
|
|
8623
|
+
generated: rich.generated
|
|
8624
|
+
});
|
|
8625
|
+
}
|
|
8626
|
+
return sizes;
|
|
8385
8627
|
}
|
|
8386
8628
|
var pptxTypeScaleRule = {
|
|
8387
8629
|
id: "pptx/type-scale",
|
|
@@ -8407,21 +8649,56 @@ var pptxTypeScaleRule = {
|
|
|
8407
8649
|
};
|
|
8408
8650
|
var pptxSizeCountRule = {
|
|
8409
8651
|
id: "pptx/size-count",
|
|
8410
|
-
description: "More distinct text sizes than maximumSizes allows, blocks included. Off until a profile or policy enables it.",
|
|
8652
|
+
description: "More distinct text sizes than maximumSizes allows across the deck, or than maximumSizesPerSlide allows on one slide (0: no per-slide ceiling), blocks and runs included. Off until a profile or policy enables it.",
|
|
8411
8653
|
code: QUALITY_CODES.TYPE_SIZE_COUNT,
|
|
8412
8654
|
category: "consistency",
|
|
8413
8655
|
defaultSeverity: "warning",
|
|
8414
8656
|
defaultCertainty: "deterministic",
|
|
8415
8657
|
formats: ["pptx"],
|
|
8416
8658
|
defaultEnabled: false,
|
|
8417
|
-
defaultParameters: { maximumSizes: 8 },
|
|
8418
|
-
evaluate: ({ facts, configuration, profile }) =>
|
|
8419
|
-
paintedSizes(facts)
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8659
|
+
defaultParameters: { maximumSizes: 8, maximumSizesPerSlide: 0 },
|
|
8660
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
8661
|
+
const sizes = paintedSizes(facts);
|
|
8662
|
+
const setBy = (parameter) => {
|
|
8663
|
+
const source = configurationSource(configuration, parameter);
|
|
8664
|
+
return { source, label: configurationLabel(source, profile) };
|
|
8665
|
+
};
|
|
8666
|
+
const findings = sizeCountFinding(
|
|
8667
|
+
sizes,
|
|
8668
|
+
numberParameter(configuration.parameters, "maximumSizes", 8),
|
|
8669
|
+
pptxThemeFact(facts)?.path ?? "/props",
|
|
8670
|
+
setBy("maximumSizes"),
|
|
8671
|
+
PPTX_TYPE_VOCABULARY
|
|
8672
|
+
).map((finding) => ({
|
|
8673
|
+
...finding,
|
|
8674
|
+
context: { ...finding.context, scope: "deck" }
|
|
8675
|
+
}));
|
|
8676
|
+
const perSlide = numberParameter(
|
|
8677
|
+
configuration.parameters,
|
|
8678
|
+
"maximumSizesPerSlide",
|
|
8679
|
+
0
|
|
8680
|
+
);
|
|
8681
|
+
if (perSlide <= 0) return findings;
|
|
8682
|
+
const bySlide = /* @__PURE__ */ new Map();
|
|
8683
|
+
for (const size of sizes)
|
|
8684
|
+
bySlide.set(size.slidePath, [
|
|
8685
|
+
...bySlide.get(size.slidePath) ?? [],
|
|
8686
|
+
size
|
|
8687
|
+
]);
|
|
8688
|
+
for (const [slidePath, onSlide] of bySlide)
|
|
8689
|
+
for (const finding of sizeCountFinding(
|
|
8690
|
+
onSlide,
|
|
8691
|
+
perSlide,
|
|
8692
|
+
slidePath,
|
|
8693
|
+
setBy("maximumSizesPerSlide"),
|
|
8694
|
+
PPTX_SLIDE_TYPE_VOCABULARY
|
|
8695
|
+
))
|
|
8696
|
+
findings.push({
|
|
8697
|
+
...finding,
|
|
8698
|
+
context: { ...finding.context, scope: "slide" }
|
|
8699
|
+
});
|
|
8700
|
+
return findings;
|
|
8701
|
+
}
|
|
8425
8702
|
};
|
|
8426
8703
|
var pptxRoleDriftRule = {
|
|
8427
8704
|
id: "pptx/role-drift",
|
|
@@ -8437,53 +8714,107 @@ var pptxRoleDriftRule = {
|
|
|
8437
8714
|
return theme ? roleDriftFindings(paintedSizes(facts), theme.roleSizesPt) : [];
|
|
8438
8715
|
}
|
|
8439
8716
|
};
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8717
|
+
var EDGE_NAMES = {
|
|
8718
|
+
left: "left edge",
|
|
8719
|
+
center: "centre line",
|
|
8720
|
+
right: "right edge"
|
|
8721
|
+
};
|
|
8722
|
+
function laidOutTitle(fact, path4, order) {
|
|
8723
|
+
if (fact.boxXPt === void 0 || fact.boxYPt === void 0 || fact.boxWidthPt === void 0 || fact.rotationDeg % 360 !== 0)
|
|
8724
|
+
return void 0;
|
|
8725
|
+
const [top, right, bottom, left] = fact.insetsPt;
|
|
8726
|
+
const innerWidth = Math.max(1, fact.boxWidthPt - left - right);
|
|
8727
|
+
const { heightPt, lines } = estimateTextHeightPt(
|
|
8728
|
+
fact.text,
|
|
8729
|
+
innerWidth,
|
|
8730
|
+
fact.fontSizePt,
|
|
8731
|
+
fact.lineSpacingPt,
|
|
8732
|
+
fact.paraSpaceBeforePt,
|
|
8733
|
+
fact.paraSpaceAfterPt
|
|
8734
|
+
);
|
|
8735
|
+
let textTop = fact.boxYPt + top;
|
|
8736
|
+
if (fact.boxHeightPt !== void 0 && !fact.autoFit) {
|
|
8737
|
+
const room = fact.boxHeightPt - top - bottom - heightPt;
|
|
8738
|
+
if (fact.verticalAlign === "middle") textTop += room / 2;
|
|
8739
|
+
else if (fact.verticalAlign === "bottom") textTop += room;
|
|
8740
|
+
}
|
|
8741
|
+
const align = fact.align === "justify" ? "left" : fact.align;
|
|
8742
|
+
const edgePt = align === "center" ? fact.boxXPt + left + innerWidth / 2 : align === "right" ? fact.boxXPt + fact.boxWidthPt - right : fact.boxXPt + left;
|
|
8743
|
+
const style = fact.styleName ?? "unstyled";
|
|
8744
|
+
const setSizePt = fact.fitFromPt ?? fact.fontSizePt;
|
|
8745
|
+
return {
|
|
8746
|
+
fact,
|
|
8747
|
+
path: path4,
|
|
8748
|
+
kind: `${style}|${setSizePt}|${align}`,
|
|
8749
|
+
style,
|
|
8750
|
+
setSizePt,
|
|
8751
|
+
align,
|
|
8752
|
+
lines,
|
|
8753
|
+
baselinePt: textTop + FIRST_BASELINE_EM * fact.fontSizePt,
|
|
8754
|
+
edgePt,
|
|
8755
|
+
order
|
|
8756
|
+
};
|
|
8757
|
+
}
|
|
8758
|
+
function deckTitles(facts, styles) {
|
|
8759
|
+
const texts = textFacts(facts).filter((fact) => !fact.slideHidden);
|
|
8760
|
+
const slideIndex = (fact) => Number(/^\/children\/(\d+)/.exec(fact.slidePath)?.[1] ?? 0);
|
|
8761
|
+
const order = new Map(
|
|
8762
|
+
texts.map((fact, index) => [fact, slideIndex(fact) * 1e4 + index])
|
|
8443
8763
|
);
|
|
8444
|
-
const
|
|
8764
|
+
const byNode = new Map(texts.map((fact) => [fact.nodePath, fact]));
|
|
8765
|
+
const titles = [];
|
|
8445
8766
|
const claimed = /* @__PURE__ */ new Set();
|
|
8446
|
-
const
|
|
8767
|
+
const add = (fact, path4) => {
|
|
8447
8768
|
if (claimed.has(fact)) return;
|
|
8448
8769
|
claimed.add(fact);
|
|
8449
|
-
|
|
8770
|
+
const title = laidOutTitle(fact, path4, order.get(fact));
|
|
8771
|
+
if (title) titles.push(title);
|
|
8450
8772
|
};
|
|
8451
|
-
for (const slot of facts)
|
|
8452
|
-
if (slot.kind
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
const painted = texts.find(
|
|
8457
|
-
(fact) => fact.slidePath === chrome.slidePath && fact.text === chrome.text
|
|
8458
|
-
);
|
|
8459
|
-
if (painted) push(chrome.block, painted);
|
|
8460
|
-
}
|
|
8773
|
+
for (const slot of facts)
|
|
8774
|
+
if (slot.kind === "pptx/chrome-slot" && slot.role === "actionTitle" && slot.nodePath !== void 0) {
|
|
8775
|
+
const painted = byNode.get(slot.nodePath);
|
|
8776
|
+
if (painted) add(painted, slot.path);
|
|
8777
|
+
}
|
|
8461
8778
|
for (const fact of texts)
|
|
8462
8779
|
if (fact.styleName !== void 0 && styles.includes(fact.styleName))
|
|
8463
|
-
|
|
8464
|
-
return
|
|
8465
|
-
}
|
|
8466
|
-
function
|
|
8467
|
-
const
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8780
|
+
add(fact, fact.path);
|
|
8781
|
+
return titles.sort((a, b) => a.order - b.order);
|
|
8782
|
+
}
|
|
8783
|
+
function titlePlacements(titles, tolerance) {
|
|
8784
|
+
const lands = (a, b) => Math.abs(a.baselinePt - b.baselinePt) <= tolerance && Math.abs(a.edgePt - b.edgePt) <= tolerance;
|
|
8785
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
8786
|
+
for (const title of titles)
|
|
8787
|
+
byKind.set(title.kind, [...byKind.get(title.kind) ?? [], title]);
|
|
8788
|
+
const placements = [];
|
|
8789
|
+
for (const kind of byKind.values()) {
|
|
8790
|
+
let pending = kind;
|
|
8791
|
+
while (pending.length > 0) {
|
|
8792
|
+
let reference = pending[0];
|
|
8793
|
+
let most = 0;
|
|
8794
|
+
for (const candidate of pending) {
|
|
8795
|
+
const count = pending.filter((other) => lands(other, candidate)).length;
|
|
8796
|
+
if (count > most) {
|
|
8797
|
+
reference = candidate;
|
|
8798
|
+
most = count;
|
|
8799
|
+
}
|
|
8800
|
+
}
|
|
8801
|
+
const reach = TITLE_PLACEMENT_LINES * reference.fact.lineSpacingPt;
|
|
8802
|
+
const members = pending.filter(
|
|
8803
|
+
(title) => Math.abs(title.baselinePt - reference.baselinePt) <= reach && Math.abs(title.edgePt - reference.edgePt) <= reach
|
|
8804
|
+
);
|
|
8805
|
+
placements.push({ reference, members });
|
|
8806
|
+
pending = pending.filter((title) => !members.includes(title));
|
|
8476
8807
|
}
|
|
8477
8808
|
}
|
|
8478
|
-
return
|
|
8809
|
+
return placements;
|
|
8479
8810
|
}
|
|
8480
8811
|
var pptxTitleDriftRule = {
|
|
8481
8812
|
id: "pptx/title-drift",
|
|
8482
|
-
description: "A slide title
|
|
8813
|
+
description: "A slide title whose laid-out first baseline or aligned edge is away from where the deck\u2019s other titles of that kind land. Off until a profile or policy enables it.",
|
|
8483
8814
|
code: QUALITY_CODES.TITLE_DRIFT,
|
|
8484
8815
|
category: "consistency",
|
|
8485
8816
|
defaultSeverity: "warning",
|
|
8486
|
-
defaultCertainty: "
|
|
8817
|
+
defaultCertainty: "estimated",
|
|
8487
8818
|
formats: ["pptx"],
|
|
8488
8819
|
defaultEnabled: false,
|
|
8489
8820
|
defaultParameters: {
|
|
@@ -8498,40 +8829,53 @@ var pptxTitleDriftRule = {
|
|
|
8498
8829
|
TITLE_DRIFT_TOLERANCE_PT
|
|
8499
8830
|
);
|
|
8500
8831
|
const findings = [];
|
|
8501
|
-
for (const
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8832
|
+
for (const { reference, members } of titlePlacements(
|
|
8833
|
+
deckTitles(facts, styles),
|
|
8834
|
+
tolerance
|
|
8835
|
+
)) {
|
|
8836
|
+
if (members.length < 2) continue;
|
|
8837
|
+
const agreeing = members.filter(
|
|
8838
|
+
(title) => Math.abs(title.baselinePt - reference.baselinePt) <= tolerance && Math.abs(title.edgePt - reference.edgePt) <= tolerance
|
|
8839
|
+
);
|
|
8840
|
+
for (const title of members) {
|
|
8841
|
+
if (agreeing.includes(title)) continue;
|
|
8842
|
+
const edge = EDGE_NAMES[title.align];
|
|
8843
|
+
const off = [
|
|
8844
|
+
{
|
|
8845
|
+
axis: "first baseline",
|
|
8846
|
+
actual: title.baselinePt,
|
|
8847
|
+
expected: reference.baselinePt
|
|
8848
|
+
},
|
|
8849
|
+
{ axis: edge, actual: title.edgePt, expected: reference.edgePt }
|
|
8850
|
+
].filter(
|
|
8851
|
+
(entry) => Math.abs(entry.actual - entry.expected) > tolerance
|
|
8518
8852
|
);
|
|
8519
|
-
if (off.length === 0) continue;
|
|
8520
8853
|
const [first] = off;
|
|
8854
|
+
const size = title.fact.fontSizePt !== reference.fact.fontSizePt ? ` The fit pass sets it at ${title.fact.fontSizePt}pt, where they set at ${reference.fact.fontSizePt}pt.` : "";
|
|
8521
8855
|
findings.push({
|
|
8522
|
-
path:
|
|
8523
|
-
|
|
8524
|
-
|
|
8856
|
+
path: title.path,
|
|
8857
|
+
relatedPaths: agreeing.map((other) => other.path),
|
|
8858
|
+
message: `This ${title.style} title lays out at ${off.map((entry) => `${entry.axis} ${round(entry.actual)}pt`).join(", ")}; the deck's other titles of that kind land at ${off.map((entry) => `${entry.axis} ${round(entry.expected)}pt`).join(", ")}.${size}`,
|
|
8859
|
+
suggestion: "Place every title of one kind with the same block or the same coordinates, anchor them alike, and keep each short enough to set at the size the others keep.",
|
|
8525
8860
|
context: {
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8861
|
+
style: title.style,
|
|
8862
|
+
setSizePt: title.setSizePt,
|
|
8863
|
+
align: title.align,
|
|
8864
|
+
axes: off.map((entry) => entry.axis),
|
|
8865
|
+
slide: title.fact.slidePath
|
|
8529
8866
|
},
|
|
8530
8867
|
evidence: {
|
|
8531
|
-
|
|
8532
|
-
|
|
8868
|
+
summary: "estimated first baseline and aligned edge",
|
|
8869
|
+
actual: round(first.actual),
|
|
8870
|
+
expected: round(first.expected),
|
|
8533
8871
|
unit: "pt",
|
|
8534
|
-
values: {
|
|
8872
|
+
values: {
|
|
8873
|
+
axis: first.axis,
|
|
8874
|
+
lines: title.lines,
|
|
8875
|
+
fontSizePt: title.fact.fontSizePt,
|
|
8876
|
+
verticalAlign: title.fact.verticalAlign,
|
|
8877
|
+
source: configurationSource(configuration, "tolerancePt")
|
|
8878
|
+
}
|
|
8535
8879
|
}
|
|
8536
8880
|
});
|
|
8537
8881
|
}
|
|
@@ -8584,7 +8928,9 @@ var pptxBulletRule = {
|
|
|
8584
8928
|
actual: bullets.items,
|
|
8585
8929
|
expected: maximumBullets,
|
|
8586
8930
|
unit: "bullets",
|
|
8587
|
-
values: {
|
|
8931
|
+
values: {
|
|
8932
|
+
source: configurationSource(configuration, "maximumBullets")
|
|
8933
|
+
}
|
|
8588
8934
|
}
|
|
8589
8935
|
});
|
|
8590
8936
|
if (maximumWords > 0 && bullets.longestWords > maximumWords)
|
|
@@ -8598,16 +8944,39 @@ var pptxBulletRule = {
|
|
|
8598
8944
|
actual: bullets.longestWords,
|
|
8599
8945
|
expected: maximumWords,
|
|
8600
8946
|
unit: "words",
|
|
8601
|
-
values: {
|
|
8947
|
+
values: {
|
|
8948
|
+
source: configurationSource(
|
|
8949
|
+
configuration,
|
|
8950
|
+
"maximumWordsPerBullet"
|
|
8951
|
+
)
|
|
8952
|
+
}
|
|
8602
8953
|
}
|
|
8603
8954
|
});
|
|
8604
8955
|
}
|
|
8605
8956
|
return findings;
|
|
8606
8957
|
}
|
|
8607
8958
|
};
|
|
8959
|
+
function safeAreaFixes(fact, canvas, safe) {
|
|
8960
|
+
const position = fact.authoredPositionIn;
|
|
8961
|
+
if (position === void 0 || fact.widthPt > canvas.widthPt - 2 * safe || fact.heightPt > canvas.heightPt - 2 * safe)
|
|
8962
|
+
return [];
|
|
8963
|
+
const clamp = (valuePt, sizePt, extentPt) => {
|
|
8964
|
+
const low = Math.ceil(safe / 72 * 1e3) / 1e3;
|
|
8965
|
+
const high = Math.floor((extentPt - safe - sizePt) / 72 * 1e3) / 1e3;
|
|
8966
|
+
return Math.min(Math.max(valuePt / 72, low), high);
|
|
8967
|
+
};
|
|
8968
|
+
const fixes = [];
|
|
8969
|
+
const x = clamp(fact.xPt, fact.widthPt, canvas.widthPt);
|
|
8970
|
+
const y = clamp(fact.yPt, fact.heightPt, canvas.heightPt);
|
|
8971
|
+
if (Math.abs(x - position.x) > 5e-4)
|
|
8972
|
+
fixes.push({ op: "replace", path: `${fact.path}/props/x`, value: x });
|
|
8973
|
+
if (Math.abs(y - position.y) > 5e-4)
|
|
8974
|
+
fixes.push({ op: "replace", path: `${fact.path}/props/y`, value: y });
|
|
8975
|
+
return fixes;
|
|
8976
|
+
}
|
|
8608
8977
|
var pptxSafeAreaRule = {
|
|
8609
8978
|
id: "pptx/safe-area",
|
|
8610
|
-
description: "Content outside the theme\u2019s safe area that is neither chrome nor a full bleed. Off until a profile or policy enables it.",
|
|
8979
|
+
description: "Content outside the theme\u2019s safe area that is neither chrome (a tracker, footer, source or logo) nor a full bleed. Off until a profile or policy enables it.",
|
|
8611
8980
|
code: QUALITY_CODES.SAFE_AREA,
|
|
8612
8981
|
category: "composition",
|
|
8613
8982
|
defaultSeverity: "warning",
|
|
@@ -8626,20 +8995,16 @@ var pptxSafeAreaRule = {
|
|
|
8626
8995
|
);
|
|
8627
8996
|
const safe = canvas?.safeAreaPt;
|
|
8628
8997
|
if (canvas === void 0 || safe === void 0 || safe <= 0) return [];
|
|
8629
|
-
const
|
|
8998
|
+
const chromeNodes = new Set(
|
|
8630
8999
|
facts.filter(
|
|
8631
|
-
(fact) => fact.kind === "pptx/chrome-slot" && (fact.role === "tracker" || fact.role === "footer")
|
|
8632
|
-
).
|
|
8633
|
-
);
|
|
8634
|
-
const chromeStyles = /* @__PURE__ */ new Set(["footer", "tracker"]);
|
|
8635
|
-
const styledChrome = new Set(
|
|
8636
|
-
textFacts(facts).filter(
|
|
8637
|
-
(fact) => fact.styleName !== void 0 && chromeStyles.has(fact.styleName)
|
|
8638
|
-
).map((fact) => fact.path)
|
|
9000
|
+
(fact) => fact.kind === "pptx/chrome-slot" && (fact.role === "tracker" || fact.role === "footer" || fact.role === "source" || fact.role === "logo")
|
|
9001
|
+
).flatMap((fact) => fact.nodePath ? [fact.nodePath] : [])
|
|
8639
9002
|
);
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
9003
|
+
const chromeStyles = /* @__PURE__ */ new Set(["footer", "tracker", "source"]);
|
|
9004
|
+
for (const fact of facts)
|
|
9005
|
+
if ((fact.kind === "pptx/text" || fact.kind === "pptx/rich-text") && fact.styleName !== void 0 && chromeStyles.has(fact.styleName))
|
|
9006
|
+
chromeNodes.add(fact.nodePath);
|
|
9007
|
+
return facts.filter((fact) => fact.kind === "pptx/box").filter((fact) => !chromeNodes.has(fact.nodePath)).flatMap((fact) => {
|
|
8643
9008
|
const right = fact.xPt + fact.widthPt;
|
|
8644
9009
|
const bottom = fact.yPt + fact.heightPt;
|
|
8645
9010
|
const spansWidth = fact.xPt <= tolerance && right >= canvas.widthPt - tolerance;
|
|
@@ -8658,6 +9023,7 @@ var pptxSafeAreaRule = {
|
|
|
8658
9023
|
right - (canvas.widthPt - safe),
|
|
8659
9024
|
bottom - (canvas.heightPt - safe)
|
|
8660
9025
|
);
|
|
9026
|
+
const fixes = safeAreaFixes(fact, canvas, safe);
|
|
8661
9027
|
return [
|
|
8662
9028
|
{
|
|
8663
9029
|
path: fact.path,
|
|
@@ -8669,7 +9035,8 @@ var pptxSafeAreaRule = {
|
|
|
8669
9035
|
expected: 0,
|
|
8670
9036
|
unit: "pt",
|
|
8671
9037
|
values: { source: "theme" }
|
|
8672
|
-
}
|
|
9038
|
+
},
|
|
9039
|
+
...fixes.length > 0 && { fixes }
|
|
8673
9040
|
}
|
|
8674
9041
|
];
|
|
8675
9042
|
});
|
|
@@ -8688,8 +9055,8 @@ var pptxSlideTitleRule = {
|
|
|
8688
9055
|
evaluate: ({ facts, configuration }) => {
|
|
8689
9056
|
const styles = stringListParameter(configuration.parameters, "titleStyles");
|
|
8690
9057
|
const titledSlides = /* @__PURE__ */ new Set();
|
|
8691
|
-
for (const fact of
|
|
8692
|
-
if (fact.styleName !== void 0 && styles.includes(fact.styleName))
|
|
9058
|
+
for (const fact of facts)
|
|
9059
|
+
if ((fact.kind === "pptx/text" || fact.kind === "pptx/rich-text") && fact.styleName !== void 0 && styles.includes(fact.styleName))
|
|
8693
9060
|
titledSlides.add(fact.slidePath);
|
|
8694
9061
|
for (const fact of facts)
|
|
8695
9062
|
if (fact.kind === "pptx/chrome-slot" && fact.role === "actionTitle" && fact.present)
|
|
@@ -8705,11 +9072,33 @@ var pptxSlideTitleRule = {
|
|
|
8705
9072
|
actual: 0,
|
|
8706
9073
|
expected: 1,
|
|
8707
9074
|
unit: "titles",
|
|
8708
|
-
values: { source:
|
|
9075
|
+
values: { source: configurationSource(configuration) }
|
|
8709
9076
|
}
|
|
8710
9077
|
}));
|
|
8711
9078
|
}
|
|
8712
9079
|
};
|
|
9080
|
+
var pptxFigureLabelRule = {
|
|
9081
|
+
id: "pptx/figure-label",
|
|
9082
|
+
description: "An image with no alt text, a background that bleeds off the slide aside. Off until a profile or policy enables it.",
|
|
9083
|
+
code: QUALITY_CODES.FIGURE_UNLABELLED,
|
|
9084
|
+
category: "accessibility",
|
|
9085
|
+
defaultSeverity: "warning",
|
|
9086
|
+
defaultCertainty: "deterministic",
|
|
9087
|
+
formats: ["pptx"],
|
|
9088
|
+
defaultEnabled: false,
|
|
9089
|
+
evaluate: ({ facts }) => {
|
|
9090
|
+
const unlabelled = /* @__PURE__ */ new Map();
|
|
9091
|
+
for (const fact of facts)
|
|
9092
|
+
if (fact.kind === "pptx/image" && fact.alt === void 0 && !fact.bleed)
|
|
9093
|
+
unlabelled.set(fact.path, fact);
|
|
9094
|
+
return [...unlabelled.values()].map((fact) => ({
|
|
9095
|
+
path: fact.path,
|
|
9096
|
+
message: "This image carries no alt text, so nothing in the deck says what it shows to a reader who cannot see it.",
|
|
9097
|
+
suggestion: "Write `alt` on the image: what it shows, in a sentence. A decorative background can bleed off the slide instead.",
|
|
9098
|
+
context: { slidePath: fact.slidePath }
|
|
9099
|
+
}));
|
|
9100
|
+
}
|
|
9101
|
+
};
|
|
8713
9102
|
var pptxImageAspectRule = {
|
|
8714
9103
|
id: "pptx/image-aspect",
|
|
8715
9104
|
description: "An image drawn at an aspect the asset does not have, where the asset can be read from the document.",
|
|
@@ -8732,7 +9121,20 @@ var pptxImageAspectRule = {
|
|
|
8732
9121
|
{
|
|
8733
9122
|
path: fact.path,
|
|
8734
9123
|
drawn: fact.drawnRatio,
|
|
8735
|
-
natural: fact.naturalRatio
|
|
9124
|
+
natural: fact.naturalRatio,
|
|
9125
|
+
...fact.authoredSizeIn && {
|
|
9126
|
+
sides: {
|
|
9127
|
+
width: {
|
|
9128
|
+
path: `${fact.path}/props/w`,
|
|
9129
|
+
value: fact.authoredSizeIn.w
|
|
9130
|
+
},
|
|
9131
|
+
height: {
|
|
9132
|
+
path: `${fact.path}/props/h`,
|
|
9133
|
+
value: fact.authoredSizeIn.h
|
|
9134
|
+
},
|
|
9135
|
+
decimals: 3
|
|
9136
|
+
}
|
|
9137
|
+
}
|
|
8736
9138
|
},
|
|
8737
9139
|
"slide",
|
|
8738
9140
|
tolerance
|
|
@@ -8758,6 +9160,7 @@ var PPTX_QUALITY_RULES = {
|
|
|
8758
9160
|
pptxOffCanvasRule,
|
|
8759
9161
|
pptxSlotBudgetRule,
|
|
8760
9162
|
pptxRequiredChromeRule,
|
|
9163
|
+
pptxSlideFooterRule,
|
|
8761
9164
|
pptxActionTitleRule,
|
|
8762
9165
|
pptxTypeScaleRule,
|
|
8763
9166
|
pptxSizeCountRule,
|
|
@@ -8766,6 +9169,7 @@ var PPTX_QUALITY_RULES = {
|
|
|
8766
9169
|
pptxBulletRule,
|
|
8767
9170
|
pptxSafeAreaRule,
|
|
8768
9171
|
pptxSlideTitleRule,
|
|
9172
|
+
pptxFigureLabelRule,
|
|
8769
9173
|
pptxImageAspectRule
|
|
8770
9174
|
]
|
|
8771
9175
|
};
|
|
@@ -8791,15 +9195,23 @@ var PPTX_QUALITY_PROFILES = {
|
|
|
8791
9195
|
"consulting-deck": {
|
|
8792
9196
|
id: "consulting-deck",
|
|
8793
9197
|
formats: ["pptx"],
|
|
8794
|
-
description: "Consulting readout: every content slide leads with a two-line action title, every chart carries a takeaway and a source, content stays inside the theme\u2019s safe area,
|
|
9198
|
+
description: "Consulting readout: every content slide leads with a two-line action title, every chart carries a takeaway and a source, every slide a block builds after the cover carries its page number, content stays inside the theme\u2019s safe area, a box holds at most five bullets of at most twelve words, every figure carries alt text, every size is on the theme scale with at most nine in the deck and six on a slide, and titles of one kind hold one line.",
|
|
8795
9199
|
rules: {
|
|
8796
9200
|
"pptx/required-chrome": {
|
|
8797
9201
|
parameters: { required: ["takeaway", "source"] }
|
|
8798
9202
|
},
|
|
9203
|
+
"pptx/slide-footer": { parameters: { required: ["pageNumber"] } },
|
|
8799
9204
|
"pptx/action-title": { parameters: { maxLines: 2 } },
|
|
8800
9205
|
"pptx/slide-density": { parameters: { maximumBodyWords: 90 } },
|
|
8801
9206
|
"pptx/type-scale": { enabled: true },
|
|
8802
|
-
|
|
9207
|
+
// Measured 2026-09-15, runs included: the house deck paints nine sizes
|
|
9208
|
+
// (9, 10, 12, 14, 16, 18, 28, 32, 40) and six on its busiest slide, and
|
|
9209
|
+
// so did every one of the 22 verification decks built from its blocks.
|
|
9210
|
+
// A size past these is one no house block paints.
|
|
9211
|
+
"pptx/size-count": {
|
|
9212
|
+
enabled: true,
|
|
9213
|
+
parameters: { maximumSizes: 9, maximumSizesPerSlide: 6 }
|
|
9214
|
+
},
|
|
8803
9215
|
"pptx/role-drift": { enabled: true },
|
|
8804
9216
|
"pptx/title-drift": {
|
|
8805
9217
|
enabled: true,
|
|
@@ -8809,7 +9221,8 @@ var PPTX_QUALITY_PROFILES = {
|
|
|
8809
9221
|
parameters: { maximumBullets: 5, maximumWordsPerBullet: 12 }
|
|
8810
9222
|
},
|
|
8811
9223
|
"pptx/safe-area": { enabled: true },
|
|
8812
|
-
"pptx/slide-title": { enabled: true }
|
|
9224
|
+
"pptx/slide-title": { enabled: true },
|
|
9225
|
+
"pptx/figure-label": { enabled: true }
|
|
8813
9226
|
}
|
|
8814
9227
|
}
|
|
8815
9228
|
};
|
|
@@ -9080,97 +9493,152 @@ function createBuilderImpl(state) {
|
|
|
9080
9493
|
newState
|
|
9081
9494
|
);
|
|
9082
9495
|
}
|
|
9083
|
-
async function
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9496
|
+
async function expandPresentation(document, options) {
|
|
9497
|
+
let internalDocument = document;
|
|
9498
|
+
const renderer = options?.renderer ?? state.renderer ?? internalDocument.renderer;
|
|
9499
|
+
const validationDocument = renderer === void 0 ? internalDocument : { ...internalDocument, renderer };
|
|
9500
|
+
const validationOptions = {
|
|
9501
|
+
...state.validation,
|
|
9502
|
+
...options?.validation
|
|
9503
|
+
};
|
|
9504
|
+
if (validationOptions.enabled !== false) {
|
|
9505
|
+
const result = validatePresentation(
|
|
9506
|
+
validationDocument,
|
|
9507
|
+
state.components,
|
|
9508
|
+
{ allowUnknownFields: validationOptions.allowUnknownFields }
|
|
9509
|
+
);
|
|
9510
|
+
if (!result.valid) {
|
|
9511
|
+
throw new ComponentValidationError3(result.errors, internalDocument);
|
|
9512
|
+
}
|
|
9513
|
+
} else if (!internalDocument || internalDocument.name !== "pptx") {
|
|
9514
|
+
throw new Error("Top-level component must be a pptx component");
|
|
9515
|
+
}
|
|
9516
|
+
const warnings = [];
|
|
9517
|
+
const context = resolveThemeContext(internalDocument, {
|
|
9518
|
+
customThemes: state.customThemes,
|
|
9519
|
+
fonts: state.fonts,
|
|
9520
|
+
warnings,
|
|
9521
|
+
defaultThemeName: typeof state.theme === "string" ? state.theme : void 0,
|
|
9522
|
+
resolveNamedTheme: (name, authored) => state.customThemes?.[name] ?? (authored && hasPptxTheme(name) ? getPptxTheme(name) : void 0) ?? (typeof state.theme === "object" && state.theme !== null ? state.theme : getPptxTheme(name))
|
|
9523
|
+
});
|
|
9524
|
+
const modedRoot = context.document;
|
|
9525
|
+
const resolvedTheme = context.theme;
|
|
9526
|
+
const validateEmitted = validationOptions.enabled === false ? void 0 : (emitted, componentLabel, parentName) => {
|
|
9527
|
+
let validationDocument2;
|
|
9528
|
+
if (parentName === "pptx") {
|
|
9529
|
+
validationDocument2 = {
|
|
9530
|
+
...modedRoot,
|
|
9531
|
+
...renderer !== void 0 ? { renderer } : {},
|
|
9532
|
+
children: emitted
|
|
9533
|
+
};
|
|
9534
|
+
} else if (parentName === "slide") {
|
|
9535
|
+
validationDocument2 = {
|
|
9536
|
+
...modedRoot,
|
|
9537
|
+
...renderer !== void 0 ? { renderer } : {},
|
|
9538
|
+
children: [{ name: "slide", props: {}, children: emitted }]
|
|
9539
|
+
};
|
|
9540
|
+
} else {
|
|
9541
|
+
return;
|
|
9542
|
+
}
|
|
9543
|
+
const result = validatePresentation(
|
|
9544
|
+
validationDocument2,
|
|
9545
|
+
state.components,
|
|
9546
|
+
{ allowUnknownFields: validationOptions.allowUnknownFields }
|
|
9547
|
+
);
|
|
9548
|
+
if (!result.valid) {
|
|
9549
|
+
throw new ComponentValidationError3(
|
|
9550
|
+
result.errors.map((error) => ({
|
|
9551
|
+
...error,
|
|
9552
|
+
message: `custom component '${componentLabel}' emitted invalid output \u2014 ${error.message}`
|
|
9553
|
+
})),
|
|
9554
|
+
emitted
|
|
9097
9555
|
);
|
|
9098
|
-
|
|
9099
|
-
|
|
9556
|
+
}
|
|
9557
|
+
};
|
|
9558
|
+
const expanded = await expandPptxBlocksWithPlugins(
|
|
9559
|
+
modedRoot,
|
|
9560
|
+
resolvedTheme,
|
|
9561
|
+
new Set(componentMap.keys()),
|
|
9562
|
+
pluginRenderer(warnings, resolvedTheme, validateEmitted)
|
|
9563
|
+
);
|
|
9564
|
+
const processedDocument = expanded.document;
|
|
9565
|
+
if (validationOptions.enabled !== false) {
|
|
9566
|
+
const result = validatePresentation(
|
|
9567
|
+
{
|
|
9568
|
+
...processedDocument,
|
|
9569
|
+
...renderer !== void 0 ? { renderer } : {}
|
|
9570
|
+
},
|
|
9571
|
+
[],
|
|
9572
|
+
{
|
|
9573
|
+
allowUnknownFields: validationOptions.allowUnknownFields
|
|
9100
9574
|
}
|
|
9101
|
-
|
|
9102
|
-
|
|
9575
|
+
);
|
|
9576
|
+
if (!result.valid) {
|
|
9577
|
+
throw new ComponentValidationError3(
|
|
9578
|
+
result.errors.map((error) => ({
|
|
9579
|
+
...error,
|
|
9580
|
+
message: `expanded plugin output failed validation \u2014 ${error.message}`
|
|
9581
|
+
})),
|
|
9582
|
+
processedDocument
|
|
9583
|
+
);
|
|
9103
9584
|
}
|
|
9104
|
-
|
|
9105
|
-
|
|
9585
|
+
}
|
|
9586
|
+
return { context, expanded, warnings, renderer };
|
|
9587
|
+
}
|
|
9588
|
+
async function prepareQuality(document, options) {
|
|
9589
|
+
const { context, expanded, warnings, renderer } = await expandPresentation(
|
|
9590
|
+
document,
|
|
9591
|
+
options
|
|
9592
|
+
);
|
|
9593
|
+
const prepared = preparePptxQualityDocument(
|
|
9594
|
+
document,
|
|
9595
|
+
{
|
|
9596
|
+
context,
|
|
9597
|
+
expanded,
|
|
9106
9598
|
customThemes: state.customThemes,
|
|
9107
9599
|
fonts: state.fonts,
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
}
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9600
|
+
services: state.services,
|
|
9601
|
+
...renderer !== void 0 && { renderer },
|
|
9602
|
+
warnings
|
|
9603
|
+
}
|
|
9604
|
+
);
|
|
9605
|
+
return { prepared, warnings };
|
|
9606
|
+
}
|
|
9607
|
+
async function generateFromPrepared(options) {
|
|
9608
|
+
const { prepared } = options;
|
|
9609
|
+
const { document: processedDocument, theme, processed } = prepared.model;
|
|
9610
|
+
const warnings = [];
|
|
9611
|
+
const renderer = options.renderer ?? state.renderer ?? prepared.renderer;
|
|
9612
|
+
const hasHighcharts = containsHighcharts(processed);
|
|
9613
|
+
const resolvedFonts = await resolveDocumentFonts(
|
|
9614
|
+
processedDocument,
|
|
9615
|
+
theme,
|
|
9616
|
+
warnings,
|
|
9617
|
+
state.fonts,
|
|
9618
|
+
hasHighcharts
|
|
9619
|
+
);
|
|
9620
|
+
const chartFonts = hasHighcharts ? toChartFontFaces2(resolvedFonts) : [];
|
|
9621
|
+
const buffer = await runWithBaseDir(
|
|
9622
|
+
options.baseDir ?? state.baseDir,
|
|
9623
|
+
() => renderProcessedViaIr(processed, warnings, {
|
|
9624
|
+
renderer,
|
|
9625
|
+
services: state.services,
|
|
9626
|
+
deterministic: options.deterministic ?? state.packaging.deterministic,
|
|
9627
|
+
generatedAt: options.generatedAt ?? state.packaging.generatedAt,
|
|
9628
|
+
...chartFonts.length > 0 ? { chartFonts } : {}
|
|
9629
|
+
})
|
|
9630
|
+
);
|
|
9631
|
+
return { buffer, warnings };
|
|
9632
|
+
}
|
|
9633
|
+
async function generate(document, options) {
|
|
9634
|
+
try {
|
|
9635
|
+
if (options?.prepared)
|
|
9636
|
+
return await generateFromPrepared(
|
|
9637
|
+
options
|
|
9135
9638
|
);
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
result.errors.map((error) => ({
|
|
9139
|
-
...error,
|
|
9140
|
-
message: `custom component '${componentLabel}' emitted invalid output \u2014 ${error.message}`
|
|
9141
|
-
})),
|
|
9142
|
-
emitted
|
|
9143
|
-
);
|
|
9144
|
-
}
|
|
9145
|
-
};
|
|
9146
|
-
const expanded = await expandPptxBlocksWithPlugins(
|
|
9147
|
-
modedRoot,
|
|
9148
|
-
resolvedTheme,
|
|
9149
|
-
new Set(componentMap.keys()),
|
|
9150
|
-
pluginRenderer(warnings, resolvedTheme, validateEmitted)
|
|
9151
|
-
);
|
|
9639
|
+
const { context, expanded, warnings, renderer } = await expandPresentation(document, options);
|
|
9640
|
+
const resolvedTheme = context.theme;
|
|
9152
9641
|
const processedDocument = expanded.document;
|
|
9153
|
-
if (validationOptions.enabled !== false) {
|
|
9154
|
-
const result = validatePresentation(
|
|
9155
|
-
{
|
|
9156
|
-
...processedDocument,
|
|
9157
|
-
...renderer !== void 0 ? { renderer } : {}
|
|
9158
|
-
},
|
|
9159
|
-
[],
|
|
9160
|
-
{
|
|
9161
|
-
allowUnknownFields: validationOptions.allowUnknownFields
|
|
9162
|
-
}
|
|
9163
|
-
);
|
|
9164
|
-
if (!result.valid) {
|
|
9165
|
-
throw new ComponentValidationError3(
|
|
9166
|
-
result.errors.map((error) => ({
|
|
9167
|
-
...error,
|
|
9168
|
-
message: `expanded plugin output failed validation \u2014 ${error.message}`
|
|
9169
|
-
})),
|
|
9170
|
-
processedDocument
|
|
9171
|
-
);
|
|
9172
|
-
}
|
|
9173
|
-
}
|
|
9174
9642
|
const processed = processPresentation(processedDocument, {
|
|
9175
9643
|
theme: resolvedTheme,
|
|
9176
9644
|
services: state.services,
|
|
@@ -9269,6 +9737,7 @@ function createBuilderImpl(state) {
|
|
|
9269
9737
|
generate,
|
|
9270
9738
|
generateBuffer: generate,
|
|
9271
9739
|
generateFile,
|
|
9740
|
+
prepareQuality,
|
|
9272
9741
|
getComponentNames,
|
|
9273
9742
|
validate,
|
|
9274
9743
|
generateSchema,
|