@json-to-office/core-docx 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"createDocumentGenerator.d.ts","sourceRoot":"","sources":["../../src/plugin/createDocumentGenerator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAK7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9E,OAAO,KAAK,EAEV,wBAAwB,EAOxB,2BAA2B,EAC5B,MAAM,SAAS,CAAC;AAmBjB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3C,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,oFAAoF;IACpF,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAyoBD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,wBAAwB,CAAC,SAAS,EAAE,CAAC,CAiBvC"}
1
+ {"version":3,"file":"createDocumentGenerator.d.ts","sourceRoot":"","sources":["../../src/plugin/createDocumentGenerator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAK7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9E,OAAO,KAAK,EAEV,wBAAwB,EAQxB,2BAA2B,EAC5B,MAAM,SAAS,CAAC;AAmBjB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3C,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,oFAAoF;IACpF,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8sBD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,wBAAwB,CAAC,SAAS,EAAE,CAAC,CAiBvC"}
@@ -7146,6 +7146,7 @@ async function renderHighchartsComponent(component, theme, themeName, context) {
7146
7146
  }
7147
7147
 
7148
7148
  // src/components/visual.ts
7149
+ import { createHash as createHash4 } from "crypto";
7149
7150
  import {
7150
7151
  clampVisualDpi,
7151
7152
  DEFAULT_VISUAL_DPI
@@ -7188,7 +7189,14 @@ function visualToImageOptions(props) {
7188
7189
  ...props.keepLines !== void 0 && { keepLines: props.keepLines }
7189
7190
  };
7190
7191
  }
7191
- async function rasterize(presentation, dpi, propsServerUrl, serviceConfig, baseDir) {
7192
+ function visualRasterKey(presentation, dpi, serverUrl) {
7193
+ return createHash4("sha256").update(JSON.stringify({ p: presentation, d: dpi, u: serverUrl ?? null })).digest("hex");
7194
+ }
7195
+ function effectiveVisualServerUrl(props, serviceConfig) {
7196
+ if (serviceConfig?.render) return void 0;
7197
+ return props.serverUrl;
7198
+ }
7199
+ async function rasterizeVisualSlide(presentation, dpi, propsServerUrl, serviceConfig, baseDir) {
7192
7200
  if (!isNodeEnvironment()) {
7193
7201
  throw new Error(
7194
7202
  "Visual rasterization requires a Node.js environment. It is not available in browser environments."
@@ -7234,13 +7242,26 @@ async function renderVisualComponent(component, theme, themeName, context) {
7234
7242
  const dpi = clampVisualDpi(
7235
7243
  props.dpi ?? serviceConfig?.dpi ?? DEFAULT_VISUAL_DPI
7236
7244
  );
7237
- const result = await rasterize(
7238
- presentation,
7239
- dpi,
7240
- props.serverUrl,
7241
- serviceConfig,
7242
- getBaseDir()
7245
+ const preRasterized = context?.visualRasterResults?.get(
7246
+ visualRasterKey(
7247
+ presentation,
7248
+ dpi,
7249
+ effectiveVisualServerUrl(props, serviceConfig)
7250
+ )
7243
7251
  );
7252
+ let result;
7253
+ if (preRasterized) {
7254
+ if (!preRasterized.ok) throw new Error(preRasterized.error);
7255
+ result = preRasterized;
7256
+ } else {
7257
+ result = await rasterizeVisualSlide(
7258
+ presentation,
7259
+ dpi,
7260
+ props.serverUrl,
7261
+ serviceConfig,
7262
+ getBaseDir()
7263
+ );
7264
+ }
7244
7265
  return await createImage(
7245
7266
  result.base64DataUri,
7246
7267
  theme,
@@ -7302,6 +7323,203 @@ var textSpaceAfterComponent = createComponent({
7302
7323
 
7303
7324
  // src/core/render.ts
7304
7325
  init_docxImagePositioning();
7326
+
7327
+ // src/core/prerasterizeVisuals.ts
7328
+ import {
7329
+ clampVisualDpi as clampVisualDpi2,
7330
+ DEFAULT_VISUAL_DPI as DEFAULT_VISUAL_DPI2,
7331
+ MAX_RASTERIZE_BATCH_SLIDES
7332
+ } from "@json-to-office/shared";
7333
+
7334
+ // src/utils/promiseLimiter.ts
7335
+ function createLimiter(max) {
7336
+ let active = 0;
7337
+ const queue = [];
7338
+ const release = () => {
7339
+ active--;
7340
+ queue.shift()?.();
7341
+ };
7342
+ return async function limit(fn) {
7343
+ while (active >= max)
7344
+ await new Promise((resolve2) => queue.push(resolve2));
7345
+ active++;
7346
+ try {
7347
+ return await fn();
7348
+ } finally {
7349
+ release();
7350
+ }
7351
+ };
7352
+ }
7353
+
7354
+ // src/core/prerasterizeVisuals.ts
7355
+ var DEFAULT_FALLBACK_CONCURRENCY = 4;
7356
+ var BATCH_TIMEOUT_BASE_MS = 3e4;
7357
+ var BATCH_TIMEOUT_PER_SLIDE_MS = 1e4;
7358
+ function collectVisualProps(root) {
7359
+ const found = [];
7360
+ const seen = /* @__PURE__ */ new WeakSet();
7361
+ const visit = (node) => {
7362
+ if (!node || typeof node !== "object") return;
7363
+ if (seen.has(node)) return;
7364
+ seen.add(node);
7365
+ if (Array.isArray(node)) {
7366
+ for (const item of node) visit(item);
7367
+ return;
7368
+ }
7369
+ const obj = node;
7370
+ if (typeof obj.name === "string" && obj.enabled === false) return;
7371
+ if (obj.name === "visual" && obj.props && typeof obj.props === "object") {
7372
+ found.push(obj.props);
7373
+ return;
7374
+ }
7375
+ for (const value of Object.values(obj)) visit(value);
7376
+ };
7377
+ visit(root);
7378
+ return found;
7379
+ }
7380
+ function toErrorMessage(error) {
7381
+ return error instanceof Error ? error.message : String(error);
7382
+ }
7383
+ function* chunksOf(items, size) {
7384
+ for (let i = 0; i < items.length; i += size) yield items.slice(i, i + size);
7385
+ }
7386
+ function applyBatchResponse(chunk, response, map) {
7387
+ const results = response?.results;
7388
+ if (!Array.isArray(results) || results.length !== chunk.length) return false;
7389
+ const entries = [];
7390
+ for (let i = 0; i < chunk.length; i++) {
7391
+ const item = results[i];
7392
+ if (item && item.ok === true && typeof item.base64DataUri === "string" && item.base64DataUri.length > 0 && typeof item.width === "number" && typeof item.height === "number") {
7393
+ entries.push([
7394
+ chunk[i].key,
7395
+ {
7396
+ ok: true,
7397
+ base64DataUri: item.base64DataUri,
7398
+ width: item.width,
7399
+ height: item.height
7400
+ }
7401
+ ]);
7402
+ } else if (item && item.ok === false && typeof item.error === "string") {
7403
+ entries.push([chunk[i].key, { ok: false, error: item.error }]);
7404
+ } else {
7405
+ return false;
7406
+ }
7407
+ }
7408
+ for (const [key, value] of entries) map.set(key, value);
7409
+ return true;
7410
+ }
7411
+ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
7412
+ const map = /* @__PURE__ */ new Map();
7413
+ if (!isNodeEnvironment() && !serviceConfig?.renderBatch && !serviceConfig?.render) {
7414
+ return map;
7415
+ }
7416
+ const visuals = collectVisualProps(root);
7417
+ if (visuals.length === 0) return map;
7418
+ const targets = /* @__PURE__ */ new Map();
7419
+ for (const props of visuals) {
7420
+ try {
7421
+ const serverUrl2 = effectiveVisualServerUrl(props, serviceConfig);
7422
+ if (serverUrl2 !== void 0) continue;
7423
+ const presentation = buildVisualPresentation(props);
7424
+ const dpi = clampVisualDpi2(
7425
+ props.dpi ?? serviceConfig?.dpi ?? DEFAULT_VISUAL_DPI2
7426
+ );
7427
+ const key = visualRasterKey(presentation, dpi, serverUrl2);
7428
+ if (!targets.has(key)) targets.set(key, { key, presentation, dpi });
7429
+ } catch {
7430
+ continue;
7431
+ }
7432
+ }
7433
+ if (targets.size === 0) return map;
7434
+ const unique = [...targets.values()];
7435
+ const limit = createLimiter(
7436
+ Math.max(1, options.concurrency ?? DEFAULT_FALLBACK_CONCURRENCY)
7437
+ );
7438
+ const rasterizeIndividually = async (chunk) => {
7439
+ await Promise.all(
7440
+ chunk.map(
7441
+ (target) => limit(async () => {
7442
+ try {
7443
+ const result = await rasterizeVisualSlide(
7444
+ target.presentation,
7445
+ target.dpi,
7446
+ void 0,
7447
+ serviceConfig,
7448
+ options.baseDir
7449
+ );
7450
+ map.set(target.key, { ok: true, ...result });
7451
+ } catch (error) {
7452
+ map.set(target.key, { ok: false, error: toErrorMessage(error) });
7453
+ }
7454
+ })
7455
+ )
7456
+ );
7457
+ };
7458
+ if (serviceConfig?.renderBatch) {
7459
+ for (const chunk of chunksOf(unique, MAX_RASTERIZE_BATCH_SLIDES)) {
7460
+ try {
7461
+ const response = await serviceConfig.renderBatch({
7462
+ slides: chunk.map((target) => ({
7463
+ presentation: target.presentation,
7464
+ dpi: target.dpi
7465
+ })),
7466
+ ...options.baseDir !== void 0 && { baseDir: options.baseDir }
7467
+ });
7468
+ if (!applyBatchResponse(chunk, response, map)) {
7469
+ throw new Error(
7470
+ "batch rasterizer returned a malformed result (expected index-aligned results[])"
7471
+ );
7472
+ }
7473
+ } catch (error) {
7474
+ if (serviceConfig.render || serviceConfig.serverUrl) {
7475
+ await rasterizeIndividually(chunk);
7476
+ } else {
7477
+ const message = toErrorMessage(error);
7478
+ for (const target of chunk) {
7479
+ map.set(target.key, { ok: false, error: message });
7480
+ }
7481
+ }
7482
+ }
7483
+ }
7484
+ return map;
7485
+ }
7486
+ if (serviceConfig?.render) {
7487
+ await rasterizeIndividually(unique);
7488
+ return map;
7489
+ }
7490
+ const serverUrl = resolveServiceUrl(
7491
+ void 0,
7492
+ serviceConfig?.serverUrl,
7493
+ DEFAULT_RASTERIZE_SERVER_URL
7494
+ );
7495
+ for (const chunk of chunksOf(unique, MAX_RASTERIZE_BATCH_SLIDES)) {
7496
+ let applied = false;
7497
+ try {
7498
+ const response = await postJsonToService({
7499
+ url: serverUrl,
7500
+ path: "/rasterize/batch",
7501
+ body: {
7502
+ slides: chunk.map((target) => ({
7503
+ presentation: target.presentation,
7504
+ dpi: target.dpi
7505
+ })),
7506
+ ...options.baseDir !== void 0 && { baseDir: options.baseDir }
7507
+ },
7508
+ headers: serviceConfig?.headers,
7509
+ timeoutMs: BATCH_TIMEOUT_BASE_MS + BATCH_TIMEOUT_PER_SLIDE_MS * chunk.length,
7510
+ serviceLabel: "PPTX batch rasterization service",
7511
+ onUnreachable: (url, cause) => `PPTX rasterization service is not reachable at ${url}. Cause: ${cause}`
7512
+ });
7513
+ applied = applyBatchResponse(chunk, await response.json(), map);
7514
+ } catch {
7515
+ applied = false;
7516
+ }
7517
+ if (!applied) await rasterizeIndividually(chunk);
7518
+ }
7519
+ return map;
7520
+ }
7521
+
7522
+ // src/core/render.ts
7305
7523
  function getAlignment3(alignment) {
7306
7524
  switch (alignment) {
7307
7525
  case "center":
@@ -7358,6 +7576,21 @@ async function renderDocumentScoped(structure, layout, options) {
7358
7576
  structure.themeName
7359
7577
  );
7360
7578
  context.services = options?.services;
7579
+ try {
7580
+ const visualRasterResults = await prerasterizeVisuals(
7581
+ layout.sections,
7582
+ options?.services?.pptx,
7583
+ { baseDir: getBaseDir() }
7584
+ );
7585
+ if (visualRasterResults.size > 0) {
7586
+ context.visualRasterResults = visualRasterResults;
7587
+ }
7588
+ } catch (error) {
7589
+ console.warn(
7590
+ "[core-docx] Visual pre-rasterization failed; falling back to per-visual rasterization:",
7591
+ error instanceof Error ? error.message : error
7592
+ );
7593
+ }
7361
7594
  let sectionBookmarkCounter = 0;
7362
7595
  let previousHeader = void 0;
7363
7596
  let previousFooter = void 0;
@@ -8051,70 +8284,104 @@ function createBuilderImpl(state) {
8051
8284
  }
8052
8285
  return new Set(list);
8053
8286
  }
8054
- async function generate(document, options) {
8055
- try {
8056
- const preserveSet = resolvePreserveSet(options);
8057
- const rootIn = document;
8058
- const internalDocument = rootIn.props === void 0 ? { ...rootIn, props: {} } : rootIn;
8059
- const vOpts = {
8060
- ...state.validation,
8061
- ...options?.validation
8062
- };
8063
- if (vOpts.enabled !== false) {
8064
- const result = validateDocument(
8065
- internalDocument,
8066
- state.components,
8067
- { allowUnknownFields: vOpts.allowUnknownFields }
8287
+ async function expandDocument(document, options) {
8288
+ const preserveSet = resolvePreserveSet(options);
8289
+ const rootIn = document;
8290
+ const internalDocument = rootIn.props === void 0 ? { ...rootIn, props: {} } : rootIn;
8291
+ const vOpts = {
8292
+ ...state.validation,
8293
+ ...options?.validation
8294
+ };
8295
+ if (vOpts.enabled !== false) {
8296
+ const result = validateDocument(
8297
+ internalDocument,
8298
+ state.components,
8299
+ { allowUnknownFields: vOpts.allowUnknownFields }
8300
+ );
8301
+ if (!result.valid) {
8302
+ throw new ComponentValidationError2(
8303
+ (result.errors ?? []).map((e) => ({
8304
+ path: e.path ?? "",
8305
+ message: e.message
8306
+ })),
8307
+ internalDocument
8068
8308
  );
8069
- if (!result.valid) {
8070
- throw new ComponentValidationError2(
8071
- (result.errors ?? []).map((e) => ({
8072
- path: e.path ?? "",
8073
- message: e.message
8074
- })),
8075
- internalDocument
8076
- );
8077
- }
8078
8309
  }
8079
- const validateEmitted = vOpts.enabled === false ? void 0 : (emitted, componentLabel) => {
8080
- const result = validateDocument(
8081
- { ...internalDocument, children: emitted },
8082
- state.components,
8083
- { allowUnknownFields: vOpts.allowUnknownFields }
8310
+ }
8311
+ const validateEmitted = vOpts.enabled === false ? void 0 : (emitted, componentLabel) => {
8312
+ const result = validateDocument(
8313
+ { ...internalDocument, children: emitted },
8314
+ state.components,
8315
+ { allowUnknownFields: vOpts.allowUnknownFields }
8316
+ );
8317
+ if (!result.valid) {
8318
+ throw new ComponentValidationError2(
8319
+ (result.errors ?? []).map((e) => ({
8320
+ path: e.path ?? "",
8321
+ message: `custom component '${componentLabel}' emitted invalid output \u2014 ${e.message}`
8322
+ })),
8323
+ emitted
8084
8324
  );
8085
- if (!result.valid) {
8086
- throw new ComponentValidationError2(
8087
- (result.errors ?? []).map((e) => ({
8088
- path: e.path ?? "",
8089
- message: `custom component '${componentLabel}' emitted invalid output \u2014 ${e.message}`
8090
- })),
8091
- emitted
8092
- );
8093
- }
8325
+ }
8326
+ };
8327
+ const warnings = [];
8328
+ const {
8329
+ document: modedRoot,
8330
+ theme: modedTheme,
8331
+ themeName
8332
+ } = resolveThemeContext(internalDocument, {
8333
+ customThemes: state.customThemes,
8334
+ fonts: state.fonts,
8335
+ warnings,
8336
+ resolveNamedTheme: resolveDocumentTheme
8337
+ });
8338
+ const processed = await processDocumentComponents(
8339
+ modedRoot.children || [],
8340
+ preserveSet,
8341
+ warnings,
8342
+ modedTheme,
8343
+ validateEmitted
8344
+ );
8345
+ const processedDocument = {
8346
+ ...modedRoot,
8347
+ children: processed.standard
8348
+ };
8349
+ const [modedDoc] = normalizeDocument(processedDocument);
8350
+ return {
8351
+ modedRoot,
8352
+ modedTheme,
8353
+ themeName,
8354
+ processed,
8355
+ modedDoc,
8356
+ warnings,
8357
+ preserveSet
8358
+ };
8359
+ }
8360
+ async function expandStandardDefinition(document, options) {
8361
+ try {
8362
+ const { modedDoc, warnings } = await expandDocument(document, options);
8363
+ return {
8364
+ standardDefinition: modedDoc,
8365
+ warnings: warnings.length > 0 ? warnings : null
8094
8366
  };
8095
- const warnings = [];
8367
+ } catch (error) {
8368
+ if (state.debug) {
8369
+ console.error("Document expansion error:", error);
8370
+ }
8371
+ throw error;
8372
+ }
8373
+ }
8374
+ async function generate(document, options) {
8375
+ try {
8096
8376
  const {
8097
- document: modedRoot,
8098
- theme: modedTheme,
8099
- themeName
8100
- } = resolveThemeContext(internalDocument, {
8101
- customThemes: state.customThemes,
8102
- fonts: state.fonts,
8103
- warnings,
8104
- resolveNamedTheme: resolveDocumentTheme
8105
- });
8106
- const processed = await processDocumentComponents(
8107
- modedRoot.children || [],
8108
- preserveSet,
8109
- warnings,
8377
+ modedRoot,
8110
8378
  modedTheme,
8111
- validateEmitted
8112
- );
8113
- const processedDocument = {
8114
- ...modedRoot,
8115
- children: processed.standard
8116
- };
8117
- const [modedDoc] = normalizeDocument(processedDocument);
8379
+ themeName,
8380
+ processed,
8381
+ modedDoc,
8382
+ warnings,
8383
+ preserveSet
8384
+ } = await expandDocument(document, options);
8118
8385
  await resolveDocumentFonts(modedDoc, modedTheme, state.fonts, warnings);
8119
8386
  const packageOptions = {
8120
8387
  deterministic: options?.deterministic ?? state.deterministic,
@@ -8241,6 +8508,7 @@ function createBuilderImpl(state) {
8241
8508
  generate,
8242
8509
  generateBuffer,
8243
8510
  generateFile,
8511
+ expandStandardDefinition,
8244
8512
  getComponentNames,
8245
8513
  validate,
8246
8514
  generateSchema,