@json-to-office/core-pptx 7.0.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/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
- sink.push({ objectName, xml });
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(opts, fill2, elementPath, ctx.pendingFills);
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) applyFill(opts, element.fill, element.path, ctx);
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) applyFill(opts, element.fill, element.path, ctx);
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
- out = out.slice(0, solidStart) + fill2.xml + out.slice(solidEnd + solidEndTag.length);
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 withFills = applyPendingFills(xml, options.pendingFills);
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 buildPresentation(ir, pendingFills, warnings) {
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 = { pptx, resources, pendingFills, warnings };
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 emitSlide(pptx, slideIr, ctx) {
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) slide.background = 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 slideIr.elements) {
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 { assetUnreadableError } from "@json-to-office/shared/rendering";
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
- return new Uint8Array(await readFile(origin.path));
2009
- }
2010
- const response = await fetch(origin.url);
2011
- if (!response.ok) {
2012
- throw new Error(`HTTP ${response.status} ${response.statusText}`);
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
- return new Uint8Array(await response.arrayBuffer());
2326
+ const defect = imageIntegrityDefect3(bytes);
2327
+ if (defect) throw new Error(defect);
2328
+ return bytes;
2015
2329
  } catch (error) {
2016
- throw assetUnreadableError(location, error);
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: compileChartLabelFont(
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: compileChartLabelFont(
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 PX_PER_INCH = 96;
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 / PX_PER_INCH,
4025
- h: props.h ?? chart.heightPx / PX_PER_INCH
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, cache) {
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) / PX_PER_INCH * 72;
4449
+ return (props.options.chart?.width ?? DEFAULT_CHART_WIDTH_PX) / PX_PER_INCH2 * 72;
4108
4450
  }
4109
4451
  return void 0;
4110
4452
  }
@@ -6038,6 +6380,7 @@ function fillColorHexes(fill2, theme) {
6038
6380
  return [...new Set(found)];
6039
6381
  }
6040
6382
  var FOCUS_CORNERS = {
6383
+ center: [0.5, 0.5],
6041
6384
  topLeft: [0, 0],
6042
6385
  topRight: [1, 0],
6043
6386
  bottomLeft: [0, 1],
@@ -6053,7 +6396,7 @@ function lerpHex(a, b, t) {
6053
6396
  }
6054
6397
  function gradientPosition(gradient, fx, fy, widthUnits, heightUnits) {
6055
6398
  if (gradient.type === "radial") {
6056
- const focus = FOCUS_CORNERS[typeof gradient.focus === "string" ? gradient.focus : "topLeft"] ?? FOCUS_CORNERS.topLeft;
6399
+ const focus = FOCUS_CORNERS[typeof gradient.focus === "string" ? gradient.focus : "center"] ?? FOCUS_CORNERS.center;
6057
6400
  const radius = Math.hypot(widthUnits, heightUnits) / 2;
6058
6401
  if (radius === 0) return 0;
6059
6402
  const distance = Math.hypot(