@json-to-office/core-pptx 6.0.0 → 6.2.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/core/expandHighcharts.d.ts.map +1 -1
- package/dist/index.js +557 -46
- package/dist/index.js.map +1 -1
- package/dist/quality/facts.d.ts +47 -1
- package/dist/quality/facts.d.ts.map +1 -1
- package/dist/quality/rules.d.ts +95 -1
- package/dist/quality/rules.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1014,8 +1014,8 @@ async function repairSvgRasterFallbacks(zip, warnings) {
|
|
|
1014
1014
|
const source = zip.file(item.svgPart);
|
|
1015
1015
|
if (!source) throw new Error(`missing part ${item.svgPart}`);
|
|
1016
1016
|
const svg = await source.async("string");
|
|
1017
|
-
const
|
|
1018
|
-
const fitTo = resolveFitTo(
|
|
1017
|
+
const probe3 = new Resvg(svg);
|
|
1018
|
+
const fitTo = resolveFitTo(probe3.width / probe3.height, item.cx, item.cy);
|
|
1019
1019
|
const key = `${fitTo.mode}:${fitTo.value}
|
|
1020
1020
|
${svg}`;
|
|
1021
1021
|
let png = rendered.get(key);
|
|
@@ -3937,12 +3937,11 @@ import {
|
|
|
3937
3937
|
REMOTE_EXPORT_WARNING,
|
|
3938
3938
|
chartFamilyResolver,
|
|
3939
3939
|
chartPointsPerPixel,
|
|
3940
|
-
chartRequestKey,
|
|
3941
|
-
dedupeChartRequest,
|
|
3942
|
-
limitChartRequest,
|
|
3943
3940
|
postJsonToService,
|
|
3941
|
+
recordChartCollected,
|
|
3944
3942
|
recordChartRetry,
|
|
3945
3943
|
resolveServiceUrl,
|
|
3944
|
+
sendChartRequest,
|
|
3946
3945
|
withChartFontFaceCss,
|
|
3947
3946
|
withChartTypography
|
|
3948
3947
|
} from "@json-to-office/shared";
|
|
@@ -4001,24 +4000,21 @@ async function expandList(components, path4, scope) {
|
|
|
4001
4000
|
}
|
|
4002
4001
|
async function expandOne(component, path4, scope) {
|
|
4003
4002
|
const props = component.props;
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
withThemeTypography(
|
|
4010
|
-
withThemeColors(props, scope.theme, scope.warnings),
|
|
4011
|
-
scope.theme,
|
|
4012
|
-
scope.slideWidth,
|
|
4013
|
-
scope.warnings
|
|
4014
|
-
),
|
|
4003
|
+
recordChartCollected();
|
|
4004
|
+
const chart = await renderChart(
|
|
4005
|
+
withChartFontFaces(
|
|
4006
|
+
withThemeTypography(
|
|
4007
|
+
withThemeColors(props, scope.theme, scope.warnings),
|
|
4015
4008
|
scope.theme,
|
|
4016
|
-
scope.
|
|
4009
|
+
scope.slideWidth,
|
|
4010
|
+
scope.warnings
|
|
4017
4011
|
),
|
|
4018
|
-
scope.
|
|
4019
|
-
scope.
|
|
4020
|
-
|
|
4021
|
-
|
|
4012
|
+
scope.theme,
|
|
4013
|
+
scope.chartFonts
|
|
4014
|
+
),
|
|
4015
|
+
scope.services,
|
|
4016
|
+
scope.warnings,
|
|
4017
|
+
scope.chartCache
|
|
4022
4018
|
);
|
|
4023
4019
|
void path4;
|
|
4024
4020
|
return {
|
|
@@ -4063,14 +4059,16 @@ async function renderChart(config, services, warnings, cache) {
|
|
|
4063
4059
|
// for callers that omit it.
|
|
4064
4060
|
...config.resources ? { resources: config.resources } : {}
|
|
4065
4061
|
};
|
|
4066
|
-
return
|
|
4062
|
+
return sendChartRequest({
|
|
4067
4063
|
cache,
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4064
|
+
serverUrl,
|
|
4065
|
+
concurrency: services?.concurrency,
|
|
4066
|
+
requestBody,
|
|
4067
|
+
send: () => postChart(serverUrl, requestBody, config, services)
|
|
4068
|
+
});
|
|
4071
4069
|
}
|
|
4072
4070
|
async function postChart(serverUrl, requestBody, config, services) {
|
|
4073
|
-
const
|
|
4071
|
+
const base64Data = await postJsonToService({
|
|
4074
4072
|
url: serverUrl,
|
|
4075
4073
|
path: "/export",
|
|
4076
4074
|
body: requestBody,
|
|
@@ -4082,10 +4080,11 @@ async function postChart(serverUrl, requestBody, config, services) {
|
|
|
4082
4080
|
// The code lets a server route report a missing export server as a
|
|
4083
4081
|
// dependency outage with this message, not as an internal error.
|
|
4084
4082
|
onUnreachable: (url, cause) => `Highcharts Export Server is not running at ${url}. Start it with: npx highcharts-export-server --enableServer true
|
|
4085
|
-
Cause: ${cause}
|
|
4083
|
+
Cause: ${cause}`,
|
|
4084
|
+
decode: (response) => response.text()
|
|
4086
4085
|
});
|
|
4087
4086
|
return {
|
|
4088
|
-
dataUri: `data:image/png;base64,${
|
|
4087
|
+
dataUri: `data:image/png;base64,${base64Data}`,
|
|
4089
4088
|
widthPx: config.options.chart?.width ?? DEFAULT_CHART_WIDTH_PX,
|
|
4090
4089
|
heightPx: config.options.chart?.height ?? 720
|
|
4091
4090
|
};
|
|
@@ -4332,7 +4331,12 @@ import {
|
|
|
4332
4331
|
DEFAULT_PPTX_RENDERER_ID as DEFAULT_PPTX_RENDERER_ID2,
|
|
4333
4332
|
SEMANTIC_COLOR_NAMES as SEMANTIC_COLOR_NAMES2
|
|
4334
4333
|
} from "@json-to-office/shared-pptx";
|
|
4335
|
-
import {
|
|
4334
|
+
import {
|
|
4335
|
+
designCanvas as designCanvas2,
|
|
4336
|
+
designColors as designColors2,
|
|
4337
|
+
typeScaleSizes
|
|
4338
|
+
} from "@json-to-office/shared";
|
|
4339
|
+
import probe2 from "probe-image-size";
|
|
4336
4340
|
|
|
4337
4341
|
// src/core/generationContext.ts
|
|
4338
4342
|
import { applyExportMode } from "@json-to-office/shared";
|
|
@@ -5971,6 +5975,30 @@ function horizontalAlign(props, ctx) {
|
|
|
5971
5975
|
const styled = typeof props.style === "string" ? ctx.styles[props.style]?.align : void 0;
|
|
5972
5976
|
return styled && HORIZONTAL_ALIGNMENTS.has(styled) ? styled : "left";
|
|
5973
5977
|
}
|
|
5978
|
+
function bulletItems(props) {
|
|
5979
|
+
const runs = Array.isArray(props.runs) ? props.runs : void 0;
|
|
5980
|
+
const whole = props.bullet !== void 0 && props.bullet !== false;
|
|
5981
|
+
if (runs) {
|
|
5982
|
+
const bulleted = runs.map(asRecord).filter(
|
|
5983
|
+
(run) => run !== void 0 && (whole || run.bullet !== void 0 && run.bullet !== false)
|
|
5984
|
+
).map((run) => typeof run.text === "string" ? run.text : "");
|
|
5985
|
+
return bulleted.filter((item) => item.trim() !== "");
|
|
5986
|
+
}
|
|
5987
|
+
if (!whole || typeof props.text !== "string") return [];
|
|
5988
|
+
return props.text.split("\n").filter((line) => line.trim() !== "");
|
|
5989
|
+
}
|
|
5990
|
+
function readablePptxRatio(props) {
|
|
5991
|
+
const source = typeof props.base64 === "string" ? props.base64 : typeof props.path === "string" && props.path.startsWith("data:") ? props.path : void 0;
|
|
5992
|
+
if (source === void 0) return void 0;
|
|
5993
|
+
const comma = source.indexOf(",");
|
|
5994
|
+
if (comma < 0) return void 0;
|
|
5995
|
+
try {
|
|
5996
|
+
const size = probe2.sync(Buffer.from(source.slice(comma + 1), "base64"));
|
|
5997
|
+
return size && size.height > 0 ? size.width / size.height : void 0;
|
|
5998
|
+
} catch {
|
|
5999
|
+
return void 0;
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
5974
6002
|
function themeContext(theme) {
|
|
5975
6003
|
return {
|
|
5976
6004
|
styles: theme.styles ?? {},
|
|
@@ -6310,7 +6338,7 @@ function authoredPropsAtPointer(root, pointer) {
|
|
|
6310
6338
|
}
|
|
6311
6339
|
return asRecord(asRecord(node)?.props);
|
|
6312
6340
|
}
|
|
6313
|
-
function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slideHeightIn, ctx, theme, slideBackground, analyzedTextPaths, analyzedContentPaths, paletteTokens, authoredPropsAt, addFact) {
|
|
6341
|
+
function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slideHeightIn, ctx, theme, slideBackground, analyzedTextPaths, analyzedContentPaths, paletteTokens, authoredPropsAt, authoredPath, addFact) {
|
|
6314
6342
|
const nodes = [];
|
|
6315
6343
|
const surfaces = [];
|
|
6316
6344
|
const boxes = [];
|
|
@@ -6470,17 +6498,61 @@ function addSlideFacts(roots, slidePath, renderedIndex, grid, slideWidthIn, slid
|
|
|
6470
6498
|
align: horizontalAlign(node.props, ctx),
|
|
6471
6499
|
rotationDeg: asNumber(node.props.rotate) ?? 0,
|
|
6472
6500
|
bold: typography.bold,
|
|
6501
|
+
...asNumber(node.props.fontSize) !== void 0 && {
|
|
6502
|
+
sizePath: `${node.path}/props/fontSize`
|
|
6503
|
+
},
|
|
6504
|
+
generated: authoredPath(node.path) !== node.path,
|
|
6473
6505
|
autoFit: node.props.h === void 0 && gridPos === void 0,
|
|
6474
6506
|
...colorHex !== void 0 && { colorHex },
|
|
6475
6507
|
...!backgroundUnknown && backgroundHexes.length > 0 && { backgroundHexes }
|
|
6476
6508
|
});
|
|
6477
6509
|
});
|
|
6510
|
+
const CHROME_STYLES = /* @__PURE__ */ new Set(["footer", "tracker", "source", "caption"]);
|
|
6511
|
+
const carriesContent = (box) => box.componentName !== "shape" || typeof box.props.text === "string" && box.props.text.trim() !== "";
|
|
6478
6512
|
addFact({
|
|
6479
6513
|
id: `pptx:slide:${renderedIndex}:${slidePath}`,
|
|
6480
6514
|
kind: "pptx/slide",
|
|
6481
6515
|
path: slidePath,
|
|
6482
|
-
bodyWords
|
|
6516
|
+
bodyWords,
|
|
6517
|
+
contentBoxes: boxes.filter(
|
|
6518
|
+
(box) => ["text", "shape", "table", "chart", "highcharts", "image"].includes(
|
|
6519
|
+
box.componentName
|
|
6520
|
+
) && carriesContent(box) && !CHROME_STYLES.has(String(box.props.style ?? ""))
|
|
6521
|
+
).length
|
|
6483
6522
|
});
|
|
6523
|
+
for (const box of boxes) {
|
|
6524
|
+
if (box.componentName === "image") {
|
|
6525
|
+
const resolved = resolveBox(box.props, grid, slideWidthIn, slideHeightIn);
|
|
6526
|
+
const alt = box.props.alt;
|
|
6527
|
+
const natural = readablePptxRatio(box.props);
|
|
6528
|
+
const authored = authoredPropsAt(box.path) ?? box.props;
|
|
6529
|
+
const stretched = authored.w !== void 0 && authored.h !== void 0 && !["contain", "cover"].includes(
|
|
6530
|
+
String(asRecord(authored.sizing)?.type ?? "")
|
|
6531
|
+
);
|
|
6532
|
+
addFact({
|
|
6533
|
+
id: `pptx:image:${renderedIndex}:${box.path}`,
|
|
6534
|
+
kind: "pptx/image",
|
|
6535
|
+
path: box.path,
|
|
6536
|
+
...typeof alt === "string" && alt.trim() !== "" && { alt },
|
|
6537
|
+
...stretched && isCompleteBox(resolved) && resolved.heightPt > 0 && {
|
|
6538
|
+
drawnRatio: resolved.widthPt / resolved.heightPt
|
|
6539
|
+
},
|
|
6540
|
+
...natural !== void 0 && { naturalRatio: natural }
|
|
6541
|
+
});
|
|
6542
|
+
}
|
|
6543
|
+
const bullets = bulletItems(box.props);
|
|
6544
|
+
if (bullets.length > 1)
|
|
6545
|
+
addFact({
|
|
6546
|
+
id: `pptx:bullets:${renderedIndex}:${box.path}`,
|
|
6547
|
+
kind: "pptx/bullets",
|
|
6548
|
+
path: box.path,
|
|
6549
|
+
slidePath,
|
|
6550
|
+
items: bullets.length,
|
|
6551
|
+
longestWords: Math.max(
|
|
6552
|
+
...bullets.map((item) => item.split(/\s+/).filter(Boolean).length)
|
|
6553
|
+
)
|
|
6554
|
+
});
|
|
6555
|
+
}
|
|
6484
6556
|
}
|
|
6485
6557
|
function preparePptxQualityDocument(document, options = {}) {
|
|
6486
6558
|
const facts = [];
|
|
@@ -6502,17 +6574,6 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6502
6574
|
};
|
|
6503
6575
|
};
|
|
6504
6576
|
const props = asRecord(document.props) ?? {};
|
|
6505
|
-
addFact({
|
|
6506
|
-
id: "pptx:canvas",
|
|
6507
|
-
kind: "pptx/canvas",
|
|
6508
|
-
path: "/props",
|
|
6509
|
-
...asNumber(props.slideWidth) !== void 0 && {
|
|
6510
|
-
widthIn: asNumber(props.slideWidth)
|
|
6511
|
-
},
|
|
6512
|
-
...asNumber(props.slideHeight) !== void 0 && {
|
|
6513
|
-
heightIn: asNumber(props.slideHeight)
|
|
6514
|
-
}
|
|
6515
|
-
});
|
|
6516
6577
|
collectPlaceholders(document).forEach((occurrence, index) => {
|
|
6517
6578
|
addFact({
|
|
6518
6579
|
id: `pptx:placeholder:${index}:${occurrence.path}`,
|
|
@@ -6557,6 +6618,26 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6557
6618
|
warnings
|
|
6558
6619
|
});
|
|
6559
6620
|
const ctx = themeContext(processed.theme);
|
|
6621
|
+
const safeAreaIn = asNumber(
|
|
6622
|
+
processed.theme.spacing?.canvas?.[designCanvas2("pptx", {
|
|
6623
|
+
width: processed.slideWidth,
|
|
6624
|
+
height: processed.slideHeight
|
|
6625
|
+
})]?.safeAreaIn
|
|
6626
|
+
);
|
|
6627
|
+
addFact({
|
|
6628
|
+
id: "pptx:canvas",
|
|
6629
|
+
kind: "pptx/canvas",
|
|
6630
|
+
path: "/props",
|
|
6631
|
+
...asNumber(props.slideWidth) !== void 0 && {
|
|
6632
|
+
widthIn: asNumber(props.slideWidth)
|
|
6633
|
+
},
|
|
6634
|
+
...asNumber(props.slideHeight) !== void 0 && {
|
|
6635
|
+
heightIn: asNumber(props.slideHeight)
|
|
6636
|
+
},
|
|
6637
|
+
widthPt: processed.slideWidth * 72,
|
|
6638
|
+
heightPt: processed.slideHeight * 72,
|
|
6639
|
+
...safeAreaIn !== void 0 && { safeAreaPt: safeAreaIn * 72 }
|
|
6640
|
+
});
|
|
6560
6641
|
const paletteHexes = {};
|
|
6561
6642
|
const visualColors = designColors2(
|
|
6562
6643
|
processed.theme.colors,
|
|
@@ -6577,12 +6658,31 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6577
6658
|
if (hex)
|
|
6578
6659
|
paletteHexes[SEMANTIC_COLOR_NAMES2.includes(token) ? token : hex] = hex;
|
|
6579
6660
|
}
|
|
6661
|
+
const roleSizesPt = {};
|
|
6662
|
+
for (const [name, style] of Object.entries(processed.theme.styles ?? {})) {
|
|
6663
|
+
const size = asNumber(style?.fontSize);
|
|
6664
|
+
if (size !== void 0) roleSizesPt[name] = size;
|
|
6665
|
+
}
|
|
6666
|
+
const defaultFontSize = asNumber(processed.theme.defaults?.fontSize);
|
|
6667
|
+
const scale = processed.theme.typography?.scale?.[designCanvas2("pptx", {
|
|
6668
|
+
width: processed.slideWidth,
|
|
6669
|
+
height: processed.slideHeight
|
|
6670
|
+
})];
|
|
6671
|
+
const typeScalePt = [
|
|
6672
|
+
.../* @__PURE__ */ new Set([
|
|
6673
|
+
...Object.values(roleSizesPt),
|
|
6674
|
+
...defaultFontSize === void 0 ? [] : [defaultFontSize],
|
|
6675
|
+
...scale ? typeScaleSizes(scale) : []
|
|
6676
|
+
])
|
|
6677
|
+
].sort((a, b) => a - b);
|
|
6580
6678
|
addFact({
|
|
6581
6679
|
id: "pptx:theme",
|
|
6582
6680
|
kind: "pptx/theme",
|
|
6583
6681
|
path: "/props",
|
|
6584
6682
|
themeName: processed.theme.name,
|
|
6585
6683
|
paletteHexes,
|
|
6684
|
+
typeScalePt,
|
|
6685
|
+
roleSizesPt,
|
|
6586
6686
|
fontFamilies: [
|
|
6587
6687
|
...new Set(
|
|
6588
6688
|
[processed.theme.fonts?.heading, processed.theme.fonts?.body].filter(
|
|
@@ -6633,6 +6733,7 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6633
6733
|
analyzedContentPaths,
|
|
6634
6734
|
paletteTokens,
|
|
6635
6735
|
authoredPropsAt,
|
|
6736
|
+
authoredPath,
|
|
6636
6737
|
addFact
|
|
6637
6738
|
);
|
|
6638
6739
|
});
|
|
@@ -6673,6 +6774,9 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6673
6774
|
relatedPaths: [role.invocation],
|
|
6674
6775
|
block: role.block,
|
|
6675
6776
|
invocation: role.invocation,
|
|
6777
|
+
// The invocation is a slide child, so its slide is the first two
|
|
6778
|
+
// segments of its pointer — the one place that has to know that.
|
|
6779
|
+
slidePath: role.invocation.split("/").slice(0, 3).join("/"),
|
|
6676
6780
|
slot: role.slot,
|
|
6677
6781
|
role: role.role,
|
|
6678
6782
|
present,
|
|
@@ -7475,7 +7579,13 @@ import {
|
|
|
7475
7579
|
nearestPaletteToken,
|
|
7476
7580
|
offPaletteFinding,
|
|
7477
7581
|
placeholderFinding,
|
|
7582
|
+
DEFAULT_IMAGE_ASPECT_TOLERANCE,
|
|
7583
|
+
imageAspectFinding,
|
|
7584
|
+
driftingSizes,
|
|
7585
|
+
offScaleFindings,
|
|
7478
7586
|
QUALITY_CODES,
|
|
7587
|
+
roleDriftFindings,
|
|
7588
|
+
sizeCountFinding,
|
|
7479
7589
|
QualityEngine,
|
|
7480
7590
|
resolveRuleConfiguration,
|
|
7481
7591
|
tableInfoDesignFindings
|
|
@@ -8255,6 +8365,383 @@ var pptxActionTitleRule = {
|
|
|
8255
8365
|
}));
|
|
8256
8366
|
}
|
|
8257
8367
|
};
|
|
8368
|
+
var TITLE_DRIFT_TOLERANCE_PT = 2;
|
|
8369
|
+
var PPTX_TYPE_VOCABULARY = {
|
|
8370
|
+
subject: "deck",
|
|
8371
|
+
keepTo: "Keep to the theme styles \u2014 title, heading, body, label, statistic \u2014 and drop the ad-hoc sizes."
|
|
8372
|
+
};
|
|
8373
|
+
function pptxThemeFact(facts) {
|
|
8374
|
+
return facts.find(
|
|
8375
|
+
(fact) => fact.kind === "pptx/theme"
|
|
8376
|
+
);
|
|
8377
|
+
}
|
|
8378
|
+
function paintedSizes(facts) {
|
|
8379
|
+
return textFacts(facts).map((fact) => ({
|
|
8380
|
+
path: fact.path,
|
|
8381
|
+
...fact.styleName !== void 0 && { role: fact.styleName },
|
|
8382
|
+
fontSizePt: fact.fontSizePt,
|
|
8383
|
+
...fact.sizePath !== void 0 && { sizePath: fact.sizePath },
|
|
8384
|
+
generated: fact.generated
|
|
8385
|
+
}));
|
|
8386
|
+
}
|
|
8387
|
+
var pptxTypeScaleRule = {
|
|
8388
|
+
id: "pptx/type-scale",
|
|
8389
|
+
description: "An authored size the theme never paints: not a style, not a type role, not a step of its scale. Off until a profile or policy enables it.",
|
|
8390
|
+
code: QUALITY_CODES.TYPE_OFF_SCALE,
|
|
8391
|
+
category: "consistency",
|
|
8392
|
+
defaultSeverity: "warning",
|
|
8393
|
+
defaultCertainty: "deterministic",
|
|
8394
|
+
formats: ["pptx"],
|
|
8395
|
+
defaultEnabled: false,
|
|
8396
|
+
evaluate: ({ facts }) => {
|
|
8397
|
+
const theme = pptxThemeFact(facts);
|
|
8398
|
+
if (!theme) return [];
|
|
8399
|
+
const sizes = paintedSizes(facts);
|
|
8400
|
+
return offScaleFindings(
|
|
8401
|
+
sizes,
|
|
8402
|
+
theme.typeScalePt,
|
|
8403
|
+
theme.themeName,
|
|
8404
|
+
PPTX_TYPE_VOCABULARY,
|
|
8405
|
+
driftingSizes(sizes, theme.roleSizesPt)
|
|
8406
|
+
);
|
|
8407
|
+
}
|
|
8408
|
+
};
|
|
8409
|
+
var pptxSizeCountRule = {
|
|
8410
|
+
id: "pptx/size-count",
|
|
8411
|
+
description: "More distinct text sizes than maximumSizes allows, blocks included. Off until a profile or policy enables it.",
|
|
8412
|
+
code: QUALITY_CODES.TYPE_SIZE_COUNT,
|
|
8413
|
+
category: "consistency",
|
|
8414
|
+
defaultSeverity: "warning",
|
|
8415
|
+
defaultCertainty: "deterministic",
|
|
8416
|
+
formats: ["pptx"],
|
|
8417
|
+
defaultEnabled: false,
|
|
8418
|
+
defaultParameters: { maximumSizes: 8 },
|
|
8419
|
+
evaluate: ({ facts, configuration, profile }) => sizeCountFinding(
|
|
8420
|
+
paintedSizes(facts),
|
|
8421
|
+
numberParameter(configuration.parameters, "maximumSizes", 8),
|
|
8422
|
+
pptxThemeFact(facts)?.path ?? "/props",
|
|
8423
|
+
profile?.id,
|
|
8424
|
+
PPTX_TYPE_VOCABULARY
|
|
8425
|
+
)
|
|
8426
|
+
};
|
|
8427
|
+
var pptxRoleDriftRule = {
|
|
8428
|
+
id: "pptx/role-drift",
|
|
8429
|
+
description: "One named style or type role painted at two sizes across the deck; the theme size is the fix. Off until a profile or policy enables it.",
|
|
8430
|
+
code: QUALITY_CODES.TYPE_ROLE_DRIFT,
|
|
8431
|
+
category: "consistency",
|
|
8432
|
+
defaultSeverity: "warning",
|
|
8433
|
+
defaultCertainty: "deterministic",
|
|
8434
|
+
formats: ["pptx"],
|
|
8435
|
+
defaultEnabled: false,
|
|
8436
|
+
evaluate: ({ facts }) => {
|
|
8437
|
+
const theme = pptxThemeFact(facts);
|
|
8438
|
+
return theme ? roleDriftFindings(paintedSizes(facts), theme.roleSizesPt) : [];
|
|
8439
|
+
}
|
|
8440
|
+
};
|
|
8441
|
+
function titleGroups(facts, styles) {
|
|
8442
|
+
const texts = textFacts(facts).filter(
|
|
8443
|
+
(fact) => fact.boxXPt !== void 0 && fact.boxYPt !== void 0
|
|
8444
|
+
);
|
|
8445
|
+
const groups = /* @__PURE__ */ new Map();
|
|
8446
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
8447
|
+
const push = (key, fact) => {
|
|
8448
|
+
if (claimed.has(fact)) return;
|
|
8449
|
+
claimed.add(fact);
|
|
8450
|
+
groups.set(key, [...groups.get(key) ?? [], fact]);
|
|
8451
|
+
};
|
|
8452
|
+
for (const slot of facts) {
|
|
8453
|
+
if (slot.kind !== "pptx/chrome-slot" || slot.role !== "actionTitle")
|
|
8454
|
+
continue;
|
|
8455
|
+
const chrome = slot;
|
|
8456
|
+
if (chrome.text === void 0) continue;
|
|
8457
|
+
const painted = texts.find(
|
|
8458
|
+
(fact) => fact.slidePath === chrome.slidePath && fact.text === chrome.text
|
|
8459
|
+
);
|
|
8460
|
+
if (painted) push(chrome.block, painted);
|
|
8461
|
+
}
|
|
8462
|
+
for (const fact of texts)
|
|
8463
|
+
if (fact.styleName !== void 0 && styles.includes(fact.styleName))
|
|
8464
|
+
push(`style:${fact.styleName}`, fact);
|
|
8465
|
+
return groups;
|
|
8466
|
+
}
|
|
8467
|
+
function prevailing(values) {
|
|
8468
|
+
const counts = /* @__PURE__ */ new Map();
|
|
8469
|
+
for (const value of values) counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
8470
|
+
let best;
|
|
8471
|
+
let bestCount = 0;
|
|
8472
|
+
for (const value of values) {
|
|
8473
|
+
const count = counts.get(value);
|
|
8474
|
+
if (count > bestCount) {
|
|
8475
|
+
best = value;
|
|
8476
|
+
bestCount = count;
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
8479
|
+
return best;
|
|
8480
|
+
}
|
|
8481
|
+
var pptxTitleDriftRule = {
|
|
8482
|
+
id: "pptx/title-drift",
|
|
8483
|
+
description: "A slide title away from the left edge or baseline the deck\u2019s other titles of that kind share. Off until a profile or policy enables it.",
|
|
8484
|
+
code: QUALITY_CODES.TITLE_DRIFT,
|
|
8485
|
+
category: "consistency",
|
|
8486
|
+
defaultSeverity: "warning",
|
|
8487
|
+
defaultCertainty: "deterministic",
|
|
8488
|
+
formats: ["pptx"],
|
|
8489
|
+
defaultEnabled: false,
|
|
8490
|
+
defaultParameters: {
|
|
8491
|
+
titleStyles: ["title"],
|
|
8492
|
+
tolerancePt: TITLE_DRIFT_TOLERANCE_PT
|
|
8493
|
+
},
|
|
8494
|
+
evaluate: ({ facts, configuration }) => {
|
|
8495
|
+
const styles = stringListParameter(configuration.parameters, "titleStyles");
|
|
8496
|
+
const tolerance = numberParameter(
|
|
8497
|
+
configuration.parameters,
|
|
8498
|
+
"tolerancePt",
|
|
8499
|
+
TITLE_DRIFT_TOLERANCE_PT
|
|
8500
|
+
);
|
|
8501
|
+
const findings = [];
|
|
8502
|
+
for (const [kind, titles] of titleGroups(facts, styles)) {
|
|
8503
|
+
if (titles.length < 2) continue;
|
|
8504
|
+
const axes = [
|
|
8505
|
+
{
|
|
8506
|
+
key: "left edge",
|
|
8507
|
+
prop: "boxXPt",
|
|
8508
|
+
value: prevailing(titles.map((fact) => fact.boxXPt))
|
|
8509
|
+
},
|
|
8510
|
+
{
|
|
8511
|
+
key: "baseline",
|
|
8512
|
+
prop: "boxYPt",
|
|
8513
|
+
value: prevailing(titles.map((fact) => fact.boxYPt))
|
|
8514
|
+
}
|
|
8515
|
+
];
|
|
8516
|
+
for (const fact of titles) {
|
|
8517
|
+
const off = axes.filter(
|
|
8518
|
+
(axis) => axis.value !== void 0 && Math.abs(fact[axis.prop] - axis.value) > tolerance
|
|
8519
|
+
);
|
|
8520
|
+
if (off.length === 0) continue;
|
|
8521
|
+
const [first] = off;
|
|
8522
|
+
findings.push({
|
|
8523
|
+
path: fact.path,
|
|
8524
|
+
message: `This ${kind.startsWith("style:") ? kind.slice(6) : kind} title sits at ${off.map((axis) => `${axis.key} ${round(fact[axis.prop])}pt`).join(", ")}; the deck's others share ${off.map((axis) => `${axis.key} ${round(axis.value)}pt`).join(", ")}.`,
|
|
8525
|
+
suggestion: "Place every title of one kind with the same block or the same coordinates, so the deck holds one line down the page.",
|
|
8526
|
+
context: {
|
|
8527
|
+
kind,
|
|
8528
|
+
axes: off.map((axis) => axis.key),
|
|
8529
|
+
slide: fact.slidePath
|
|
8530
|
+
},
|
|
8531
|
+
evidence: {
|
|
8532
|
+
actual: round(fact[first.prop]),
|
|
8533
|
+
expected: round(first.value),
|
|
8534
|
+
unit: "pt",
|
|
8535
|
+
values: { axis: first.key, source: "profile" }
|
|
8536
|
+
}
|
|
8537
|
+
});
|
|
8538
|
+
}
|
|
8539
|
+
}
|
|
8540
|
+
return findings;
|
|
8541
|
+
}
|
|
8542
|
+
};
|
|
8543
|
+
var round = (value) => Math.round(value * 10) / 10;
|
|
8544
|
+
var pptxBulletRule = {
|
|
8545
|
+
id: "pptx/bullet-density",
|
|
8546
|
+
description: "More bullets in one box, or more words in one bullet, than the profile allows. Off at 0.",
|
|
8547
|
+
code: QUALITY_CODES.BULLET_COUNT,
|
|
8548
|
+
category: "information-design",
|
|
8549
|
+
defaultSeverity: "warning",
|
|
8550
|
+
defaultCertainty: "deterministic",
|
|
8551
|
+
formats: ["pptx"],
|
|
8552
|
+
defaultParameters: { maximumBullets: 0, maximumWordsPerBullet: 0 },
|
|
8553
|
+
evaluate: ({ facts, configuration }) => {
|
|
8554
|
+
const maximumBullets = numberParameter(
|
|
8555
|
+
configuration.parameters,
|
|
8556
|
+
"maximumBullets",
|
|
8557
|
+
0
|
|
8558
|
+
);
|
|
8559
|
+
const maximumWords = numberParameter(
|
|
8560
|
+
configuration.parameters,
|
|
8561
|
+
"maximumWordsPerBullet",
|
|
8562
|
+
0
|
|
8563
|
+
);
|
|
8564
|
+
const worst = /* @__PURE__ */ new Map();
|
|
8565
|
+
for (const fact of facts) {
|
|
8566
|
+
if (fact.kind !== "pptx/bullets") continue;
|
|
8567
|
+
const bullets = fact;
|
|
8568
|
+
const held = worst.get(bullets.path);
|
|
8569
|
+
worst.set(bullets.path, {
|
|
8570
|
+
...bullets,
|
|
8571
|
+
items: Math.max(bullets.items, held?.items ?? 0),
|
|
8572
|
+
longestWords: Math.max(bullets.longestWords, held?.longestWords ?? 0)
|
|
8573
|
+
});
|
|
8574
|
+
}
|
|
8575
|
+
const findings = [];
|
|
8576
|
+
for (const bullets of worst.values()) {
|
|
8577
|
+
if (maximumBullets > 0 && bullets.items > maximumBullets)
|
|
8578
|
+
findings.push({
|
|
8579
|
+
path: bullets.path,
|
|
8580
|
+
code: QUALITY_CODES.BULLET_COUNT,
|
|
8581
|
+
message: `${bullets.items} bullets in one box; a slide carries at most ${maximumBullets}.`,
|
|
8582
|
+
suggestion: "Keep the ones that carry the argument and move the rest to the notes, or split the slide.",
|
|
8583
|
+
context: { items: bullets.items, maximum: maximumBullets },
|
|
8584
|
+
evidence: {
|
|
8585
|
+
actual: bullets.items,
|
|
8586
|
+
expected: maximumBullets,
|
|
8587
|
+
unit: "bullets",
|
|
8588
|
+
values: { source: "profile" }
|
|
8589
|
+
}
|
|
8590
|
+
});
|
|
8591
|
+
if (maximumWords > 0 && bullets.longestWords > maximumWords)
|
|
8592
|
+
findings.push({
|
|
8593
|
+
path: bullets.path,
|
|
8594
|
+
code: QUALITY_CODES.BULLET_LENGTH,
|
|
8595
|
+
message: `The longest bullet here runs to ${bullets.longestWords} words; a bullet states its point in at most ${maximumWords}.`,
|
|
8596
|
+
suggestion: "Cut each bullet to a claim. What is left is the speaker\u2019s to say.",
|
|
8597
|
+
context: { words: bullets.longestWords, maximum: maximumWords },
|
|
8598
|
+
evidence: {
|
|
8599
|
+
actual: bullets.longestWords,
|
|
8600
|
+
expected: maximumWords,
|
|
8601
|
+
unit: "words",
|
|
8602
|
+
values: { source: "profile" }
|
|
8603
|
+
}
|
|
8604
|
+
});
|
|
8605
|
+
}
|
|
8606
|
+
return findings;
|
|
8607
|
+
}
|
|
8608
|
+
};
|
|
8609
|
+
var pptxSafeAreaRule = {
|
|
8610
|
+
id: "pptx/safe-area",
|
|
8611
|
+
description: "Content outside the theme\u2019s safe area that is neither chrome nor a full bleed. Off until a profile or policy enables it.",
|
|
8612
|
+
code: QUALITY_CODES.SAFE_AREA,
|
|
8613
|
+
category: "composition",
|
|
8614
|
+
defaultSeverity: "warning",
|
|
8615
|
+
defaultCertainty: "measured",
|
|
8616
|
+
formats: ["pptx"],
|
|
8617
|
+
defaultEnabled: false,
|
|
8618
|
+
defaultParameters: { tolerancePt: 2 },
|
|
8619
|
+
evaluate: ({ facts, configuration }) => {
|
|
8620
|
+
const tolerance = numberParameter(
|
|
8621
|
+
configuration.parameters,
|
|
8622
|
+
"tolerancePt",
|
|
8623
|
+
2
|
|
8624
|
+
);
|
|
8625
|
+
const canvas = facts.find(
|
|
8626
|
+
(fact) => fact.kind === "pptx/canvas"
|
|
8627
|
+
);
|
|
8628
|
+
const safe = canvas?.safeAreaPt;
|
|
8629
|
+
if (canvas === void 0 || safe === void 0 || safe <= 0) return [];
|
|
8630
|
+
const chromePaths = new Set(
|
|
8631
|
+
facts.filter(
|
|
8632
|
+
(fact) => fact.kind === "pptx/chrome-slot" && (fact.role === "tracker" || fact.role === "footer")
|
|
8633
|
+
).map((fact) => fact.path)
|
|
8634
|
+
);
|
|
8635
|
+
const chromeStyles = /* @__PURE__ */ new Set(["footer", "tracker"]);
|
|
8636
|
+
const styledChrome = new Set(
|
|
8637
|
+
textFacts(facts).filter(
|
|
8638
|
+
(fact) => fact.styleName !== void 0 && chromeStyles.has(fact.styleName)
|
|
8639
|
+
).map((fact) => fact.path)
|
|
8640
|
+
);
|
|
8641
|
+
return facts.filter((fact) => fact.kind === "pptx/box").filter(
|
|
8642
|
+
(fact) => !chromePaths.has(fact.path) && !styledChrome.has(fact.path)
|
|
8643
|
+
).flatMap((fact) => {
|
|
8644
|
+
const right = fact.xPt + fact.widthPt;
|
|
8645
|
+
const bottom = fact.yPt + fact.heightPt;
|
|
8646
|
+
const spansWidth = fact.xPt <= tolerance && right >= canvas.widthPt - tolerance;
|
|
8647
|
+
const spansHeight = fact.yPt <= tolerance && bottom >= canvas.heightPt - tolerance;
|
|
8648
|
+
if (spansWidth || spansHeight) return [];
|
|
8649
|
+
const breaches = [];
|
|
8650
|
+
if (fact.xPt < safe - tolerance) breaches.push("left");
|
|
8651
|
+
if (fact.yPt < safe - tolerance) breaches.push("top");
|
|
8652
|
+
if (right > canvas.widthPt - safe + tolerance) breaches.push("right");
|
|
8653
|
+
if (bottom > canvas.heightPt - safe + tolerance)
|
|
8654
|
+
breaches.push("bottom");
|
|
8655
|
+
if (breaches.length === 0) return [];
|
|
8656
|
+
const worst = Math.max(
|
|
8657
|
+
safe - fact.xPt,
|
|
8658
|
+
safe - fact.yPt,
|
|
8659
|
+
right - (canvas.widthPt - safe),
|
|
8660
|
+
bottom - (canvas.heightPt - safe)
|
|
8661
|
+
);
|
|
8662
|
+
return [
|
|
8663
|
+
{
|
|
8664
|
+
path: fact.path,
|
|
8665
|
+
message: `This ${fact.componentName} crosses the theme's ${Math.round(safe)}pt safe area at the ${breaches.join(" and ")}, by ${Math.round(worst * 10) / 10}pt.`,
|
|
8666
|
+
suggestion: "Move it inside the margin, or make it a full bleed that runs edge to edge.",
|
|
8667
|
+
context: { edges: breaches, safeAreaPt: safe },
|
|
8668
|
+
evidence: {
|
|
8669
|
+
actual: Math.round(worst * 10) / 10,
|
|
8670
|
+
expected: 0,
|
|
8671
|
+
unit: "pt",
|
|
8672
|
+
values: { source: "theme" }
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
];
|
|
8676
|
+
});
|
|
8677
|
+
}
|
|
8678
|
+
};
|
|
8679
|
+
var pptxSlideTitleRule = {
|
|
8680
|
+
id: "pptx/slide-title",
|
|
8681
|
+
description: "A content slide with no title: no actionTitle slot, no box in a title style. Off until a profile or policy enables it.",
|
|
8682
|
+
code: QUALITY_CODES.SLIDE_UNTITLED,
|
|
8683
|
+
category: "hierarchy",
|
|
8684
|
+
defaultSeverity: "warning",
|
|
8685
|
+
defaultCertainty: "deterministic",
|
|
8686
|
+
formats: ["pptx"],
|
|
8687
|
+
defaultEnabled: false,
|
|
8688
|
+
defaultParameters: { titleStyles: ["title", "display", "heading1"] },
|
|
8689
|
+
evaluate: ({ facts, configuration }) => {
|
|
8690
|
+
const styles = stringListParameter(configuration.parameters, "titleStyles");
|
|
8691
|
+
const titledSlides = /* @__PURE__ */ new Set();
|
|
8692
|
+
for (const fact of textFacts(facts))
|
|
8693
|
+
if (fact.styleName !== void 0 && styles.includes(fact.styleName))
|
|
8694
|
+
titledSlides.add(fact.slidePath);
|
|
8695
|
+
for (const fact of facts)
|
|
8696
|
+
if (fact.kind === "pptx/chrome-slot" && fact.role === "actionTitle" && fact.present)
|
|
8697
|
+
titledSlides.add(fact.slidePath);
|
|
8698
|
+
return facts.filter(
|
|
8699
|
+
(fact) => fact.kind === "pptx/slide" && fact.contentBoxes > 0 && !titledSlides.has(fact.path)
|
|
8700
|
+
).map((fact) => ({
|
|
8701
|
+
path: fact.path,
|
|
8702
|
+
message: "This slide carries content under no title, so nothing tells the reader what it is for.",
|
|
8703
|
+
suggestion: "Lead the slide with a claim: an action title in a block, or a box in the theme\u2019s title style.",
|
|
8704
|
+
context: { contentBoxes: fact.contentBoxes },
|
|
8705
|
+
evidence: {
|
|
8706
|
+
actual: 0,
|
|
8707
|
+
expected: 1,
|
|
8708
|
+
unit: "titles",
|
|
8709
|
+
values: { source: "profile" }
|
|
8710
|
+
}
|
|
8711
|
+
}));
|
|
8712
|
+
}
|
|
8713
|
+
};
|
|
8714
|
+
var pptxImageAspectRule = {
|
|
8715
|
+
id: "pptx/image-aspect",
|
|
8716
|
+
description: "An image drawn at an aspect the asset does not have, where the asset can be read from the document.",
|
|
8717
|
+
code: QUALITY_CODES.IMAGE_ASPECT,
|
|
8718
|
+
category: "integrity",
|
|
8719
|
+
defaultSeverity: "warning",
|
|
8720
|
+
defaultCertainty: "deterministic",
|
|
8721
|
+
formats: ["pptx"],
|
|
8722
|
+
defaultParameters: { tolerance: DEFAULT_IMAGE_ASPECT_TOLERANCE },
|
|
8723
|
+
evaluate: ({ facts, configuration }) => {
|
|
8724
|
+
const tolerance = numberParameter(
|
|
8725
|
+
configuration.parameters,
|
|
8726
|
+
"tolerance",
|
|
8727
|
+
DEFAULT_IMAGE_ASPECT_TOLERANCE
|
|
8728
|
+
);
|
|
8729
|
+
return facts.filter(
|
|
8730
|
+
(fact) => fact.kind === "pptx/image" && fact.drawnRatio !== void 0 && fact.naturalRatio !== void 0
|
|
8731
|
+
).flatMap((fact) => {
|
|
8732
|
+
const finding = imageAspectFinding(
|
|
8733
|
+
{
|
|
8734
|
+
path: fact.path,
|
|
8735
|
+
drawn: fact.drawnRatio,
|
|
8736
|
+
natural: fact.naturalRatio
|
|
8737
|
+
},
|
|
8738
|
+
"slide",
|
|
8739
|
+
tolerance
|
|
8740
|
+
);
|
|
8741
|
+
return finding ? [finding] : [];
|
|
8742
|
+
});
|
|
8743
|
+
}
|
|
8744
|
+
};
|
|
8258
8745
|
var PPTX_QUALITY_RULES = {
|
|
8259
8746
|
id: "pptx/default",
|
|
8260
8747
|
rules: [
|
|
@@ -8272,7 +8759,15 @@ var PPTX_QUALITY_RULES = {
|
|
|
8272
8759
|
pptxOffCanvasRule,
|
|
8273
8760
|
pptxSlotBudgetRule,
|
|
8274
8761
|
pptxRequiredChromeRule,
|
|
8275
|
-
pptxActionTitleRule
|
|
8762
|
+
pptxActionTitleRule,
|
|
8763
|
+
pptxTypeScaleRule,
|
|
8764
|
+
pptxSizeCountRule,
|
|
8765
|
+
pptxRoleDriftRule,
|
|
8766
|
+
pptxTitleDriftRule,
|
|
8767
|
+
pptxBulletRule,
|
|
8768
|
+
pptxSafeAreaRule,
|
|
8769
|
+
pptxSlideTitleRule,
|
|
8770
|
+
pptxImageAspectRule
|
|
8276
8771
|
]
|
|
8277
8772
|
};
|
|
8278
8773
|
var PPTX_QUALITY_PROFILES = {
|
|
@@ -8282,7 +8777,11 @@ var PPTX_QUALITY_PROFILES = {
|
|
|
8282
8777
|
description: "Decision deck optimized for scan speed and projection.",
|
|
8283
8778
|
rules: {
|
|
8284
8779
|
"pptx/minimum-font-size": { parameters: { minimumFontPt: 14 } },
|
|
8285
|
-
"pptx/slide-density": { parameters: { maximumBodyWords: 70 } }
|
|
8780
|
+
"pptx/slide-density": { parameters: { maximumBodyWords: 70 } },
|
|
8781
|
+
"pptx/bullet-density": {
|
|
8782
|
+
parameters: { maximumBullets: 5, maximumWordsPerBullet: 12 }
|
|
8783
|
+
},
|
|
8784
|
+
"pptx/slide-title": { enabled: true }
|
|
8286
8785
|
}
|
|
8287
8786
|
},
|
|
8288
8787
|
"technical-presentation": {
|
|
@@ -8293,13 +8792,25 @@ var PPTX_QUALITY_PROFILES = {
|
|
|
8293
8792
|
"consulting-deck": {
|
|
8294
8793
|
id: "consulting-deck",
|
|
8295
8794
|
formats: ["pptx"],
|
|
8296
|
-
description: "Consulting readout: every content slide leads with a two-line action title, every chart carries a takeaway and a source.",
|
|
8795
|
+
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, bullets stay under five and under twelve words, every size is on the theme scale with at most eight in play, and titles of one kind hold one line.",
|
|
8297
8796
|
rules: {
|
|
8298
8797
|
"pptx/required-chrome": {
|
|
8299
8798
|
parameters: { required: ["takeaway", "source"] }
|
|
8300
8799
|
},
|
|
8301
8800
|
"pptx/action-title": { parameters: { maxLines: 2 } },
|
|
8302
|
-
"pptx/slide-density": { parameters: { maximumBodyWords: 90 } }
|
|
8801
|
+
"pptx/slide-density": { parameters: { maximumBodyWords: 90 } },
|
|
8802
|
+
"pptx/type-scale": { enabled: true },
|
|
8803
|
+
"pptx/size-count": { enabled: true, parameters: { maximumSizes: 8 } },
|
|
8804
|
+
"pptx/role-drift": { enabled: true },
|
|
8805
|
+
"pptx/title-drift": {
|
|
8806
|
+
enabled: true,
|
|
8807
|
+
parameters: { titleStyles: ["title", "display"] }
|
|
8808
|
+
},
|
|
8809
|
+
"pptx/bullet-density": {
|
|
8810
|
+
parameters: { maximumBullets: 5, maximumWordsPerBullet: 12 }
|
|
8811
|
+
},
|
|
8812
|
+
"pptx/safe-area": { enabled: true },
|
|
8813
|
+
"pptx/slide-title": { enabled: true }
|
|
8303
8814
|
}
|
|
8304
8815
|
}
|
|
8305
8816
|
};
|