@json-to-office/core-pptx 6.8.0 → 7.0.1
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/generationContext.d.ts +3 -2
- package/dist/core/generationContext.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +896 -274
- package/dist/index.js.map +1 -1
- package/dist/ir/compiler.d.ts.map +1 -1
- package/dist/ir/types.d.ts +7 -0
- package/dist/ir/types.d.ts.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 +24 -0
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/quality/facts.d.ts +59 -2
- package/dist/quality/facts.d.ts.map +1 -1
- package/dist/quality/rules.d.ts +31 -6
- package/dist/quality/rules.d.ts.map +1 -1
- package/dist/renderers/office-open/chartParts.d.ts.map +1 -1
- package/dist/renderers/office-open/index.d.ts.map +1 -1
- package/dist/renderers/pptxgenjs/emit.d.ts +7 -1
- package/dist/renderers/pptxgenjs/emit.d.ts.map +1 -1
- package/dist/renderers/pptxgenjs/fills.d.ts +21 -1
- package/dist/renderers/pptxgenjs/fills.d.ts.map +1 -1
- package/dist/renderers/pptxgenjs/index.d.ts +2 -1
- package/dist/renderers/pptxgenjs/index.d.ts.map +1 -1
- package/dist/renderers/pptxgenjs/packaging.d.ts +3 -0
- package/dist/renderers/pptxgenjs/packaging.d.ts.map +1 -1
- package/dist/renderers/pptxgenjs/radialRaster.d.ts +49 -0
- package/dist/renderers/pptxgenjs/radialRaster.d.ts.map +1 -0
- package/dist/themes/defaults.d.ts +16 -1
- package/dist/themes/defaults.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -121,6 +121,9 @@ var init_units = __esm({
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
// src/renderers/pptxgenjs/fills.ts
|
|
124
|
+
function buildImageFillXml() {
|
|
125
|
+
return `<a:blipFill rotWithShape="1"><a:blip r:embed="${IMAGE_FILL_RID}"/><a:stretch><a:fillRect/></a:stretch></a:blipFill>`;
|
|
126
|
+
}
|
|
124
127
|
function colorXml(color2) {
|
|
125
128
|
const alpha = color2.transparency !== void 0 ? `<a:alpha val="${Math.round((100 - color2.transparency) * PCT_UNIT)}"/>` : "";
|
|
126
129
|
return `<a:srgbClr val="${color2.hex.toUpperCase()}">${alpha}</a:srgbClr>`;
|
|
@@ -142,18 +145,27 @@ function radialShadeXml(focus) {
|
|
|
142
145
|
function buildPatternFillXml(preset, foreground, background2) {
|
|
143
146
|
return `<a:pattFill prst="${preset}"><a:fgClr>${opaqueColorXml(foreground)}</a:fgClr><a:bgClr>${opaqueColorXml(background2)}</a:bgClr></a:pattFill>`;
|
|
144
147
|
}
|
|
145
|
-
function registerAdvancedFill(opts, fill2, elementPath, sink) {
|
|
148
|
+
function registerAdvancedFill(opts, fill2, elementPath, sink, raster) {
|
|
146
149
|
const sentinel = fill2.kind === "gradient" ? fill2.gradient.stops[0].color : fill2.foreground;
|
|
147
150
|
if (sink) {
|
|
148
|
-
const xml = fill2.kind === "gradient" ? buildGradientFillXml(fill2.gradient) : buildPatternFillXml(fill2.preset, fill2.foreground, fill2.background);
|
|
149
151
|
const objectName = `__jto_fill_${sink.length}__`;
|
|
150
|
-
|
|
152
|
+
if (fill2.kind === "gradient" && fill2.gradient.type === "radial" && raster) {
|
|
153
|
+
const image = raster.rasterize(
|
|
154
|
+
fill2.gradient,
|
|
155
|
+
raster.widthEmu,
|
|
156
|
+
raster.heightEmu
|
|
157
|
+
);
|
|
158
|
+
sink.push({ objectName, xml: buildImageFillXml(), image });
|
|
159
|
+
} else {
|
|
160
|
+
const xml = fill2.kind === "gradient" ? buildGradientFillXml(fill2.gradient) : buildPatternFillXml(fill2.preset, fill2.foreground, fill2.background);
|
|
161
|
+
sink.push({ objectName, xml });
|
|
162
|
+
}
|
|
151
163
|
opts.objectName = objectName;
|
|
152
164
|
}
|
|
153
165
|
void elementPath;
|
|
154
166
|
opts.fill = { color: sentinel.hex };
|
|
155
167
|
}
|
|
156
|
-
var ANGLE_UNIT, PCT_UNIT, RADIAL_FOCUS_RECTS;
|
|
168
|
+
var ANGLE_UNIT, PCT_UNIT, RADIAL_FOCUS_RECTS, IMAGE_FILL_RID;
|
|
157
169
|
var init_fills = __esm({
|
|
158
170
|
"src/renderers/pptxgenjs/fills.ts"() {
|
|
159
171
|
"use strict";
|
|
@@ -166,6 +178,7 @@ var init_fills = __esm({
|
|
|
166
178
|
bottomLeft: { l: 0, t: 1e5, r: 1e5, b: 0 },
|
|
167
179
|
bottomRight: { l: 1e5, t: 1e5, r: 0, b: 0 }
|
|
168
180
|
};
|
|
181
|
+
IMAGE_FILL_RID = "__jto_fill_rid__";
|
|
169
182
|
}
|
|
170
183
|
});
|
|
171
184
|
|
|
@@ -243,7 +256,13 @@ function chartOpts(element) {
|
|
|
243
256
|
face: "catAxisLabelFontFace",
|
|
244
257
|
bold: "catAxisLabelFontBold",
|
|
245
258
|
size: "catAxisLabelFontSize",
|
|
246
|
-
color: "catAxisLabelColor"
|
|
259
|
+
color: "catAxisLabelColor",
|
|
260
|
+
titleFont: {
|
|
261
|
+
face: "catAxisTitleFontFace",
|
|
262
|
+
size: "catAxisTitleFontSize",
|
|
263
|
+
color: "catAxisTitleColor"
|
|
264
|
+
},
|
|
265
|
+
titleRotate: "catAxisTitleRotate"
|
|
247
266
|
});
|
|
248
267
|
applyValueAxis(opts, o.valueAxis);
|
|
249
268
|
if (o.dataBorder) {
|
|
@@ -264,6 +283,10 @@ function applyAxis(opts, axis, keys) {
|
|
|
264
283
|
if (axis.title !== void 0) {
|
|
265
284
|
opts[keys.title] = axis.title;
|
|
266
285
|
opts[keys.showTitle] = true;
|
|
286
|
+
applyLabelFont(opts, axis.titleFont, keys.titleFont);
|
|
287
|
+
if (axis.titleRotate !== void 0) {
|
|
288
|
+
opts[keys.titleRotate] = axis.titleRotate;
|
|
289
|
+
}
|
|
267
290
|
}
|
|
268
291
|
if (axis.hidden !== void 0) opts[keys.hidden] = axis.hidden;
|
|
269
292
|
if (axis.labelRotate !== void 0) opts[keys.labelRotate] = axis.labelRotate;
|
|
@@ -282,7 +305,13 @@ function applyValueAxis(opts, axis) {
|
|
|
282
305
|
face: "valAxisLabelFontFace",
|
|
283
306
|
bold: "valAxisLabelFontBold",
|
|
284
307
|
size: "valAxisLabelFontSize",
|
|
285
|
-
color: "valAxisLabelColor"
|
|
308
|
+
color: "valAxisLabelColor",
|
|
309
|
+
titleFont: {
|
|
310
|
+
face: "valAxisTitleFontFace",
|
|
311
|
+
size: "valAxisTitleFontSize",
|
|
312
|
+
color: "valAxisTitleColor"
|
|
313
|
+
},
|
|
314
|
+
titleRotate: "valAxisTitleRotate"
|
|
286
315
|
});
|
|
287
316
|
assignDefined(opts, {
|
|
288
317
|
valAxisMinVal: axis.minValue,
|
|
@@ -546,7 +575,7 @@ function shadowOpts(shadow) {
|
|
|
546
575
|
function hyperlinkOpts(link) {
|
|
547
576
|
return link.kind === "external" ? { url: link.url, tooltip: link.tooltip } : { slide: link.slideIndex, tooltip: link.tooltip };
|
|
548
577
|
}
|
|
549
|
-
function applyFill(opts, fill2, elementPath, ctx) {
|
|
578
|
+
function applyFill(opts, fill2, elementPath, ctx, transform) {
|
|
550
579
|
switch (fill2.kind) {
|
|
551
580
|
case "none":
|
|
552
581
|
opts.fill = { type: "none" };
|
|
@@ -561,7 +590,17 @@ function applyFill(opts, fill2, elementPath, ctx) {
|
|
|
561
590
|
}
|
|
562
591
|
case "gradient":
|
|
563
592
|
case "pattern":
|
|
564
|
-
registerAdvancedFill(
|
|
593
|
+
registerAdvancedFill(
|
|
594
|
+
opts,
|
|
595
|
+
fill2,
|
|
596
|
+
elementPath,
|
|
597
|
+
ctx.pendingFills,
|
|
598
|
+
ctx.rasterizeRadial && transform ? {
|
|
599
|
+
rasterize: ctx.rasterizeRadial,
|
|
600
|
+
widthEmu: transform.widthEmu,
|
|
601
|
+
heightEmu: transform.heightEmu
|
|
602
|
+
} : void 0
|
|
603
|
+
);
|
|
565
604
|
return;
|
|
566
605
|
case "image":
|
|
567
606
|
throw new Error(
|
|
@@ -668,7 +707,9 @@ function emitTextBox(slide, element, ctx) {
|
|
|
668
707
|
...bodyDefaultOpts(element.style),
|
|
669
708
|
...textBodyOpts(element.style)
|
|
670
709
|
};
|
|
671
|
-
if (element.fill)
|
|
710
|
+
if (element.fill) {
|
|
711
|
+
applyFill(opts, element.fill, element.path, ctx, element.transform);
|
|
712
|
+
}
|
|
672
713
|
if (element.shadow) opts.shadow = shadowOpts(element.shadow);
|
|
673
714
|
if (element.hyperlink) opts.hyperlink = hyperlinkOpts(element.hyperlink);
|
|
674
715
|
if (element.altText) opts.altText = element.altText;
|
|
@@ -703,7 +744,9 @@ function emitShape(slide, element, ctx) {
|
|
|
703
744
|
return;
|
|
704
745
|
}
|
|
705
746
|
const opts = { ...transformOpts(element.transform) };
|
|
706
|
-
if (element.fill)
|
|
747
|
+
if (element.fill) {
|
|
748
|
+
applyFill(opts, element.fill, element.path, ctx, element.transform);
|
|
749
|
+
}
|
|
707
750
|
if (element.line) opts.line = lineOpts(element.line);
|
|
708
751
|
if (element.shadow) opts.shadow = shadowOpts(element.shadow);
|
|
709
752
|
if (element.cornerRadius !== void 0)
|
|
@@ -1059,7 +1102,7 @@ var init_svgRasterFallback = __esm({
|
|
|
1059
1102
|
});
|
|
1060
1103
|
|
|
1061
1104
|
// src/renderers/pptxgenjs/packaging.ts
|
|
1062
|
-
function applyPendingFills(xml, pendingFills) {
|
|
1105
|
+
function applyPendingFills(xml, pendingFills, spliced = []) {
|
|
1063
1106
|
let out = xml;
|
|
1064
1107
|
for (const [index, fill2] of pendingFills.entries()) {
|
|
1065
1108
|
const marker = `name="${fill2.objectName}"`;
|
|
@@ -1070,15 +1113,52 @@ function applyPendingFills(xml, pendingFills) {
|
|
|
1070
1113
|
const solidEndTag = "</a:solidFill>";
|
|
1071
1114
|
const solidEnd = out.indexOf(solidEndTag, solidStart);
|
|
1072
1115
|
if (solidStart !== -1 && solidEnd !== -1 && spEnd !== -1 && solidStart < spEnd) {
|
|
1073
|
-
|
|
1116
|
+
let fillXml = fill2.xml;
|
|
1117
|
+
if (fill2.image) {
|
|
1118
|
+
const token = `${IMAGE_FILL_RID}${index}`;
|
|
1119
|
+
fillXml = fillXml.replace(IMAGE_FILL_RID, token);
|
|
1120
|
+
spliced.push({ token, image: fill2.image });
|
|
1121
|
+
}
|
|
1122
|
+
out = out.slice(0, solidStart) + fillXml + out.slice(solidEnd + solidEndTag.length);
|
|
1074
1123
|
}
|
|
1075
1124
|
out = out.slice(0, markerIdx) + `name="Fill ${index + 1}"` + out.slice(markerIdx + marker.length);
|
|
1076
1125
|
}
|
|
1077
1126
|
return out;
|
|
1078
1127
|
}
|
|
1128
|
+
async function linkImageFills(zip, slidePath, xml, images, media) {
|
|
1129
|
+
if (images.length === 0) return xml;
|
|
1130
|
+
const relsPath = slidePath.replace(
|
|
1131
|
+
/^ppt\/slides\/(slide\d+\.xml)$/,
|
|
1132
|
+
"ppt/slides/_rels/$1.rels"
|
|
1133
|
+
);
|
|
1134
|
+
let rels = await zip.file(relsPath)?.async("string") ?? '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>';
|
|
1135
|
+
let nextId = Math.max(
|
|
1136
|
+
0,
|
|
1137
|
+
...[...rels.matchAll(/\bId="rId(\d+)"/g)].map((match) => Number(match[1]))
|
|
1138
|
+
) + 1;
|
|
1139
|
+
let out = xml;
|
|
1140
|
+
for (const { token, image } of images) {
|
|
1141
|
+
let part = media.get(image);
|
|
1142
|
+
if (!part) {
|
|
1143
|
+
part = `ppt/media/jto-gradient-${media.size + 1}.png`;
|
|
1144
|
+
media.set(image, part);
|
|
1145
|
+
zip.file(part, image);
|
|
1146
|
+
}
|
|
1147
|
+
const rId = `rId${nextId}`;
|
|
1148
|
+
nextId += 1;
|
|
1149
|
+
rels = rels.replace(
|
|
1150
|
+
"</Relationships>",
|
|
1151
|
+
`<Relationship Id="${rId}" Type="${IMAGE_REL_TYPE}" Target="../media/${part.slice("ppt/media/".length)}"/></Relationships>`
|
|
1152
|
+
);
|
|
1153
|
+
out = out.replace(`r:embed="${token}"`, `r:embed="${rId}"`);
|
|
1154
|
+
}
|
|
1155
|
+
zip.file(relsPath, rels);
|
|
1156
|
+
return out;
|
|
1157
|
+
}
|
|
1079
1158
|
async function packagePptxGenJsBuffer(buffer, options = {}) {
|
|
1080
1159
|
const zip = await readPackage(buffer);
|
|
1081
1160
|
let changed = false;
|
|
1161
|
+
const media = /* @__PURE__ */ new Map();
|
|
1082
1162
|
for (const [path4, entry] of Object.entries(zip.files)) {
|
|
1083
1163
|
if (!SLIDE_PART.test(path4)) continue;
|
|
1084
1164
|
let xml = await entry.async("string");
|
|
@@ -1088,7 +1168,9 @@ async function packagePptxGenJsBuffer(buffer, options = {}) {
|
|
|
1088
1168
|
fileChanged = true;
|
|
1089
1169
|
}
|
|
1090
1170
|
if (options.pendingFills?.length) {
|
|
1091
|
-
const
|
|
1171
|
+
const spliced = [];
|
|
1172
|
+
let withFills = applyPendingFills(xml, options.pendingFills, spliced);
|
|
1173
|
+
withFills = await linkImageFills(zip, path4, withFills, spliced, media);
|
|
1092
1174
|
if (withFills !== xml) {
|
|
1093
1175
|
xml = withFills;
|
|
1094
1176
|
fileChanged = true;
|
|
@@ -1107,15 +1189,190 @@ async function packagePptxGenJsBuffer(buffer, options = {}) {
|
|
|
1107
1189
|
if (!changed) return buffer;
|
|
1108
1190
|
return writePackage(zip);
|
|
1109
1191
|
}
|
|
1110
|
-
var MEDIUM_STYLE_2_ACCENT_1, NO_STYLE_NO_GRID, SLIDE_PART;
|
|
1192
|
+
var MEDIUM_STYLE_2_ACCENT_1, NO_STYLE_NO_GRID, SLIDE_PART, IMAGE_REL_TYPE;
|
|
1111
1193
|
var init_packaging = __esm({
|
|
1112
1194
|
"src/renderers/pptxgenjs/packaging.ts"() {
|
|
1113
1195
|
"use strict";
|
|
1114
1196
|
init_finalizePackage();
|
|
1115
1197
|
init_svgRasterFallback();
|
|
1198
|
+
init_fills();
|
|
1116
1199
|
MEDIUM_STYLE_2_ACCENT_1 = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
|
|
1117
1200
|
NO_STYLE_NO_GRID = "{2D5ABB26-0587-4C30-8999-92F81FD0307C}";
|
|
1118
1201
|
SLIDE_PART = /^ppt\/slides\/slide\d+\.xml$/;
|
|
1202
|
+
IMAGE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
// src/renderers/pptxgenjs/radialRaster.ts
|
|
1207
|
+
import pako from "pako";
|
|
1208
|
+
function radialRasterSize(widthEmu, heightEmu) {
|
|
1209
|
+
const w = Math.max(widthEmu, 1);
|
|
1210
|
+
const h = Math.max(heightEmu, 1);
|
|
1211
|
+
const longInches = Math.max(w, h) / EMU_PER_INCH3;
|
|
1212
|
+
const longPx = Math.min(
|
|
1213
|
+
MAX_EDGE_PX2,
|
|
1214
|
+
Math.max(MIN_EDGE_PX2, Math.round(longInches * PX_PER_INCH))
|
|
1215
|
+
);
|
|
1216
|
+
const scale = longPx / Math.max(w, h);
|
|
1217
|
+
return {
|
|
1218
|
+
width: Math.max(1, Math.round(w * scale)),
|
|
1219
|
+
height: Math.max(1, Math.round(h * scale))
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
function parseStops(gradient) {
|
|
1223
|
+
return gradient.stops.map((stop, index) => ({ stop, index })).sort((a, b) => a.stop.position - b.stop.position || a.index - b.index).map(({ stop }) => {
|
|
1224
|
+
const hex = parseInt(stop.color.hex, 16);
|
|
1225
|
+
const alpha = stop.color.transparency !== void 0 ? 255 * (1 - Math.min(100, Math.max(0, stop.color.transparency)) / 100) : 255;
|
|
1226
|
+
return {
|
|
1227
|
+
at: Math.min(100, Math.max(0, stop.position)) / 100,
|
|
1228
|
+
rgba: [hex >> 16 & 255, hex >> 8 & 255, hex & 255, alpha]
|
|
1229
|
+
};
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
function paintRadialGradient(gradient, width, height) {
|
|
1233
|
+
const stops = parseStops(gradient);
|
|
1234
|
+
const channels = stops.every((stop) => stop.rgba[3] === 255) ? 3 : 4;
|
|
1235
|
+
const [fx, fy] = FOCUS_POINTS[gradient.focus] ?? FOCUS_POINTS.center;
|
|
1236
|
+
const cx = fx * width;
|
|
1237
|
+
const cy = fy * height;
|
|
1238
|
+
const radius = Math.sqrt(width * width + height * height) / 2;
|
|
1239
|
+
const pixels = new Uint8Array(width * height * channels);
|
|
1240
|
+
for (let y = 0; y < height; y += 1) {
|
|
1241
|
+
const dy = y + 0.5 - cy;
|
|
1242
|
+
for (let x = 0; x < width; x += 1) {
|
|
1243
|
+
const dx = x + 0.5 - cx;
|
|
1244
|
+
const t = Math.min(1, Math.sqrt(dx * dx + dy * dy) / radius);
|
|
1245
|
+
let next = 0;
|
|
1246
|
+
while (next < stops.length && stops[next].at < t) next += 1;
|
|
1247
|
+
const offset = (y * width + x) * channels;
|
|
1248
|
+
if (next === 0 || next === stops.length) {
|
|
1249
|
+
const only = stops[next === 0 ? 0 : stops.length - 1].rgba;
|
|
1250
|
+
for (let c = 0; c < channels; c += 1) pixels[offset + c] = only[c];
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
const from = stops[next - 1];
|
|
1254
|
+
const to = stops[next];
|
|
1255
|
+
const u = (t - from.at) / (to.at - from.at);
|
|
1256
|
+
for (let c = 0; c < channels; c += 1) {
|
|
1257
|
+
pixels[offset + c] = Math.round(
|
|
1258
|
+
from.rgba[c] + (to.rgba[c] - from.rgba[c]) * u
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return { pixels, channels };
|
|
1264
|
+
}
|
|
1265
|
+
function crc32(bytes) {
|
|
1266
|
+
let c = 4294967295;
|
|
1267
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
1268
|
+
c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
1269
|
+
}
|
|
1270
|
+
return (c ^ 4294967295) >>> 0;
|
|
1271
|
+
}
|
|
1272
|
+
function chunk(type, data) {
|
|
1273
|
+
const out = new Uint8Array(12 + data.length);
|
|
1274
|
+
const view = new DataView(out.buffer);
|
|
1275
|
+
view.setUint32(0, data.length);
|
|
1276
|
+
for (let i = 0; i < 4; i += 1) out[4 + i] = type.charCodeAt(i);
|
|
1277
|
+
out.set(data, 8);
|
|
1278
|
+
view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
|
|
1279
|
+
return out;
|
|
1280
|
+
}
|
|
1281
|
+
function paethFiltered(pixels, width, height, channels) {
|
|
1282
|
+
const stride = width * channels;
|
|
1283
|
+
const out = new Uint8Array(height * (stride + 1));
|
|
1284
|
+
for (let y = 0; y < height; y += 1) {
|
|
1285
|
+
const row = y * stride;
|
|
1286
|
+
const up = row - stride;
|
|
1287
|
+
const at = y * (stride + 1);
|
|
1288
|
+
out[at] = 4;
|
|
1289
|
+
for (let i = 0; i < stride; i += 1) {
|
|
1290
|
+
const a = i >= channels ? pixels[row + i - channels] : 0;
|
|
1291
|
+
const b = y > 0 ? pixels[up + i] : 0;
|
|
1292
|
+
const c = y > 0 && i >= channels ? pixels[up + i - channels] : 0;
|
|
1293
|
+
const pa = Math.abs(b - c);
|
|
1294
|
+
const pb = Math.abs(a - c);
|
|
1295
|
+
const pc = Math.abs(a + b - 2 * c);
|
|
1296
|
+
const predictor = pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
|
|
1297
|
+
out[at + 1 + i] = pixels[row + i] - predictor & 255;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return out;
|
|
1301
|
+
}
|
|
1302
|
+
function encodePng(pixels, width, height, channels) {
|
|
1303
|
+
const header = new Uint8Array(13);
|
|
1304
|
+
const view = new DataView(header.buffer);
|
|
1305
|
+
view.setUint32(0, width);
|
|
1306
|
+
view.setUint32(4, height);
|
|
1307
|
+
header[8] = 8;
|
|
1308
|
+
header[9] = channels === 4 ? 6 : 2;
|
|
1309
|
+
const parts = [
|
|
1310
|
+
new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
1311
|
+
chunk("IHDR", header),
|
|
1312
|
+
chunk(
|
|
1313
|
+
"IDAT",
|
|
1314
|
+
pako.deflate(paethFiltered(pixels, width, height, channels), {
|
|
1315
|
+
level: 6
|
|
1316
|
+
})
|
|
1317
|
+
),
|
|
1318
|
+
chunk("IEND", new Uint8Array(0))
|
|
1319
|
+
];
|
|
1320
|
+
const png = new Uint8Array(parts.reduce((sum, p) => sum + p.length, 0));
|
|
1321
|
+
let offset = 0;
|
|
1322
|
+
for (const part of parts) {
|
|
1323
|
+
png.set(part, offset);
|
|
1324
|
+
offset += part.length;
|
|
1325
|
+
}
|
|
1326
|
+
return png;
|
|
1327
|
+
}
|
|
1328
|
+
function pngDataUri(png) {
|
|
1329
|
+
return `data:image/png;base64,${Buffer.from(png).toString("base64")}`;
|
|
1330
|
+
}
|
|
1331
|
+
var MAX_EDGE_PX2, PX_PER_INCH, MIN_EDGE_PX2, EMU_PER_INCH3, CACHE_LIMIT, FOCUS_POINTS, CRC_TABLE, cache, rasterizeRadialGradient;
|
|
1332
|
+
var init_radialRaster = __esm({
|
|
1333
|
+
"src/renderers/pptxgenjs/radialRaster.ts"() {
|
|
1334
|
+
"use strict";
|
|
1335
|
+
MAX_EDGE_PX2 = 1600;
|
|
1336
|
+
PX_PER_INCH = 160;
|
|
1337
|
+
MIN_EDGE_PX2 = 32;
|
|
1338
|
+
EMU_PER_INCH3 = 914400;
|
|
1339
|
+
CACHE_LIMIT = 32;
|
|
1340
|
+
FOCUS_POINTS = {
|
|
1341
|
+
center: [0.5, 0.5],
|
|
1342
|
+
topLeft: [0, 0],
|
|
1343
|
+
topRight: [1, 0],
|
|
1344
|
+
bottomLeft: [0, 1],
|
|
1345
|
+
bottomRight: [1, 1]
|
|
1346
|
+
};
|
|
1347
|
+
CRC_TABLE = (() => {
|
|
1348
|
+
const table = new Uint32Array(256);
|
|
1349
|
+
for (let n = 0; n < 256; n += 1) {
|
|
1350
|
+
let c = n;
|
|
1351
|
+
for (let k = 0; k < 8; k += 1) {
|
|
1352
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
1353
|
+
}
|
|
1354
|
+
table[n] = c >>> 0;
|
|
1355
|
+
}
|
|
1356
|
+
return table;
|
|
1357
|
+
})();
|
|
1358
|
+
cache = /* @__PURE__ */ new Map();
|
|
1359
|
+
rasterizeRadialGradient = (gradient, widthEmu, heightEmu) => {
|
|
1360
|
+
const { width, height } = radialRasterSize(widthEmu, heightEmu);
|
|
1361
|
+
const key = JSON.stringify([gradient.focus, gradient.stops, width, height]);
|
|
1362
|
+
const hit = cache.get(key);
|
|
1363
|
+
if (hit) {
|
|
1364
|
+
cache.delete(key);
|
|
1365
|
+
cache.set(key, hit);
|
|
1366
|
+
return hit;
|
|
1367
|
+
}
|
|
1368
|
+
const { pixels, channels } = paintRadialGradient(gradient, width, height);
|
|
1369
|
+
const png = encodePng(pixels, width, height, channels);
|
|
1370
|
+
cache.set(key, png);
|
|
1371
|
+
if (cache.size > CACHE_LIMIT) {
|
|
1372
|
+
cache.delete(cache.keys().next().value);
|
|
1373
|
+
}
|
|
1374
|
+
return png;
|
|
1375
|
+
};
|
|
1119
1376
|
}
|
|
1120
1377
|
});
|
|
1121
1378
|
|
|
@@ -1126,6 +1383,9 @@ __export(pptxgenjs_exports, {
|
|
|
1126
1383
|
buildPresentation: () => buildPresentation,
|
|
1127
1384
|
createPptxGenJsRenderer: () => createPptxGenJsRenderer
|
|
1128
1385
|
});
|
|
1386
|
+
import { readFile } from "fs/promises";
|
|
1387
|
+
import { imageIntegrityDefect as imageIntegrityDefect2 } from "@json-to-office/shared/images/node";
|
|
1388
|
+
import { assetUnreadableError as assetUnreadableError2 } from "@json-to-office/shared/rendering";
|
|
1129
1389
|
import PptxGenJS from "pptxgenjs";
|
|
1130
1390
|
function createPptxGenJsRenderer() {
|
|
1131
1391
|
return {
|
|
@@ -1133,6 +1393,7 @@ function createPptxGenJsRenderer() {
|
|
|
1133
1393
|
format: "pptx",
|
|
1134
1394
|
capabilities: PPTXGENJS_CAPABILITIES,
|
|
1135
1395
|
async render(ir, options) {
|
|
1396
|
+
await assertFileImagesDecode(ir.resources);
|
|
1136
1397
|
const pendingFills = [];
|
|
1137
1398
|
const pptx = buildPresentation(ir, pendingFills, options?.warnings);
|
|
1138
1399
|
const raw = await pptx.write({
|
|
@@ -1148,7 +1409,21 @@ function createPptxGenJsRenderer() {
|
|
|
1148
1409
|
}
|
|
1149
1410
|
};
|
|
1150
1411
|
}
|
|
1151
|
-
function
|
|
1412
|
+
async function assertFileImagesDecode(resources) {
|
|
1413
|
+
await Promise.all(
|
|
1414
|
+
resources.map(async ({ origin }) => {
|
|
1415
|
+
if (origin.kind !== "file") return;
|
|
1416
|
+
let defect;
|
|
1417
|
+
try {
|
|
1418
|
+
defect = imageIntegrityDefect2(await readFile(origin.path));
|
|
1419
|
+
} catch (error) {
|
|
1420
|
+
throw assetUnreadableError2(origin.path, error);
|
|
1421
|
+
}
|
|
1422
|
+
if (defect) throw assetUnreadableError2(origin.path, new Error(defect));
|
|
1423
|
+
})
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
function buildPresentation(ir, pendingFills, warnings, rasterizeRadial = rasterizeRadialGradient) {
|
|
1152
1427
|
const pptx = new PptxGenJS();
|
|
1153
1428
|
if (ir.metadata.title) pptx.title = ir.metadata.title;
|
|
1154
1429
|
if (ir.metadata.author) pptx.author = ir.metadata.author;
|
|
@@ -1168,9 +1443,15 @@ function buildPresentation(ir, pendingFills, warnings) {
|
|
|
1168
1443
|
const resources = new Map(
|
|
1169
1444
|
ir.resources.map((resource) => [resource.id, resource])
|
|
1170
1445
|
);
|
|
1171
|
-
const ctx = {
|
|
1446
|
+
const ctx = {
|
|
1447
|
+
pptx,
|
|
1448
|
+
resources,
|
|
1449
|
+
pendingFills,
|
|
1450
|
+
warnings,
|
|
1451
|
+
rasterizeRadial
|
|
1452
|
+
};
|
|
1172
1453
|
for (const slide of ir.slides) {
|
|
1173
|
-
emitSlide(pptx, slide, ctx);
|
|
1454
|
+
emitSlide(pptx, slide, ir.size, ctx);
|
|
1174
1455
|
}
|
|
1175
1456
|
return pptx;
|
|
1176
1457
|
}
|
|
@@ -1181,12 +1462,36 @@ function backgroundProps(background2, resources) {
|
|
|
1181
1462
|
if (!resource) return void 0;
|
|
1182
1463
|
return imageSourceOpts(resource);
|
|
1183
1464
|
}
|
|
1184
|
-
function
|
|
1465
|
+
function radialBackdrop(element, size) {
|
|
1466
|
+
if (element?.kind !== "shape") return void 0;
|
|
1467
|
+
const { transform, fill: fill2 } = element;
|
|
1468
|
+
if (fill2?.kind !== "gradient" || fill2.gradient.type !== "radial") {
|
|
1469
|
+
return void 0;
|
|
1470
|
+
}
|
|
1471
|
+
const bare = element.geometry === "rect" && !element.line && !element.shadow && !element.hyperlink && !element.altText && !(element.runs && element.runs.length > 0) && !transform.rotationDegrees && !transform.flipHorizontal && !transform.flipVertical;
|
|
1472
|
+
const fullBleed = transform.xEmu === 0 && transform.yEmu === 0 && transform.widthEmu === size.widthEmu && transform.heightEmu === size.heightEmu;
|
|
1473
|
+
return bare && fullBleed ? fill2.gradient : void 0;
|
|
1474
|
+
}
|
|
1475
|
+
function emitSlide(pptx, slideIr, size, ctx) {
|
|
1185
1476
|
const slide = pptx.addSlide();
|
|
1477
|
+
let elements = slideIr.elements;
|
|
1186
1478
|
const background2 = backgroundProps(slideIr.background, ctx.resources);
|
|
1187
|
-
if (background2)
|
|
1479
|
+
if (background2) {
|
|
1480
|
+
slide.background = background2;
|
|
1481
|
+
} else if (ctx.rasterizeRadial) {
|
|
1482
|
+
const gradient = radialBackdrop(elements[0], size);
|
|
1483
|
+
if (gradient) {
|
|
1484
|
+
const png = ctx.rasterizeRadial(
|
|
1485
|
+
gradient,
|
|
1486
|
+
elements[0].transform.widthEmu,
|
|
1487
|
+
elements[0].transform.heightEmu
|
|
1488
|
+
);
|
|
1489
|
+
slide.background = { data: pngDataUri(png) };
|
|
1490
|
+
elements = elements.slice(1);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1188
1493
|
if (slideIr.hidden) slide.hidden = true;
|
|
1189
|
-
for (const element of
|
|
1494
|
+
for (const element of elements) {
|
|
1190
1495
|
emitElement(slide, element, ctx);
|
|
1191
1496
|
}
|
|
1192
1497
|
if (slideIr.notes) slide.addNotes(slideIr.notes);
|
|
@@ -1198,6 +1503,7 @@ var init_pptxgenjs = __esm({
|
|
|
1198
1503
|
init_units();
|
|
1199
1504
|
init_emit();
|
|
1200
1505
|
init_packaging();
|
|
1506
|
+
init_radialRaster();
|
|
1201
1507
|
PPTXGENJS_RENDERER_ID = "pptxgenjs";
|
|
1202
1508
|
PPTXGENJS_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
1203
1509
|
"rich-text",
|
|
@@ -1300,6 +1606,8 @@ function axisEdits(axis) {
|
|
|
1300
1606
|
const value = axis;
|
|
1301
1607
|
return {
|
|
1302
1608
|
...axis.title !== void 0 ? { title: axis.title } : {},
|
|
1609
|
+
...axis.title !== void 0 && textStyle(axis.titleFont) ? { titleFont: textStyle(axis.titleFont) } : {},
|
|
1610
|
+
...axis.title !== void 0 && axis.titleRotate !== void 0 ? { titleRotation: axis.titleRotate } : {},
|
|
1303
1611
|
...axis.hidden !== void 0 ? { hidden: axis.hidden } : {},
|
|
1304
1612
|
...axis.showLine !== void 0 ? { lineVisible: axis.showLine } : {},
|
|
1305
1613
|
...axis.labelRotate !== void 0 ? { labelRotation: axis.labelRotate } : {},
|
|
@@ -1894,8 +2202,9 @@ __export(office_open_exports, {
|
|
|
1894
2202
|
buildPresentationOptions: () => buildPresentationOptions,
|
|
1895
2203
|
createOfficeOpenPptxRenderer: () => createOfficeOpenPptxRenderer
|
|
1896
2204
|
});
|
|
1897
|
-
import { readFile } from "fs/promises";
|
|
1898
|
-
import {
|
|
2205
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2206
|
+
import { imageIntegrityDefect as imageIntegrityDefect3 } from "@json-to-office/shared/images/node";
|
|
2207
|
+
import { assetUnreadableError as assetUnreadableError3 } from "@json-to-office/shared/rendering";
|
|
1899
2208
|
async function createOfficeOpenPptxRenderer() {
|
|
1900
2209
|
const backend = await import(
|
|
1901
2210
|
/* @vite-ignore */
|
|
@@ -2004,16 +2313,21 @@ async function resourceBytes(origin) {
|
|
|
2004
2313
|
if (origin.kind === "inline") return origin.bytes;
|
|
2005
2314
|
const location = origin.kind === "file" ? origin.path : origin.url;
|
|
2006
2315
|
try {
|
|
2316
|
+
let bytes;
|
|
2007
2317
|
if (origin.kind === "file") {
|
|
2008
|
-
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2318
|
+
bytes = new Uint8Array(await readFile2(origin.path));
|
|
2319
|
+
} else {
|
|
2320
|
+
const response = await fetch(origin.url);
|
|
2321
|
+
if (!response.ok) {
|
|
2322
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
2323
|
+
}
|
|
2324
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
2013
2325
|
}
|
|
2014
|
-
|
|
2326
|
+
const defect = imageIntegrityDefect3(bytes);
|
|
2327
|
+
if (defect) throw new Error(defect);
|
|
2328
|
+
return bytes;
|
|
2015
2329
|
} catch (error) {
|
|
2016
|
-
throw
|
|
2330
|
+
throw assetUnreadableError3(location, error);
|
|
2017
2331
|
}
|
|
2018
2332
|
}
|
|
2019
2333
|
var OFFICE_OPEN_PPTX_RENDERER_ID, OFFICE_OPEN_PPTX, OFFICE_OPEN_CAPABILITIES, SCHEME_SLOTS;
|
|
@@ -2101,7 +2415,9 @@ function resolveFromBaseDir(filePath) {
|
|
|
2101
2415
|
}
|
|
2102
2416
|
|
|
2103
2417
|
// src/ir/compiler.ts
|
|
2418
|
+
import { imageIntegrityDefect } from "@json-to-office/shared/images/node";
|
|
2104
2419
|
import {
|
|
2420
|
+
assetUnreadableError,
|
|
2105
2421
|
FeatureRequirementCollector
|
|
2106
2422
|
} from "@json-to-office/shared/rendering";
|
|
2107
2423
|
import { PATTERN_FILL_PRESETS } from "@json-to-office/shared-pptx";
|
|
@@ -3097,6 +3413,8 @@ function compileImage(component, scope) {
|
|
|
3097
3413
|
function internImageSource(source, path4, ctx) {
|
|
3098
3414
|
const inline = parseDataUri(source);
|
|
3099
3415
|
if (inline) {
|
|
3416
|
+
const defect = imageIntegrityDefect(inline.bytes);
|
|
3417
|
+
if (defect) throw assetUnreadableError(source, new Error(defect));
|
|
3100
3418
|
return ctx.resources.intern({
|
|
3101
3419
|
kind: "inline",
|
|
3102
3420
|
bytes: inline.bytes,
|
|
@@ -3456,6 +3774,16 @@ function compileChartLabelFont(ctx, slot, face, weight, hasBoldToggle, extra = {
|
|
|
3456
3774
|
}
|
|
3457
3775
|
return font;
|
|
3458
3776
|
}
|
|
3777
|
+
var DEFAULT_AXIS_TITLE_POINTS = 12;
|
|
3778
|
+
function axisTitleFont(ctx, labelFont, resolved, authored) {
|
|
3779
|
+
const fontFamily = authored.face ?? labelFont.fontFamily ?? ctx.theme.fonts?.body;
|
|
3780
|
+
return {
|
|
3781
|
+
...fontFamily !== void 0 ? { fontFamily } : {},
|
|
3782
|
+
fontSize: authored.size ?? labelFont.fontSize ?? DEFAULT_AXIS_TITLE_POINTS,
|
|
3783
|
+
bold: false,
|
|
3784
|
+
color: authored.color !== void 0 ? resolved(authored.color) : labelFont.color ?? resolved(void 0)
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3459
3787
|
function compileChartOptions(props, ctx) {
|
|
3460
3788
|
const colorSources = props.chartColors ?? definedChartColorTokens(ctx.theme);
|
|
3461
3789
|
const colors = colorSources.map(
|
|
@@ -3463,6 +3791,28 @@ function compileChartOptions(props, ctx) {
|
|
|
3463
3791
|
);
|
|
3464
3792
|
const themeTextColor = irColor(resolveColor("text", ctx.theme, ctx.warnings));
|
|
3465
3793
|
const resolved = (value) => value ? irColor(resolveColor(value, ctx.theme, ctx.warnings)) : themeTextColor;
|
|
3794
|
+
const catLabelFont = compileChartLabelFont(
|
|
3795
|
+
ctx,
|
|
3796
|
+
"catAxisLabelFontFace",
|
|
3797
|
+
props.catAxisLabelFontFace,
|
|
3798
|
+
props.catAxisLabelFontWeight,
|
|
3799
|
+
true,
|
|
3800
|
+
{
|
|
3801
|
+
...props.catAxisLabelFontSize !== void 0 ? { fontSize: props.catAxisLabelFontSize } : {},
|
|
3802
|
+
color: resolved(props.catAxisLabelColor)
|
|
3803
|
+
}
|
|
3804
|
+
);
|
|
3805
|
+
const valLabelFont = compileChartLabelFont(
|
|
3806
|
+
ctx,
|
|
3807
|
+
"valAxisLabelFontFace",
|
|
3808
|
+
props.valAxisLabelFontFace,
|
|
3809
|
+
props.valAxisLabelFontWeight,
|
|
3810
|
+
true,
|
|
3811
|
+
{
|
|
3812
|
+
...props.valAxisLabelFontSize !== void 0 ? { fontSize: props.valAxisLabelFontSize } : {},
|
|
3813
|
+
color: resolved(props.valAxisLabelColor)
|
|
3814
|
+
}
|
|
3815
|
+
);
|
|
3466
3816
|
return {
|
|
3467
3817
|
colors,
|
|
3468
3818
|
...pick(props, {
|
|
@@ -3499,24 +3849,26 @@ function compileChartOptions(props, ctx) {
|
|
|
3499
3849
|
),
|
|
3500
3850
|
categoryAxis: {
|
|
3501
3851
|
...props.catAxisTitle !== void 0 ? { title: props.catAxisTitle } : {},
|
|
3852
|
+
titleFont: axisTitleFont(ctx, catLabelFont, resolved, {
|
|
3853
|
+
face: props.catAxisTitleFontFace,
|
|
3854
|
+
size: props.catAxisTitleFontSize,
|
|
3855
|
+
color: props.catAxisTitleColor
|
|
3856
|
+
}),
|
|
3857
|
+
...props.catAxisTitleRotate !== void 0 ? { titleRotate: props.catAxisTitleRotate } : {},
|
|
3502
3858
|
...props.catAxisHidden !== void 0 ? { hidden: props.catAxisHidden } : {},
|
|
3503
3859
|
...props.catAxisLabelRotate !== void 0 ? { labelRotate: props.catAxisLabelRotate } : {},
|
|
3504
3860
|
...props.catAxisLineShow !== void 0 ? { showLine: props.catAxisLineShow } : {},
|
|
3505
3861
|
...props.catGridLine !== void 0 ? { gridLine: compileGridLine(props.catGridLine, ctx) } : {},
|
|
3506
|
-
labelFont:
|
|
3507
|
-
ctx,
|
|
3508
|
-
"catAxisLabelFontFace",
|
|
3509
|
-
props.catAxisLabelFontFace,
|
|
3510
|
-
props.catAxisLabelFontWeight,
|
|
3511
|
-
true,
|
|
3512
|
-
{
|
|
3513
|
-
...props.catAxisLabelFontSize !== void 0 ? { fontSize: props.catAxisLabelFontSize } : {},
|
|
3514
|
-
color: resolved(props.catAxisLabelColor)
|
|
3515
|
-
}
|
|
3516
|
-
)
|
|
3862
|
+
labelFont: catLabelFont
|
|
3517
3863
|
},
|
|
3518
3864
|
valueAxis: {
|
|
3519
3865
|
...props.valAxisTitle !== void 0 ? { title: props.valAxisTitle } : {},
|
|
3866
|
+
titleFont: axisTitleFont(ctx, valLabelFont, resolved, {
|
|
3867
|
+
face: props.valAxisTitleFontFace,
|
|
3868
|
+
size: props.valAxisTitleFontSize,
|
|
3869
|
+
color: props.valAxisTitleColor
|
|
3870
|
+
}),
|
|
3871
|
+
...props.valAxisTitleRotate !== void 0 ? { titleRotate: props.valAxisTitleRotate } : {},
|
|
3520
3872
|
...props.valAxisHidden !== void 0 ? { hidden: props.valAxisHidden } : {},
|
|
3521
3873
|
...props.valAxisMinVal !== void 0 ? { minValue: props.valAxisMinVal } : {},
|
|
3522
3874
|
...props.valAxisMaxVal !== void 0 ? { maxValue: props.valAxisMaxVal } : {},
|
|
@@ -3524,17 +3876,7 @@ function compileChartOptions(props, ctx) {
|
|
|
3524
3876
|
...props.valAxisLabelFormatCode !== void 0 ? { labelFormatCode: props.valAxisLabelFormatCode } : {},
|
|
3525
3877
|
...props.valAxisLineShow !== void 0 ? { showLine: props.valAxisLineShow } : {},
|
|
3526
3878
|
...props.valGridLine !== void 0 ? { gridLine: compileGridLine(props.valGridLine, ctx) } : {},
|
|
3527
|
-
labelFont:
|
|
3528
|
-
ctx,
|
|
3529
|
-
"valAxisLabelFontFace",
|
|
3530
|
-
props.valAxisLabelFontFace,
|
|
3531
|
-
props.valAxisLabelFontWeight,
|
|
3532
|
-
true,
|
|
3533
|
-
{
|
|
3534
|
-
...props.valAxisLabelFontSize !== void 0 ? { fontSize: props.valAxisLabelFontSize } : {},
|
|
3535
|
-
color: resolved(props.valAxisLabelColor)
|
|
3536
|
-
}
|
|
3537
|
-
)
|
|
3879
|
+
labelFont: valLabelFont
|
|
3538
3880
|
},
|
|
3539
3881
|
...props.dataBorder !== void 0 ? {
|
|
3540
3882
|
dataBorder: {
|
|
@@ -3949,7 +4291,7 @@ function isNodeEnvironment() {
|
|
|
3949
4291
|
}
|
|
3950
4292
|
|
|
3951
4293
|
// src/core/expandHighcharts.ts
|
|
3952
|
-
var
|
|
4294
|
+
var PX_PER_INCH2 = 96;
|
|
3953
4295
|
var DEFAULT_CHART_WIDTH_PX = 960;
|
|
3954
4296
|
var DEFAULT_EXPORT_SERVER_URL = "http://localhost:7801";
|
|
3955
4297
|
async function expandHighchartsComponents(presentation, services, warnings, chartFonts) {
|
|
@@ -4021,8 +4363,8 @@ async function expandOne(component, path4, scope) {
|
|
|
4021
4363
|
base64: chart.dataUri,
|
|
4022
4364
|
x: props.x ?? 0,
|
|
4023
4365
|
y: props.y ?? 0,
|
|
4024
|
-
w: props.w ?? chart.widthPx /
|
|
4025
|
-
h: props.h ?? chart.heightPx /
|
|
4366
|
+
w: props.w ?? chart.widthPx / PX_PER_INCH2,
|
|
4367
|
+
h: props.h ?? chart.heightPx / PX_PER_INCH2
|
|
4026
4368
|
}
|
|
4027
4369
|
};
|
|
4028
4370
|
}
|
|
@@ -4039,7 +4381,7 @@ function assertExportServerAllowed(serverUrl, services, warnings) {
|
|
|
4039
4381
|
message: notice
|
|
4040
4382
|
});
|
|
4041
4383
|
}
|
|
4042
|
-
async function renderChart(config, services, warnings,
|
|
4384
|
+
async function renderChart(config, services, warnings, cache2) {
|
|
4043
4385
|
if (!isNodeEnvironment()) {
|
|
4044
4386
|
throw new Error(
|
|
4045
4387
|
"Highcharts export server requires a Node.js environment. Chart generation is not available in browser environments."
|
|
@@ -4057,7 +4399,7 @@ async function renderChart(config, services, warnings, cache) {
|
|
|
4057
4399
|
...config.resources ? { resources: config.resources } : {}
|
|
4058
4400
|
};
|
|
4059
4401
|
return sendChartRequest({
|
|
4060
|
-
cache,
|
|
4402
|
+
cache: cache2,
|
|
4061
4403
|
serverUrl,
|
|
4062
4404
|
concurrency: services?.concurrency,
|
|
4063
4405
|
requestBody,
|
|
@@ -4104,7 +4446,7 @@ function placedWidthPt(props, slideWidth) {
|
|
|
4104
4446
|
return parseFloat(w) / 100 * slideWidth * 72;
|
|
4105
4447
|
}
|
|
4106
4448
|
if (w === void 0 && props.grid === void 0) {
|
|
4107
|
-
return (props.options.chart?.width ?? DEFAULT_CHART_WIDTH_PX) /
|
|
4449
|
+
return (props.options.chart?.width ?? DEFAULT_CHART_WIDTH_PX) / PX_PER_INCH2 * 72;
|
|
4108
4450
|
}
|
|
4109
4451
|
return void 0;
|
|
4110
4452
|
}
|
|
@@ -5163,36 +5505,8 @@ var DEFAULT_STYLES = {
|
|
|
5163
5505
|
body: { fontSize: 14 },
|
|
5164
5506
|
caption: { fontSize: 10, italic: true, fontColor: "text2" }
|
|
5165
5507
|
};
|
|
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
|
-
};
|
|
5508
|
+
var DEFAULT_PPTX_THEME = CONSULTING_PPTX_THEME;
|
|
5194
5509
|
var PPTX_THEMES = {
|
|
5195
|
-
default: DEFAULT_PPTX_THEME,
|
|
5196
5510
|
consulting: CONSULTING_PPTX_THEME,
|
|
5197
5511
|
vermilion: VERMILION_PPTX_THEME,
|
|
5198
5512
|
devportal: DEVPORTAL_PPTX_THEME,
|
|
@@ -5332,7 +5646,7 @@ function resolveThemeContext(documentIn, options = {}) {
|
|
|
5332
5646
|
inlineTheme = document.props.theme;
|
|
5333
5647
|
}
|
|
5334
5648
|
const authoredThemeName = typeof document.props.theme === "string" ? document.props.theme : void 0;
|
|
5335
|
-
const baseThemeName = inlineTheme ? inlineTheme.name || "inline-theme" : authoredThemeName ?? defaultThemeName ?? "
|
|
5649
|
+
const baseThemeName = inlineTheme ? inlineTheme.name || "inline-theme" : authoredThemeName ?? defaultThemeName ?? "consulting";
|
|
5336
5650
|
let theme = inlineTheme ?? (resolveNamedTheme ? resolveNamedTheme(baseThemeName, authoredThemeName !== void 0) : customThemes?.[baseThemeName] ?? getPptxTheme(baseThemeName));
|
|
5337
5651
|
theme = resolvePptxDesignSystem(
|
|
5338
5652
|
theme,
|
|
@@ -5913,10 +6227,22 @@ async function expandPptxBlocksWithPlugins(document, theme, plugins, render, pre
|
|
|
5913
6227
|
preserve
|
|
5914
6228
|
});
|
|
5915
6229
|
const finished = finishPptxBlocks(composed.standard, evaluator, effects2);
|
|
5916
|
-
return {
|
|
6230
|
+
return {
|
|
6231
|
+
...finished,
|
|
6232
|
+
preserved: composed.preserved,
|
|
6233
|
+
pluginOutputs: evaluator.pluginOutputs
|
|
6234
|
+
};
|
|
5917
6235
|
}
|
|
5918
6236
|
|
|
5919
6237
|
// src/quality/facts.ts
|
|
6238
|
+
function textInsetsPt(props, componentName) {
|
|
6239
|
+
const margin = props.margin;
|
|
6240
|
+
if (typeof margin === "number" && Number.isFinite(margin))
|
|
6241
|
+
return [margin, margin, margin, margin];
|
|
6242
|
+
if (Array.isArray(margin) && margin.length === 4 && margin.every((side) => typeof side === "number" && Number.isFinite(side)))
|
|
6243
|
+
return margin;
|
|
6244
|
+
return componentName === "shape" ? [3.6, 7.2, 3.6, 7.2] : [0, 0, 0, 0];
|
|
6245
|
+
}
|
|
5920
6246
|
function asRecord(value) {
|
|
5921
6247
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
5922
6248
|
}
|
|
@@ -6054,6 +6380,7 @@ function fillColorHexes(fill2, theme) {
|
|
|
6054
6380
|
return [...new Set(found)];
|
|
6055
6381
|
}
|
|
6056
6382
|
var FOCUS_CORNERS = {
|
|
6383
|
+
center: [0.5, 0.5],
|
|
6057
6384
|
topLeft: [0, 0],
|
|
6058
6385
|
topRight: [1, 0],
|
|
6059
6386
|
bottomLeft: [0, 1],
|
|
@@ -6069,7 +6396,7 @@ function lerpHex(a, b, t) {
|
|
|
6069
6396
|
}
|
|
6070
6397
|
function gradientPosition(gradient, fx, fy, widthUnits, heightUnits) {
|
|
6071
6398
|
if (gradient.type === "radial") {
|
|
6072
|
-
const focus = FOCUS_CORNERS[typeof gradient.focus === "string" ? gradient.focus : "
|
|
6399
|
+
const focus = FOCUS_CORNERS[typeof gradient.focus === "string" ? gradient.focus : "center"] ?? FOCUS_CORNERS.center;
|
|
6073
6400
|
const radius = Math.hypot(widthUnits, heightUnits) / 2;
|
|
6074
6401
|
if (radius === 0) return 0;
|
|
6075
6402
|
const distance = Math.hypot(
|
|
@@ -6174,7 +6501,13 @@ function collectSlideNodes(component, path4, text, richText, surfaces, boxes, co
|
|
|
6174
6501
|
const content = typeof props.text === "string" ? props.text : void 0;
|
|
6175
6502
|
if (content !== void 0 && content.trim() !== "" && props.runs === void 0) {
|
|
6176
6503
|
if (rec.name === "text" || rec.name === "shape") {
|
|
6177
|
-
text.push({
|
|
6504
|
+
text.push({
|
|
6505
|
+
props,
|
|
6506
|
+
path: path4,
|
|
6507
|
+
componentName: rec.name,
|
|
6508
|
+
text: content,
|
|
6509
|
+
order
|
|
6510
|
+
});
|
|
6178
6511
|
}
|
|
6179
6512
|
}
|
|
6180
6513
|
if (Array.isArray(props.runs) && (rec.name === "text" || rec.name === "shape")) {
|
|
@@ -6182,7 +6515,13 @@ function collectSlideNodes(component, path4, text, richText, surfaces, boxes, co
|
|
|
6182
6515
|
(run) => run && typeof run.text === "string" ? `${run.text}${run.breakLine === true ? "\n" : ""}` : ""
|
|
6183
6516
|
).join("");
|
|
6184
6517
|
if (joined.trim() !== "")
|
|
6185
|
-
richText.push({
|
|
6518
|
+
richText.push({
|
|
6519
|
+
props,
|
|
6520
|
+
path: path4,
|
|
6521
|
+
componentName: String(rec.name),
|
|
6522
|
+
text: joined,
|
|
6523
|
+
order
|
|
6524
|
+
});
|
|
6186
6525
|
}
|
|
6187
6526
|
const componentName = typeof rec.name === "string" ? rec.name : "";
|
|
6188
6527
|
if (componentName === "chart" || componentName === "highcharts" || componentName === "table") {
|
|
@@ -6357,6 +6696,16 @@ function tableFact(node, slidePath, authoredProps) {
|
|
|
6357
6696
|
}
|
|
6358
6697
|
return { ...base, columns };
|
|
6359
6698
|
}
|
|
6699
|
+
function authoredNameAtPointer(root, pointer) {
|
|
6700
|
+
let node = root;
|
|
6701
|
+
for (const token of pointer.split("/").slice(1)) {
|
|
6702
|
+
const key = token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
6703
|
+
node = Array.isArray(node) ? node[Number(key)] : asRecord(node)?.[key];
|
|
6704
|
+
if (node === void 0) return void 0;
|
|
6705
|
+
}
|
|
6706
|
+
const name = asRecord(node)?.name;
|
|
6707
|
+
return typeof name === "string" ? name : void 0;
|
|
6708
|
+
}
|
|
6360
6709
|
function authoredPropsAtPointer(root, pointer) {
|
|
6361
6710
|
let node = root;
|
|
6362
6711
|
for (const token of pointer.split("/").slice(1)) {
|
|
@@ -6374,7 +6723,7 @@ function authoredPropsAtPointer(root, pointer) {
|
|
|
6374
6723
|
}
|
|
6375
6724
|
return asRecord(asRecord(node)?.props);
|
|
6376
6725
|
}
|
|
6377
|
-
function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn, slideHeightIn, ctx, theme, slideBackground, analyzedTextPaths, analyzedContentPaths, paletteTokens, authoredPropsAt, statedPropsAt, authoredPath, addFact) {
|
|
6726
|
+
function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn, slideHeightIn, ctx, theme, slideBackground, analyzedTextPaths, analyzedContentPaths, paletteTokens, authoredPropsAt, statedPropsAt, authoredPath, addFact, ownsProps = () => true) {
|
|
6378
6727
|
const nodes = [];
|
|
6379
6728
|
const richNodes = [];
|
|
6380
6729
|
const surfaces = [];
|
|
@@ -6406,6 +6755,8 @@ function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn
|
|
|
6406
6755
|
statedPropsAt
|
|
6407
6756
|
);
|
|
6408
6757
|
if (!isCompleteBox(box)) return;
|
|
6758
|
+
const onSlide = /^\/children\/\d+\/children\/\d+$/.test(node.path) && authoredPath(node.path) === node.path;
|
|
6759
|
+
const authored = onSlide ? authoredPropsAt(node.path) : void 0;
|
|
6409
6760
|
addFact({
|
|
6410
6761
|
id: `pptx:box:${renderedIndex}:${boxIndex}:${node.path}`,
|
|
6411
6762
|
kind: "pptx/box",
|
|
@@ -6418,7 +6769,10 @@ function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn
|
|
|
6418
6769
|
xPt: box.xPt,
|
|
6419
6770
|
yPt: box.yPt,
|
|
6420
6771
|
widthPt: box.widthPt,
|
|
6421
|
-
heightPt: box.heightPt
|
|
6772
|
+
heightPt: box.heightPt,
|
|
6773
|
+
...typeof authored?.x === "number" && typeof authored?.y === "number" && authored.grid === void 0 && {
|
|
6774
|
+
authoredPositionIn: { x: authored.x, y: authored.y }
|
|
6775
|
+
}
|
|
6422
6776
|
});
|
|
6423
6777
|
});
|
|
6424
6778
|
const surfaceBoxes = surfaces.flatMap((surface) => {
|
|
@@ -6522,6 +6876,8 @@ function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn
|
|
|
6522
6876
|
}
|
|
6523
6877
|
}
|
|
6524
6878
|
const colorHex = typeof node.props.color === "string" ? resolveColor(node.props.color, theme)?.toUpperCase() : void 0;
|
|
6879
|
+
const stated = statedPropsAt(node.path);
|
|
6880
|
+
const setSize = stated && node.props.fit !== void 0 ? resolveTypography(stated, ctx).fontSize : void 0;
|
|
6525
6881
|
addFact({
|
|
6526
6882
|
id: `pptx:text:${renderedIndex}:${nodeIndex}:${node.path}`,
|
|
6527
6883
|
kind: "pptx/text",
|
|
@@ -6531,6 +6887,7 @@ function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn
|
|
|
6531
6887
|
nodePath: node.path,
|
|
6532
6888
|
text: node.text,
|
|
6533
6889
|
fontSizePt: typography.fontSize,
|
|
6890
|
+
...setSize !== void 0 && setSize !== typography.fontSize && { fitFromPt: setSize },
|
|
6534
6891
|
lineSpacingPt: typography.lineSpacing,
|
|
6535
6892
|
paraSpaceBeforePt: typography.paraSpaceBefore,
|
|
6536
6893
|
paraSpaceAfterPt: typography.paraSpaceAfter,
|
|
@@ -6539,6 +6896,7 @@ function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn
|
|
|
6539
6896
|
...boxYPt !== void 0 && { boxYPt },
|
|
6540
6897
|
...boxWidthPt !== void 0 && boxWidthPt > 0 && { boxWidthPt },
|
|
6541
6898
|
...boxHeightPt !== void 0 && boxHeightPt > 0 && { boxHeightPt },
|
|
6899
|
+
insetsPt: textInsetsPt(node.props, node.componentName),
|
|
6542
6900
|
verticalAlign: node.props.valign === "middle" || node.props.valign === "bottom" ? node.props.valign : "top",
|
|
6543
6901
|
align: horizontalAlign(node.props, ctx),
|
|
6544
6902
|
rotationDeg: asNumber(node.props.rotate) ?? 0,
|
|
@@ -6550,6 +6908,7 @@ function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn
|
|
|
6550
6908
|
sizePath: `${node.path}/props/fontSize`
|
|
6551
6909
|
},
|
|
6552
6910
|
generated: authoredPath(node.path) !== node.path,
|
|
6911
|
+
ownsProps: ownsProps(node.path),
|
|
6553
6912
|
autoFit: node.props.h === void 0 && gridPos === void 0,
|
|
6554
6913
|
...colorHex !== void 0 && { colorHex },
|
|
6555
6914
|
...!backgroundUnknown && backgroundHexes.length > 0 && { backgroundHexes }
|
|
@@ -6630,7 +6989,10 @@ function addSlideFacts(roots, slidePath, renderedIndex, page, grid, slideWidthIn
|
|
|
6630
6989
|
...stretched && isCompleteBox(resolved) && resolved.heightPt > 0 && {
|
|
6631
6990
|
drawnRatio: resolved.widthPt / resolved.heightPt
|
|
6632
6991
|
},
|
|
6633
|
-
...natural !== void 0 && { naturalRatio: natural }
|
|
6992
|
+
...natural !== void 0 && { naturalRatio: natural },
|
|
6993
|
+
...stretched && typeof authored.w === "number" && typeof authored.h === "number" && {
|
|
6994
|
+
authoredSizeIn: { w: authored.w, h: authored.h }
|
|
6995
|
+
}
|
|
6634
6996
|
});
|
|
6635
6997
|
}
|
|
6636
6998
|
const bullets = bulletItems(box.props);
|
|
@@ -6651,7 +7013,7 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6651
7013
|
const facts = [];
|
|
6652
7014
|
const provenance = {};
|
|
6653
7015
|
let sourceMap = {};
|
|
6654
|
-
const authoredPath = (path4) => toAuthoredPointer(sourceMap, path4);
|
|
7016
|
+
const authoredPath = (path4) => toAuthoredPointer(sourceMap, path4, options.expanded?.pluginOutputs);
|
|
6655
7017
|
const addFact = (raw) => {
|
|
6656
7018
|
const fact = {
|
|
6657
7019
|
...raw,
|
|
@@ -6698,12 +7060,12 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6698
7060
|
});
|
|
6699
7061
|
});
|
|
6700
7062
|
const warnings = options.warnings ?? [];
|
|
6701
|
-
const context = resolveThemeContext(document, {
|
|
7063
|
+
const context = options.context ?? resolveThemeContext(document, {
|
|
6702
7064
|
customThemes: options.customThemes,
|
|
6703
7065
|
fonts: options.fonts,
|
|
6704
7066
|
warnings
|
|
6705
7067
|
});
|
|
6706
|
-
const expanded = expandPptxBlocks(context.document, context.theme);
|
|
7068
|
+
const expanded = options.expanded ?? expandPptxBlocks(context.document, context.theme);
|
|
6707
7069
|
sourceMap = expanded.sourceMap;
|
|
6708
7070
|
const processed = processPresentation(expanded.document, {
|
|
6709
7071
|
theme: context.theme,
|
|
@@ -6797,6 +7159,10 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6797
7159
|
(value) => `#${resolveColor(value, processed.theme)}`
|
|
6798
7160
|
) ?? SERIES_COLOR_TOKENS.filter((token) => paletteHexes[token] !== void 0);
|
|
6799
7161
|
const authoredPropsAt = (pointer) => authoredPropsAtPointer(document, authoredPath(pointer));
|
|
7162
|
+
const pluginOutputs = options.expanded?.pluginOutputs ?? [];
|
|
7163
|
+
const ownsProps = (pointer) => !pluginOutputs.some(
|
|
7164
|
+
(prefix) => pointer === prefix || pointer.startsWith(`${prefix}/`)
|
|
7165
|
+
) && authoredNameAtPointer(document, authoredPath(pointer)) !== "block";
|
|
6800
7166
|
const statedPropsAt = (pointer) => authoredPropsAtPointer(expanded.document, pointer);
|
|
6801
7167
|
let exportedPages = 0;
|
|
6802
7168
|
processed.slides.forEach((slide, renderedIndex) => {
|
|
@@ -6834,7 +7200,8 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6834
7200
|
authoredPropsAt,
|
|
6835
7201
|
statedPropsAt,
|
|
6836
7202
|
authoredPath,
|
|
6837
|
-
addFact
|
|
7203
|
+
addFact,
|
|
7204
|
+
ownsProps
|
|
6838
7205
|
);
|
|
6839
7206
|
});
|
|
6840
7207
|
for (const budget of blockSlotBudgets(context.document, expanded.blocks)) {
|
|
@@ -6844,10 +7211,10 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6844
7211
|
...budget
|
|
6845
7212
|
});
|
|
6846
7213
|
}
|
|
6847
|
-
const
|
|
7214
|
+
const slotNodes = nodesBySlot(processed, slideIndexes, sourceMap);
|
|
6848
7215
|
for (const role of blockSlotRoles(context.document, expanded.blocks)) {
|
|
6849
7216
|
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);
|
|
6850
|
-
const bound =
|
|
7217
|
+
const bound = slotNodes.get(role.path);
|
|
6851
7218
|
let measured;
|
|
6852
7219
|
if (bound && typeof bound.props.text === "string") {
|
|
6853
7220
|
const typography = resolveTypography(bound.props, ctx);
|
|
@@ -6885,6 +7252,30 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6885
7252
|
...measured
|
|
6886
7253
|
});
|
|
6887
7254
|
}
|
|
7255
|
+
const footerType = typeof processed.theme.chrome?.confidentialFooter?.type === "string" ? processed.theme.chrome.confidentialFooter.type : "footer";
|
|
7256
|
+
processed.slides.forEach((slide, renderedIndex) => {
|
|
7257
|
+
const authoredIndex = slideIndexes[renderedIndex];
|
|
7258
|
+
if (authoredIndex === void 0) return;
|
|
7259
|
+
const slidePath = `/children/${authoredIndex}`;
|
|
7260
|
+
const onSlide = (fact) => "slidePath" in fact && fact.slidePath === slidePath;
|
|
7261
|
+
const texts = facts.filter(
|
|
7262
|
+
(fact) => (fact.kind === "pptx/text" || fact.kind === "pptx/rich-text") && onSlide(fact)
|
|
7263
|
+
);
|
|
7264
|
+
addFact({
|
|
7265
|
+
id: `pptx:slide-chrome:${slidePath}`,
|
|
7266
|
+
kind: "pptx/slide-chrome",
|
|
7267
|
+
path: slidePath,
|
|
7268
|
+
index: renderedIndex,
|
|
7269
|
+
...slide.hidden === true && { hidden: true },
|
|
7270
|
+
blocks: expanded.blocks.filter(
|
|
7271
|
+
(pointer) => pointer.startsWith(`${slidePath}/children/`)
|
|
7272
|
+
).length,
|
|
7273
|
+
pageNumber: texts.some((fact) => fact.text.includes("{PAGE_NUMBER}")),
|
|
7274
|
+
footer: texts.some((fact) => fact.styleName === footerType) || facts.some(
|
|
7275
|
+
(fact) => fact.kind === "pptx/chrome-slot" && fact.role === "footer" && fact.present && onSlide(fact)
|
|
7276
|
+
)
|
|
7277
|
+
});
|
|
7278
|
+
});
|
|
6888
7279
|
return {
|
|
6889
7280
|
format: "pptx",
|
|
6890
7281
|
model: {
|
|
@@ -6907,7 +7298,7 @@ function preparePptxQualityDocument(document, options = {}) {
|
|
|
6907
7298
|
}
|
|
6908
7299
|
};
|
|
6909
7300
|
}
|
|
6910
|
-
function
|
|
7301
|
+
function nodesBySlot(processed, slideIndexes, sourceMap) {
|
|
6911
7302
|
const found = /* @__PURE__ */ new Map();
|
|
6912
7303
|
const visit = (component, path4) => {
|
|
6913
7304
|
if (component.enabled === false) return;
|
|
@@ -6915,6 +7306,10 @@ function textNodesBySlot(processed, slideIndexes, sourceMap) {
|
|
|
6915
7306
|
const origin = toAuthoredPointer(sourceMap, `${path4}/props/text`);
|
|
6916
7307
|
if (origin !== `${path4}/props/text` && !found.has(origin))
|
|
6917
7308
|
found.set(origin, { props: asRecord(component.props) ?? {}, path: path4 });
|
|
7309
|
+
} else {
|
|
7310
|
+
const origin = toAuthoredPointer(sourceMap, path4);
|
|
7311
|
+
if (origin !== path4 && origin.includes("/props/slots/") && !found.has(origin))
|
|
7312
|
+
found.set(origin, { props: asRecord(component.props) ?? {}, path: path4 });
|
|
6918
7313
|
}
|
|
6919
7314
|
(component.children ?? []).forEach(
|
|
6920
7315
|
(child, index) => visit(child, `${path4}/children/${index}`)
|
|
@@ -7673,6 +8068,8 @@ import {
|
|
|
7673
8068
|
// src/quality/rules.ts
|
|
7674
8069
|
import {
|
|
7675
8070
|
chartInfoDesignFindings,
|
|
8071
|
+
configurationLabel,
|
|
8072
|
+
configurationSource,
|
|
7676
8073
|
DEFAULT_MAXIMUM_CHART_SERIES,
|
|
7677
8074
|
DEFAULT_MAXIMUM_PIE_SLICES,
|
|
7678
8075
|
fontCountFinding,
|
|
@@ -7813,19 +8210,24 @@ var pptxMinimumFontRule = {
|
|
|
7813
8210
|
);
|
|
7814
8211
|
return textFacts(facts).filter((fact) => fact.fontSizePt < minimum).map((fact) => ({
|
|
7815
8212
|
message: `Effective font size is ${fact.fontSizePt}pt \u2014 unreadable on a projected slide.`,
|
|
7816
|
-
|
|
8213
|
+
// Text a definition or a plugin generated reports at the invocation
|
|
8214
|
+
// the author wrote, whose props are the block's or the plugin's: no
|
|
8215
|
+
// fontSize of theirs to lift, so no patch.
|
|
8216
|
+
path: fact.ownsProps ? `${fact.path}/props` : fact.path,
|
|
7817
8217
|
suggestion: `Use at least ${minimum}pt; captions rarely work below 10pt.`,
|
|
7818
8218
|
context: { fontSize: fact.fontSizePt, threshold: minimum },
|
|
7819
8219
|
evidence: { actual: fact.fontSizePt, expected: minimum, unit: "pt" },
|
|
7820
8220
|
// `add` replaces an existing member, so this lifts an explicit
|
|
7821
8221
|
// fontSize and overrides an inherited style value alike.
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
8222
|
+
...fact.ownsProps && {
|
|
8223
|
+
fixes: [
|
|
8224
|
+
{
|
|
8225
|
+
op: "add",
|
|
8226
|
+
path: `${fact.path}/props/fontSize`,
|
|
8227
|
+
value: minimum
|
|
8228
|
+
}
|
|
8229
|
+
]
|
|
8230
|
+
}
|
|
7829
8231
|
}));
|
|
7830
8232
|
}
|
|
7831
8233
|
};
|
|
@@ -8429,18 +8831,67 @@ var pptxRequiredChromeRule = {
|
|
|
8429
8831
|
if (required.length === 0) return [];
|
|
8430
8832
|
return facts.filter(
|
|
8431
8833
|
(fact) => fact.kind === "pptx/chrome-slot"
|
|
8432
|
-
).filter((fact) => required.includes(fact.role) && !fact.present).map((fact) =>
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8834
|
+
).filter((fact) => required.includes(fact.role) && !fact.present).map((fact) => {
|
|
8835
|
+
const source = configurationSource(configuration, "required");
|
|
8836
|
+
return {
|
|
8837
|
+
path: fact.path,
|
|
8838
|
+
relatedPaths: [fact.invocation],
|
|
8839
|
+
message: `${fact.block} states no ${fact.role} in its "${fact.slot}" slot; ${configurationLabel(source, profile)} expects one on every ${fact.block}.`,
|
|
8840
|
+
suggestion: `Fill the "${fact.slot}" slot. The theme already styles it.`,
|
|
8841
|
+
context: { block: fact.block, slot: fact.slot, role: fact.role },
|
|
8842
|
+
evidence: {
|
|
8843
|
+
actual: "empty",
|
|
8844
|
+
expected: fact.role,
|
|
8845
|
+
values: { source, required }
|
|
8846
|
+
}
|
|
8847
|
+
};
|
|
8848
|
+
});
|
|
8849
|
+
}
|
|
8850
|
+
};
|
|
8851
|
+
var SLIDE_CHROME_PARTS = ["pageNumber", "footer"];
|
|
8852
|
+
var pptxSlideFooterRule = {
|
|
8853
|
+
id: "pptx/slide-footer",
|
|
8854
|
+
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.",
|
|
8855
|
+
code: QUALITY_CODES.CHROME_MISSING,
|
|
8856
|
+
category: "consistency",
|
|
8857
|
+
defaultSeverity: "warning",
|
|
8858
|
+
defaultCertainty: "deterministic",
|
|
8859
|
+
formats: ["pptx"],
|
|
8860
|
+
defaultParameters: { required: [], fromSlide: 1 },
|
|
8861
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
8862
|
+
const required = stringListParameter(
|
|
8863
|
+
configuration.parameters,
|
|
8864
|
+
"required"
|
|
8865
|
+
).filter(
|
|
8866
|
+
(part) => SLIDE_CHROME_PARTS.includes(part)
|
|
8867
|
+
);
|
|
8868
|
+
if (required.length === 0) return [];
|
|
8869
|
+
const from = numberParameter(configuration.parameters, "fromSlide", 1);
|
|
8870
|
+
return facts.filter(
|
|
8871
|
+
(fact) => fact.kind === "pptx/slide-chrome" && fact.blocks > 0 && fact.hidden !== true && fact.index >= from
|
|
8872
|
+
).flatMap((fact) => {
|
|
8873
|
+
const missing = required.filter((part) => !fact[part]);
|
|
8874
|
+
if (missing.length === 0) return [];
|
|
8875
|
+
const parts = missing.map((part) => part === "pageNumber" ? "page number" : "footer").join(" or ");
|
|
8876
|
+
const source = configurationSource(
|
|
8877
|
+
configuration,
|
|
8878
|
+
"required",
|
|
8879
|
+
"fromSlide"
|
|
8880
|
+
);
|
|
8881
|
+
return [
|
|
8882
|
+
{
|
|
8883
|
+
path: fact.path,
|
|
8884
|
+
message: `Slide ${fact.index + 1} carries no ${parts}; ${configurationLabel(source, profile)} expects one on every slide a block builds from slide ${from + 1} on.`,
|
|
8885
|
+
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.",
|
|
8886
|
+
context: { slide: fact.index, missing },
|
|
8887
|
+
evidence: {
|
|
8888
|
+
actual: required.filter((part) => fact[part]),
|
|
8889
|
+
expected: required,
|
|
8890
|
+
values: { source, fromSlide: from }
|
|
8891
|
+
}
|
|
8892
|
+
}
|
|
8893
|
+
];
|
|
8894
|
+
});
|
|
8444
8895
|
}
|
|
8445
8896
|
};
|
|
8446
8897
|
var pptxActionTitleRule = {
|
|
@@ -8473,6 +8924,8 @@ var pptxActionTitleRule = {
|
|
|
8473
8924
|
}
|
|
8474
8925
|
};
|
|
8475
8926
|
var TITLE_DRIFT_TOLERANCE_PT = 2;
|
|
8927
|
+
var FIRST_BASELINE_EM = 0.8;
|
|
8928
|
+
var TITLE_PLACEMENT_LINES = 2;
|
|
8476
8929
|
var PPTX_TYPE_VOCABULARY = {
|
|
8477
8930
|
subject: "deck",
|
|
8478
8931
|
keepTo: "Keep to the theme styles \u2014 title, heading, body, label, statistic \u2014 and drop the ad-hoc sizes."
|
|
@@ -8549,11 +9002,15 @@ var pptxSizeCountRule = {
|
|
|
8549
9002
|
defaultParameters: { maximumSizes: 8, maximumSizesPerSlide: 0 },
|
|
8550
9003
|
evaluate: ({ facts, configuration, profile }) => {
|
|
8551
9004
|
const sizes = paintedSizes(facts);
|
|
9005
|
+
const setBy = (parameter) => {
|
|
9006
|
+
const source = configurationSource(configuration, parameter);
|
|
9007
|
+
return { source, label: configurationLabel(source, profile) };
|
|
9008
|
+
};
|
|
8552
9009
|
const findings = sizeCountFinding(
|
|
8553
9010
|
sizes,
|
|
8554
9011
|
numberParameter(configuration.parameters, "maximumSizes", 8),
|
|
8555
9012
|
pptxThemeFact(facts)?.path ?? "/props",
|
|
8556
|
-
|
|
9013
|
+
setBy("maximumSizes"),
|
|
8557
9014
|
PPTX_TYPE_VOCABULARY
|
|
8558
9015
|
).map((finding) => ({
|
|
8559
9016
|
...finding,
|
|
@@ -8576,7 +9033,7 @@ var pptxSizeCountRule = {
|
|
|
8576
9033
|
onSlide,
|
|
8577
9034
|
perSlide,
|
|
8578
9035
|
slidePath,
|
|
8579
|
-
|
|
9036
|
+
setBy("maximumSizesPerSlide"),
|
|
8580
9037
|
PPTX_SLIDE_TYPE_VOCABULARY
|
|
8581
9038
|
))
|
|
8582
9039
|
findings.push({
|
|
@@ -8600,53 +9057,107 @@ var pptxRoleDriftRule = {
|
|
|
8600
9057
|
return theme ? roleDriftFindings(paintedSizes(facts), theme.roleSizesPt) : [];
|
|
8601
9058
|
}
|
|
8602
9059
|
};
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
9060
|
+
var EDGE_NAMES = {
|
|
9061
|
+
left: "left edge",
|
|
9062
|
+
center: "centre line",
|
|
9063
|
+
right: "right edge"
|
|
9064
|
+
};
|
|
9065
|
+
function laidOutTitle(fact, path4, order) {
|
|
9066
|
+
if (fact.boxXPt === void 0 || fact.boxYPt === void 0 || fact.boxWidthPt === void 0 || fact.rotationDeg % 360 !== 0)
|
|
9067
|
+
return void 0;
|
|
9068
|
+
const [top, right, bottom, left] = fact.insetsPt;
|
|
9069
|
+
const innerWidth = Math.max(1, fact.boxWidthPt - left - right);
|
|
9070
|
+
const { heightPt, lines } = estimateTextHeightPt(
|
|
9071
|
+
fact.text,
|
|
9072
|
+
innerWidth,
|
|
9073
|
+
fact.fontSizePt,
|
|
9074
|
+
fact.lineSpacingPt,
|
|
9075
|
+
fact.paraSpaceBeforePt,
|
|
9076
|
+
fact.paraSpaceAfterPt
|
|
8606
9077
|
);
|
|
8607
|
-
|
|
9078
|
+
let textTop = fact.boxYPt + top;
|
|
9079
|
+
if (fact.boxHeightPt !== void 0 && !fact.autoFit) {
|
|
9080
|
+
const room = fact.boxHeightPt - top - bottom - heightPt;
|
|
9081
|
+
if (fact.verticalAlign === "middle") textTop += room / 2;
|
|
9082
|
+
else if (fact.verticalAlign === "bottom") textTop += room;
|
|
9083
|
+
}
|
|
9084
|
+
const align = fact.align === "justify" ? "left" : fact.align;
|
|
9085
|
+
const edgePt = align === "center" ? fact.boxXPt + left + innerWidth / 2 : align === "right" ? fact.boxXPt + fact.boxWidthPt - right : fact.boxXPt + left;
|
|
9086
|
+
const style = fact.styleName ?? "unstyled";
|
|
9087
|
+
const setSizePt = fact.fitFromPt ?? fact.fontSizePt;
|
|
9088
|
+
return {
|
|
9089
|
+
fact,
|
|
9090
|
+
path: path4,
|
|
9091
|
+
kind: `${style}|${setSizePt}|${align}`,
|
|
9092
|
+
style,
|
|
9093
|
+
setSizePt,
|
|
9094
|
+
align,
|
|
9095
|
+
lines,
|
|
9096
|
+
baselinePt: textTop + FIRST_BASELINE_EM * fact.fontSizePt,
|
|
9097
|
+
edgePt,
|
|
9098
|
+
order
|
|
9099
|
+
};
|
|
9100
|
+
}
|
|
9101
|
+
function deckTitles(facts, styles) {
|
|
9102
|
+
const texts = textFacts(facts).filter((fact) => !fact.slideHidden);
|
|
9103
|
+
const slideIndex = (fact) => Number(/^\/children\/(\d+)/.exec(fact.slidePath)?.[1] ?? 0);
|
|
9104
|
+
const order = new Map(
|
|
9105
|
+
texts.map((fact, index) => [fact, slideIndex(fact) * 1e4 + index])
|
|
9106
|
+
);
|
|
9107
|
+
const byNode = new Map(texts.map((fact) => [fact.nodePath, fact]));
|
|
9108
|
+
const titles = [];
|
|
8608
9109
|
const claimed = /* @__PURE__ */ new Set();
|
|
8609
|
-
const
|
|
9110
|
+
const add = (fact, path4) => {
|
|
8610
9111
|
if (claimed.has(fact)) return;
|
|
8611
9112
|
claimed.add(fact);
|
|
8612
|
-
|
|
9113
|
+
const title = laidOutTitle(fact, path4, order.get(fact));
|
|
9114
|
+
if (title) titles.push(title);
|
|
8613
9115
|
};
|
|
8614
|
-
for (const slot of facts)
|
|
8615
|
-
if (slot.kind
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
const painted = texts.find(
|
|
8620
|
-
(fact) => fact.slidePath === chrome.slidePath && fact.text === chrome.text
|
|
8621
|
-
);
|
|
8622
|
-
if (painted) push(chrome.block, painted);
|
|
8623
|
-
}
|
|
9116
|
+
for (const slot of facts)
|
|
9117
|
+
if (slot.kind === "pptx/chrome-slot" && slot.role === "actionTitle" && slot.nodePath !== void 0) {
|
|
9118
|
+
const painted = byNode.get(slot.nodePath);
|
|
9119
|
+
if (painted) add(painted, slot.path);
|
|
9120
|
+
}
|
|
8624
9121
|
for (const fact of texts)
|
|
8625
9122
|
if (fact.styleName !== void 0 && styles.includes(fact.styleName))
|
|
8626
|
-
|
|
8627
|
-
return
|
|
8628
|
-
}
|
|
8629
|
-
function
|
|
8630
|
-
const
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
9123
|
+
add(fact, fact.path);
|
|
9124
|
+
return titles.sort((a, b) => a.order - b.order);
|
|
9125
|
+
}
|
|
9126
|
+
function titlePlacements(titles, tolerance) {
|
|
9127
|
+
const lands = (a, b) => Math.abs(a.baselinePt - b.baselinePt) <= tolerance && Math.abs(a.edgePt - b.edgePt) <= tolerance;
|
|
9128
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
9129
|
+
for (const title of titles)
|
|
9130
|
+
byKind.set(title.kind, [...byKind.get(title.kind) ?? [], title]);
|
|
9131
|
+
const placements = [];
|
|
9132
|
+
for (const kind of byKind.values()) {
|
|
9133
|
+
let pending = kind;
|
|
9134
|
+
while (pending.length > 0) {
|
|
9135
|
+
let reference = pending[0];
|
|
9136
|
+
let most = 0;
|
|
9137
|
+
for (const candidate of pending) {
|
|
9138
|
+
const count = pending.filter((other) => lands(other, candidate)).length;
|
|
9139
|
+
if (count > most) {
|
|
9140
|
+
reference = candidate;
|
|
9141
|
+
most = count;
|
|
9142
|
+
}
|
|
9143
|
+
}
|
|
9144
|
+
const reach = TITLE_PLACEMENT_LINES * reference.fact.lineSpacingPt;
|
|
9145
|
+
const members = pending.filter(
|
|
9146
|
+
(title) => Math.abs(title.baselinePt - reference.baselinePt) <= reach && Math.abs(title.edgePt - reference.edgePt) <= reach
|
|
9147
|
+
);
|
|
9148
|
+
placements.push({ reference, members });
|
|
9149
|
+
pending = pending.filter((title) => !members.includes(title));
|
|
8639
9150
|
}
|
|
8640
9151
|
}
|
|
8641
|
-
return
|
|
9152
|
+
return placements;
|
|
8642
9153
|
}
|
|
8643
9154
|
var pptxTitleDriftRule = {
|
|
8644
9155
|
id: "pptx/title-drift",
|
|
8645
|
-
description: "A slide title
|
|
9156
|
+
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.",
|
|
8646
9157
|
code: QUALITY_CODES.TITLE_DRIFT,
|
|
8647
9158
|
category: "consistency",
|
|
8648
9159
|
defaultSeverity: "warning",
|
|
8649
|
-
defaultCertainty: "
|
|
9160
|
+
defaultCertainty: "estimated",
|
|
8650
9161
|
formats: ["pptx"],
|
|
8651
9162
|
defaultEnabled: false,
|
|
8652
9163
|
defaultParameters: {
|
|
@@ -8661,40 +9172,53 @@ var pptxTitleDriftRule = {
|
|
|
8661
9172
|
TITLE_DRIFT_TOLERANCE_PT
|
|
8662
9173
|
);
|
|
8663
9174
|
const findings = [];
|
|
8664
|
-
for (const
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
9175
|
+
for (const { reference, members } of titlePlacements(
|
|
9176
|
+
deckTitles(facts, styles),
|
|
9177
|
+
tolerance
|
|
9178
|
+
)) {
|
|
9179
|
+
if (members.length < 2) continue;
|
|
9180
|
+
const agreeing = members.filter(
|
|
9181
|
+
(title) => Math.abs(title.baselinePt - reference.baselinePt) <= tolerance && Math.abs(title.edgePt - reference.edgePt) <= tolerance
|
|
9182
|
+
);
|
|
9183
|
+
for (const title of members) {
|
|
9184
|
+
if (agreeing.includes(title)) continue;
|
|
9185
|
+
const edge = EDGE_NAMES[title.align];
|
|
9186
|
+
const off = [
|
|
9187
|
+
{
|
|
9188
|
+
axis: "first baseline",
|
|
9189
|
+
actual: title.baselinePt,
|
|
9190
|
+
expected: reference.baselinePt
|
|
9191
|
+
},
|
|
9192
|
+
{ axis: edge, actual: title.edgePt, expected: reference.edgePt }
|
|
9193
|
+
].filter(
|
|
9194
|
+
(entry) => Math.abs(entry.actual - entry.expected) > tolerance
|
|
8681
9195
|
);
|
|
8682
|
-
if (off.length === 0) continue;
|
|
8683
9196
|
const [first] = off;
|
|
9197
|
+
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.` : "";
|
|
8684
9198
|
findings.push({
|
|
8685
|
-
path:
|
|
8686
|
-
|
|
8687
|
-
|
|
9199
|
+
path: title.path,
|
|
9200
|
+
relatedPaths: agreeing.map((other) => other.path),
|
|
9201
|
+
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}`,
|
|
9202
|
+
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.",
|
|
8688
9203
|
context: {
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
9204
|
+
style: title.style,
|
|
9205
|
+
setSizePt: title.setSizePt,
|
|
9206
|
+
align: title.align,
|
|
9207
|
+
axes: off.map((entry) => entry.axis),
|
|
9208
|
+
slide: title.fact.slidePath
|
|
8692
9209
|
},
|
|
8693
9210
|
evidence: {
|
|
8694
|
-
|
|
8695
|
-
|
|
9211
|
+
summary: "estimated first baseline and aligned edge",
|
|
9212
|
+
actual: round(first.actual),
|
|
9213
|
+
expected: round(first.expected),
|
|
8696
9214
|
unit: "pt",
|
|
8697
|
-
values: {
|
|
9215
|
+
values: {
|
|
9216
|
+
axis: first.axis,
|
|
9217
|
+
lines: title.lines,
|
|
9218
|
+
fontSizePt: title.fact.fontSizePt,
|
|
9219
|
+
verticalAlign: title.fact.verticalAlign,
|
|
9220
|
+
source: configurationSource(configuration, "tolerancePt")
|
|
9221
|
+
}
|
|
8698
9222
|
}
|
|
8699
9223
|
});
|
|
8700
9224
|
}
|
|
@@ -8747,7 +9271,9 @@ var pptxBulletRule = {
|
|
|
8747
9271
|
actual: bullets.items,
|
|
8748
9272
|
expected: maximumBullets,
|
|
8749
9273
|
unit: "bullets",
|
|
8750
|
-
values: {
|
|
9274
|
+
values: {
|
|
9275
|
+
source: configurationSource(configuration, "maximumBullets")
|
|
9276
|
+
}
|
|
8751
9277
|
}
|
|
8752
9278
|
});
|
|
8753
9279
|
if (maximumWords > 0 && bullets.longestWords > maximumWords)
|
|
@@ -8761,16 +9287,39 @@ var pptxBulletRule = {
|
|
|
8761
9287
|
actual: bullets.longestWords,
|
|
8762
9288
|
expected: maximumWords,
|
|
8763
9289
|
unit: "words",
|
|
8764
|
-
values: {
|
|
9290
|
+
values: {
|
|
9291
|
+
source: configurationSource(
|
|
9292
|
+
configuration,
|
|
9293
|
+
"maximumWordsPerBullet"
|
|
9294
|
+
)
|
|
9295
|
+
}
|
|
8765
9296
|
}
|
|
8766
9297
|
});
|
|
8767
9298
|
}
|
|
8768
9299
|
return findings;
|
|
8769
9300
|
}
|
|
8770
9301
|
};
|
|
9302
|
+
function safeAreaFixes(fact, canvas, safe) {
|
|
9303
|
+
const position = fact.authoredPositionIn;
|
|
9304
|
+
if (position === void 0 || fact.widthPt > canvas.widthPt - 2 * safe || fact.heightPt > canvas.heightPt - 2 * safe)
|
|
9305
|
+
return [];
|
|
9306
|
+
const clamp = (valuePt, sizePt, extentPt) => {
|
|
9307
|
+
const low = Math.ceil(safe / 72 * 1e3) / 1e3;
|
|
9308
|
+
const high = Math.floor((extentPt - safe - sizePt) / 72 * 1e3) / 1e3;
|
|
9309
|
+
return Math.min(Math.max(valuePt / 72, low), high);
|
|
9310
|
+
};
|
|
9311
|
+
const fixes = [];
|
|
9312
|
+
const x = clamp(fact.xPt, fact.widthPt, canvas.widthPt);
|
|
9313
|
+
const y = clamp(fact.yPt, fact.heightPt, canvas.heightPt);
|
|
9314
|
+
if (Math.abs(x - position.x) > 5e-4)
|
|
9315
|
+
fixes.push({ op: "replace", path: `${fact.path}/props/x`, value: x });
|
|
9316
|
+
if (Math.abs(y - position.y) > 5e-4)
|
|
9317
|
+
fixes.push({ op: "replace", path: `${fact.path}/props/y`, value: y });
|
|
9318
|
+
return fixes;
|
|
9319
|
+
}
|
|
8771
9320
|
var pptxSafeAreaRule = {
|
|
8772
9321
|
id: "pptx/safe-area",
|
|
8773
|
-
description: "Content outside the theme\u2019s safe area that is neither chrome nor a full bleed. Off until a profile or policy enables it.",
|
|
9322
|
+
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.",
|
|
8774
9323
|
code: QUALITY_CODES.SAFE_AREA,
|
|
8775
9324
|
category: "composition",
|
|
8776
9325
|
defaultSeverity: "warning",
|
|
@@ -8791,7 +9340,7 @@ var pptxSafeAreaRule = {
|
|
|
8791
9340
|
if (canvas === void 0 || safe === void 0 || safe <= 0) return [];
|
|
8792
9341
|
const chromeNodes = new Set(
|
|
8793
9342
|
facts.filter(
|
|
8794
|
-
(fact) => fact.kind === "pptx/chrome-slot" && (fact.role === "tracker" || fact.role === "footer" || fact.role === "source")
|
|
9343
|
+
(fact) => fact.kind === "pptx/chrome-slot" && (fact.role === "tracker" || fact.role === "footer" || fact.role === "source" || fact.role === "logo")
|
|
8795
9344
|
).flatMap((fact) => fact.nodePath ? [fact.nodePath] : [])
|
|
8796
9345
|
);
|
|
8797
9346
|
const chromeStyles = /* @__PURE__ */ new Set(["footer", "tracker", "source"]);
|
|
@@ -8817,6 +9366,7 @@ var pptxSafeAreaRule = {
|
|
|
8817
9366
|
right - (canvas.widthPt - safe),
|
|
8818
9367
|
bottom - (canvas.heightPt - safe)
|
|
8819
9368
|
);
|
|
9369
|
+
const fixes = safeAreaFixes(fact, canvas, safe);
|
|
8820
9370
|
return [
|
|
8821
9371
|
{
|
|
8822
9372
|
path: fact.path,
|
|
@@ -8828,7 +9378,8 @@ var pptxSafeAreaRule = {
|
|
|
8828
9378
|
expected: 0,
|
|
8829
9379
|
unit: "pt",
|
|
8830
9380
|
values: { source: "theme" }
|
|
8831
|
-
}
|
|
9381
|
+
},
|
|
9382
|
+
...fixes.length > 0 && { fixes }
|
|
8832
9383
|
}
|
|
8833
9384
|
];
|
|
8834
9385
|
});
|
|
@@ -8864,7 +9415,7 @@ var pptxSlideTitleRule = {
|
|
|
8864
9415
|
actual: 0,
|
|
8865
9416
|
expected: 1,
|
|
8866
9417
|
unit: "titles",
|
|
8867
|
-
values: { source:
|
|
9418
|
+
values: { source: configurationSource(configuration) }
|
|
8868
9419
|
}
|
|
8869
9420
|
}));
|
|
8870
9421
|
}
|
|
@@ -8913,7 +9464,20 @@ var pptxImageAspectRule = {
|
|
|
8913
9464
|
{
|
|
8914
9465
|
path: fact.path,
|
|
8915
9466
|
drawn: fact.drawnRatio,
|
|
8916
|
-
natural: fact.naturalRatio
|
|
9467
|
+
natural: fact.naturalRatio,
|
|
9468
|
+
...fact.authoredSizeIn && {
|
|
9469
|
+
sides: {
|
|
9470
|
+
width: {
|
|
9471
|
+
path: `${fact.path}/props/w`,
|
|
9472
|
+
value: fact.authoredSizeIn.w
|
|
9473
|
+
},
|
|
9474
|
+
height: {
|
|
9475
|
+
path: `${fact.path}/props/h`,
|
|
9476
|
+
value: fact.authoredSizeIn.h
|
|
9477
|
+
},
|
|
9478
|
+
decimals: 3
|
|
9479
|
+
}
|
|
9480
|
+
}
|
|
8917
9481
|
},
|
|
8918
9482
|
"slide",
|
|
8919
9483
|
tolerance
|
|
@@ -8939,6 +9503,7 @@ var PPTX_QUALITY_RULES = {
|
|
|
8939
9503
|
pptxOffCanvasRule,
|
|
8940
9504
|
pptxSlotBudgetRule,
|
|
8941
9505
|
pptxRequiredChromeRule,
|
|
9506
|
+
pptxSlideFooterRule,
|
|
8942
9507
|
pptxActionTitleRule,
|
|
8943
9508
|
pptxTypeScaleRule,
|
|
8944
9509
|
pptxSizeCountRule,
|
|
@@ -8973,11 +9538,12 @@ var PPTX_QUALITY_PROFILES = {
|
|
|
8973
9538
|
"consulting-deck": {
|
|
8974
9539
|
id: "consulting-deck",
|
|
8975
9540
|
formats: ["pptx"],
|
|
8976
|
-
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, 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.",
|
|
9541
|
+
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.",
|
|
8977
9542
|
rules: {
|
|
8978
9543
|
"pptx/required-chrome": {
|
|
8979
9544
|
parameters: { required: ["takeaway", "source"] }
|
|
8980
9545
|
},
|
|
9546
|
+
"pptx/slide-footer": { parameters: { required: ["pageNumber"] } },
|
|
8981
9547
|
"pptx/action-title": { parameters: { maxLines: 2 } },
|
|
8982
9548
|
"pptx/slide-density": { parameters: { maximumBodyWords: 90 } },
|
|
8983
9549
|
"pptx/type-scale": { enabled: true },
|
|
@@ -9270,97 +9836,152 @@ function createBuilderImpl(state) {
|
|
|
9270
9836
|
newState
|
|
9271
9837
|
);
|
|
9272
9838
|
}
|
|
9273
|
-
async function
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9839
|
+
async function expandPresentation(document, options) {
|
|
9840
|
+
let internalDocument = document;
|
|
9841
|
+
const renderer = options?.renderer ?? state.renderer ?? internalDocument.renderer;
|
|
9842
|
+
const validationDocument = renderer === void 0 ? internalDocument : { ...internalDocument, renderer };
|
|
9843
|
+
const validationOptions = {
|
|
9844
|
+
...state.validation,
|
|
9845
|
+
...options?.validation
|
|
9846
|
+
};
|
|
9847
|
+
if (validationOptions.enabled !== false) {
|
|
9848
|
+
const result = validatePresentation(
|
|
9849
|
+
validationDocument,
|
|
9850
|
+
state.components,
|
|
9851
|
+
{ allowUnknownFields: validationOptions.allowUnknownFields }
|
|
9852
|
+
);
|
|
9853
|
+
if (!result.valid) {
|
|
9854
|
+
throw new ComponentValidationError3(result.errors, internalDocument);
|
|
9855
|
+
}
|
|
9856
|
+
} else if (!internalDocument || internalDocument.name !== "pptx") {
|
|
9857
|
+
throw new Error("Top-level component must be a pptx component");
|
|
9858
|
+
}
|
|
9859
|
+
const warnings = [];
|
|
9860
|
+
const context = resolveThemeContext(internalDocument, {
|
|
9861
|
+
customThemes: state.customThemes,
|
|
9862
|
+
fonts: state.fonts,
|
|
9863
|
+
warnings,
|
|
9864
|
+
defaultThemeName: typeof state.theme === "string" ? state.theme : void 0,
|
|
9865
|
+
resolveNamedTheme: (name, authored) => state.customThemes?.[name] ?? (authored && hasPptxTheme(name) ? getPptxTheme(name) : void 0) ?? (typeof state.theme === "object" && state.theme !== null ? state.theme : getPptxTheme(name))
|
|
9866
|
+
});
|
|
9867
|
+
const modedRoot = context.document;
|
|
9868
|
+
const resolvedTheme = context.theme;
|
|
9869
|
+
const validateEmitted = validationOptions.enabled === false ? void 0 : (emitted, componentLabel, parentName) => {
|
|
9870
|
+
let validationDocument2;
|
|
9871
|
+
if (parentName === "pptx") {
|
|
9872
|
+
validationDocument2 = {
|
|
9873
|
+
...modedRoot,
|
|
9874
|
+
...renderer !== void 0 ? { renderer } : {},
|
|
9875
|
+
children: emitted
|
|
9876
|
+
};
|
|
9877
|
+
} else if (parentName === "slide") {
|
|
9878
|
+
validationDocument2 = {
|
|
9879
|
+
...modedRoot,
|
|
9880
|
+
...renderer !== void 0 ? { renderer } : {},
|
|
9881
|
+
children: [{ name: "slide", props: {}, children: emitted }]
|
|
9882
|
+
};
|
|
9883
|
+
} else {
|
|
9884
|
+
return;
|
|
9885
|
+
}
|
|
9886
|
+
const result = validatePresentation(
|
|
9887
|
+
validationDocument2,
|
|
9888
|
+
state.components,
|
|
9889
|
+
{ allowUnknownFields: validationOptions.allowUnknownFields }
|
|
9890
|
+
);
|
|
9891
|
+
if (!result.valid) {
|
|
9892
|
+
throw new ComponentValidationError3(
|
|
9893
|
+
result.errors.map((error) => ({
|
|
9894
|
+
...error,
|
|
9895
|
+
message: `custom component '${componentLabel}' emitted invalid output \u2014 ${error.message}`
|
|
9896
|
+
})),
|
|
9897
|
+
emitted
|
|
9287
9898
|
);
|
|
9288
|
-
|
|
9289
|
-
|
|
9899
|
+
}
|
|
9900
|
+
};
|
|
9901
|
+
const expanded = await expandPptxBlocksWithPlugins(
|
|
9902
|
+
modedRoot,
|
|
9903
|
+
resolvedTheme,
|
|
9904
|
+
new Set(componentMap.keys()),
|
|
9905
|
+
pluginRenderer(warnings, resolvedTheme, validateEmitted)
|
|
9906
|
+
);
|
|
9907
|
+
const processedDocument = expanded.document;
|
|
9908
|
+
if (validationOptions.enabled !== false) {
|
|
9909
|
+
const result = validatePresentation(
|
|
9910
|
+
{
|
|
9911
|
+
...processedDocument,
|
|
9912
|
+
...renderer !== void 0 ? { renderer } : {}
|
|
9913
|
+
},
|
|
9914
|
+
[],
|
|
9915
|
+
{
|
|
9916
|
+
allowUnknownFields: validationOptions.allowUnknownFields
|
|
9290
9917
|
}
|
|
9291
|
-
|
|
9292
|
-
|
|
9918
|
+
);
|
|
9919
|
+
if (!result.valid) {
|
|
9920
|
+
throw new ComponentValidationError3(
|
|
9921
|
+
result.errors.map((error) => ({
|
|
9922
|
+
...error,
|
|
9923
|
+
message: `expanded plugin output failed validation \u2014 ${error.message}`
|
|
9924
|
+
})),
|
|
9925
|
+
processedDocument
|
|
9926
|
+
);
|
|
9293
9927
|
}
|
|
9294
|
-
|
|
9295
|
-
|
|
9928
|
+
}
|
|
9929
|
+
return { context, expanded, warnings, renderer };
|
|
9930
|
+
}
|
|
9931
|
+
async function prepareQuality(document, options) {
|
|
9932
|
+
const { context, expanded, warnings, renderer } = await expandPresentation(
|
|
9933
|
+
document,
|
|
9934
|
+
options
|
|
9935
|
+
);
|
|
9936
|
+
const prepared = preparePptxQualityDocument(
|
|
9937
|
+
document,
|
|
9938
|
+
{
|
|
9939
|
+
context,
|
|
9940
|
+
expanded,
|
|
9296
9941
|
customThemes: state.customThemes,
|
|
9297
9942
|
fonts: state.fonts,
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
}
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9943
|
+
services: state.services,
|
|
9944
|
+
...renderer !== void 0 && { renderer },
|
|
9945
|
+
warnings
|
|
9946
|
+
}
|
|
9947
|
+
);
|
|
9948
|
+
return { prepared, warnings };
|
|
9949
|
+
}
|
|
9950
|
+
async function generateFromPrepared(options) {
|
|
9951
|
+
const { prepared } = options;
|
|
9952
|
+
const { document: processedDocument, theme, processed } = prepared.model;
|
|
9953
|
+
const warnings = [];
|
|
9954
|
+
const renderer = options.renderer ?? state.renderer ?? prepared.renderer;
|
|
9955
|
+
const hasHighcharts = containsHighcharts(processed);
|
|
9956
|
+
const resolvedFonts = await resolveDocumentFonts(
|
|
9957
|
+
processedDocument,
|
|
9958
|
+
theme,
|
|
9959
|
+
warnings,
|
|
9960
|
+
state.fonts,
|
|
9961
|
+
hasHighcharts
|
|
9962
|
+
);
|
|
9963
|
+
const chartFonts = hasHighcharts ? toChartFontFaces2(resolvedFonts) : [];
|
|
9964
|
+
const buffer = await runWithBaseDir(
|
|
9965
|
+
options.baseDir ?? state.baseDir,
|
|
9966
|
+
() => renderProcessedViaIr(processed, warnings, {
|
|
9967
|
+
renderer,
|
|
9968
|
+
services: state.services,
|
|
9969
|
+
deterministic: options.deterministic ?? state.packaging.deterministic,
|
|
9970
|
+
generatedAt: options.generatedAt ?? state.packaging.generatedAt,
|
|
9971
|
+
...chartFonts.length > 0 ? { chartFonts } : {}
|
|
9972
|
+
})
|
|
9973
|
+
);
|
|
9974
|
+
return { buffer, warnings };
|
|
9975
|
+
}
|
|
9976
|
+
async function generate(document, options) {
|
|
9977
|
+
try {
|
|
9978
|
+
if (options?.prepared)
|
|
9979
|
+
return await generateFromPrepared(
|
|
9980
|
+
options
|
|
9325
9981
|
);
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
result.errors.map((error) => ({
|
|
9329
|
-
...error,
|
|
9330
|
-
message: `custom component '${componentLabel}' emitted invalid output \u2014 ${error.message}`
|
|
9331
|
-
})),
|
|
9332
|
-
emitted
|
|
9333
|
-
);
|
|
9334
|
-
}
|
|
9335
|
-
};
|
|
9336
|
-
const expanded = await expandPptxBlocksWithPlugins(
|
|
9337
|
-
modedRoot,
|
|
9338
|
-
resolvedTheme,
|
|
9339
|
-
new Set(componentMap.keys()),
|
|
9340
|
-
pluginRenderer(warnings, resolvedTheme, validateEmitted)
|
|
9341
|
-
);
|
|
9982
|
+
const { context, expanded, warnings, renderer } = await expandPresentation(document, options);
|
|
9983
|
+
const resolvedTheme = context.theme;
|
|
9342
9984
|
const processedDocument = expanded.document;
|
|
9343
|
-
if (validationOptions.enabled !== false) {
|
|
9344
|
-
const result = validatePresentation(
|
|
9345
|
-
{
|
|
9346
|
-
...processedDocument,
|
|
9347
|
-
...renderer !== void 0 ? { renderer } : {}
|
|
9348
|
-
},
|
|
9349
|
-
[],
|
|
9350
|
-
{
|
|
9351
|
-
allowUnknownFields: validationOptions.allowUnknownFields
|
|
9352
|
-
}
|
|
9353
|
-
);
|
|
9354
|
-
if (!result.valid) {
|
|
9355
|
-
throw new ComponentValidationError3(
|
|
9356
|
-
result.errors.map((error) => ({
|
|
9357
|
-
...error,
|
|
9358
|
-
message: `expanded plugin output failed validation \u2014 ${error.message}`
|
|
9359
|
-
})),
|
|
9360
|
-
processedDocument
|
|
9361
|
-
);
|
|
9362
|
-
}
|
|
9363
|
-
}
|
|
9364
9985
|
const processed = processPresentation(processedDocument, {
|
|
9365
9986
|
theme: resolvedTheme,
|
|
9366
9987
|
services: state.services,
|
|
@@ -9459,6 +10080,7 @@ function createBuilderImpl(state) {
|
|
|
9459
10080
|
generate,
|
|
9460
10081
|
generateBuffer: generate,
|
|
9461
10082
|
generateFile,
|
|
10083
|
+
prepareQuality,
|
|
9462
10084
|
getComponentNames,
|
|
9463
10085
|
validate,
|
|
9464
10086
|
generateSchema,
|