@json-to-office/core-docx 0.14.0 → 0.16.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.
@@ -850,12 +850,20 @@ function getPageDimensions(size) {
850
850
  }
851
851
  return size;
852
852
  }
853
- var PAGE_SIZES, getTableStyle, getDocumentMargins, getPageSetup;
853
+ var resolveTableParagraphSpacing, PAGE_SIZES, getTableStyle, getDocumentMargins, getPageSetup;
854
854
  var init_layoutUtils = __esm({
855
855
  "src/styles/utils/layoutUtils.ts"() {
856
856
  "use strict";
857
857
  init_colorUtils();
858
858
  init_styleHelpers();
859
+ resolveTableParagraphSpacing = (style) => {
860
+ const lineSpacing = convertLineSpacing(style?.lineSpacing) ?? convertLineSpacing({ type: "single" });
861
+ return {
862
+ before: style?.spacing?.before !== void 0 ? pointsToTwips(style.spacing.before) : 0,
863
+ after: style?.spacing?.after !== void 0 ? pointsToTwips(style.spacing.after) : 0,
864
+ ...lineSpacing
865
+ };
866
+ };
859
867
  PAGE_SIZES = {
860
868
  A4: { width: 11906, height: 16838 },
861
869
  A3: { width: 16838, height: 23811 },
@@ -866,7 +874,10 @@ var init_layoutUtils = __esm({
866
874
  const themeConfig = resolveTheme(theme, themeName);
867
875
  if (themeConfig && themeConfig.styles?.normal) {
868
876
  const normalStyle = themeConfig.styles.normal;
877
+ const styles = themeConfig.styles;
869
878
  return {
879
+ cellParagraph: resolveTableParagraphSpacing(styles.tableCell),
880
+ headerParagraph: resolveTableParagraphSpacing(styles.tableHeader),
870
881
  tableHeader: {
871
882
  fill: resolveColor(
872
883
  themeConfig.colors?.primary || "#000000",
@@ -932,6 +943,8 @@ var init_layoutUtils = __esm({
932
943
  };
933
944
  }
934
945
  return {
946
+ cellParagraph: resolveTableParagraphSpacing(),
947
+ headerParagraph: resolveTableParagraphSpacing(),
935
948
  tableHeader: {
936
949
  fill: "000000",
937
950
  color: "FFFFFF",
@@ -2025,7 +2038,8 @@ import {
2025
2038
  isTableComponent,
2026
2039
  isListComponent,
2027
2040
  isTocComponent,
2028
- isHighchartsComponent
2041
+ isHighchartsComponent,
2042
+ isVisualComponent
2029
2043
  } from "@json-to-office/shared-docx";
2030
2044
 
2031
2045
  // src/utils/formatters.ts
@@ -2118,6 +2132,9 @@ function resolveListProps(props, theme) {
2118
2132
  function resolveHighchartsProps(props, _theme) {
2119
2133
  return props;
2120
2134
  }
2135
+ function resolveVisualProps(props, _theme) {
2136
+ return props;
2137
+ }
2121
2138
  function getCustomComponentDefaults(theme, componentName) {
2122
2139
  const defaults = getComponentDefaults(theme);
2123
2140
  return defaults?.[componentName] || {};
@@ -2147,7 +2164,8 @@ var RESOLVER_MAP = {
2147
2164
  section: resolveSectionProps,
2148
2165
  columns: resolveColumnsProps,
2149
2166
  list: resolveListProps,
2150
- highcharts: resolveHighchartsProps
2167
+ highcharts: resolveHighchartsProps,
2168
+ visual: resolveVisualProps
2151
2169
  };
2152
2170
  function resolveComponentDefaults(component, theme) {
2153
2171
  if (!component.props) return component;
@@ -4128,7 +4146,7 @@ function initializeComponentCache(cache) {
4128
4146
  }
4129
4147
  }
4130
4148
  async function renderComponentWithCache(component, theme, themeName, context, bypassCache = false) {
4131
- const forceBypassForType = component.name === "toc" || component.name === "section" || componentHasRevision(component);
4149
+ const forceBypassForType = component.name === "toc" || component.name === "section" || component.name === "visual" || componentHasRevision(component);
4132
4150
  if (!componentCache) {
4133
4151
  initializeComponentCache();
4134
4152
  }
@@ -5169,6 +5187,7 @@ async function createTable(columns, tableConfig, theme, themeName, _options = {}
5169
5187
  children: [
5170
5188
  new Paragraph({
5171
5189
  ...tableConfig.keepInOnePage && { keepNext: true },
5190
+ spacing: tableStyle.headerParagraph,
5172
5191
  alignment: getAlignment(horizontalAlignment),
5173
5192
  children: cellChildren
5174
5193
  })
@@ -5263,6 +5282,7 @@ async function createTable(columns, tableConfig, theme, themeName, _options = {}
5263
5282
  children: [
5264
5283
  new Paragraph({
5265
5284
  ...tableConfig.keepInOnePage && !isLastRow || isLastRow && tableConfig.keepNext ? { keepNext: true } : {},
5285
+ spacing: tableStyle.cellParagraph,
5266
5286
  alignment: AlignmentType2.LEFT,
5267
5287
  children: []
5268
5288
  })
@@ -5338,6 +5358,7 @@ async function createTable(columns, tableConfig, theme, themeName, _options = {}
5338
5358
  children: [
5339
5359
  new Paragraph({
5340
5360
  ...tableConfig.keepInOnePage && !isLastRow || isLastRow && tableConfig.keepNext ? { keepNext: true } : {},
5361
+ spacing: tableStyle.cellParagraph,
5341
5362
  alignment: getAlignment(horizontalAlignment),
5342
5363
  children: cellChildren
5343
5364
  })
@@ -6377,21 +6398,61 @@ function isNodeEnvironment() {
6377
6398
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null && typeof process.versions.node === "string";
6378
6399
  }
6379
6400
 
6401
+ // src/utils/serviceClient.ts
6402
+ var DEFAULT_TIMEOUT_MS = 3e4;
6403
+ function resolveServiceUrl(propsUrl, servicesUrl, defaultUrl) {
6404
+ const raw = (propsUrl || servicesUrl || defaultUrl).trim();
6405
+ const withScheme = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
6406
+ return withScheme.replace(/\/+$/, "");
6407
+ }
6408
+ async function postJsonToService(opts) {
6409
+ const resolvedHeaders = typeof opts.headers === "function" ? await opts.headers(opts.body) : opts.headers;
6410
+ const headers = {
6411
+ "Content-Type": "application/json",
6412
+ ...resolvedHeaders
6413
+ };
6414
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
6415
+ const controller = new AbortController();
6416
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
6417
+ let response;
6418
+ try {
6419
+ response = await fetch(`${opts.url}${opts.path}`, {
6420
+ method: "POST",
6421
+ headers,
6422
+ body: JSON.stringify(opts.body),
6423
+ signal: controller.signal
6424
+ });
6425
+ } catch (error) {
6426
+ if (error?.name === "AbortError") {
6427
+ throw new Error(
6428
+ `${opts.serviceLabel} timed out after ${timeoutMs}ms at ${opts.url}.`
6429
+ );
6430
+ }
6431
+ const cause = error instanceof Error ? error.message : String(error);
6432
+ throw new Error(opts.onUnreachable(opts.url, cause));
6433
+ } finally {
6434
+ clearTimeout(timer);
6435
+ }
6436
+ if (!response.ok) {
6437
+ throw new Error(
6438
+ `${opts.serviceLabel} returned ${response.status}: ${response.statusText}`
6439
+ );
6440
+ }
6441
+ return response;
6442
+ }
6443
+
6380
6444
  // src/components/highcharts.ts
6381
6445
  var DEFAULT_EXPORT_SERVER_URL = "http://localhost:7801";
6382
- function getExportServerUrl(propsUrl, servicesUrl) {
6383
- const raw = propsUrl || servicesUrl || DEFAULT_EXPORT_SERVER_URL;
6384
- return raw.startsWith("http") ? raw : `http://${raw}`;
6385
- }
6386
6446
  async function generateChart(config, servicesConfig) {
6387
6447
  if (!isNodeEnvironment()) {
6388
6448
  throw new Error(
6389
6449
  "Highcharts export server requires a Node.js environment. Chart generation is not available in browser environments."
6390
6450
  );
6391
6451
  }
6392
- const serverUrl = getExportServerUrl(
6452
+ const serverUrl = resolveServiceUrl(
6393
6453
  config.serverUrl,
6394
- servicesConfig?.serverUrl
6454
+ servicesConfig?.serverUrl,
6455
+ DEFAULT_EXPORT_SERVER_URL
6395
6456
  );
6396
6457
  const requestBody = {
6397
6458
  infile: config.options,
@@ -6399,26 +6460,15 @@ async function generateChart(config, servicesConfig) {
6399
6460
  b64: true,
6400
6461
  scale: config.scale
6401
6462
  };
6402
- const resolvedHeaders = typeof servicesConfig?.headers === "function" ? await servicesConfig.headers(requestBody) : servicesConfig?.headers;
6403
- const headers = {
6404
- "Content-Type": "application/json",
6405
- ...resolvedHeaders
6406
- };
6407
- const response = await fetch(`${serverUrl}/export`, {
6408
- method: "POST",
6409
- headers,
6410
- body: JSON.stringify(requestBody)
6411
- }).catch((error) => {
6412
- throw new Error(
6413
- `Highcharts Export Server is not running at ${serverUrl}. Start it with: npx highcharts-export-server --enableServer true
6414
- Cause: ${error instanceof Error ? error.message : String(error)}`
6415
- );
6463
+ const response = await postJsonToService({
6464
+ url: serverUrl,
6465
+ path: "/export",
6466
+ body: requestBody,
6467
+ headers: servicesConfig?.headers,
6468
+ serviceLabel: "Highcharts export server",
6469
+ onUnreachable: (url, cause) => `Highcharts Export Server is not running at ${url}. Start it with: npx highcharts-export-server --enableServer true
6470
+ Cause: ${cause}`
6416
6471
  });
6417
- if (!response.ok) {
6418
- throw new Error(
6419
- `Highcharts export server returned ${response.status}: ${response.statusText}`
6420
- );
6421
- }
6422
6472
  const base64Data = await response.text();
6423
6473
  const base64DataUri = `data:image/png;base64,${base64Data}`;
6424
6474
  const width = config.options.chart.width;
@@ -6452,6 +6502,109 @@ async function renderHighchartsComponent(component, theme, themeName, context) {
6452
6502
  return imageParagraphs;
6453
6503
  }
6454
6504
 
6505
+ // src/components/visual.ts
6506
+ import {
6507
+ clampVisualDpi,
6508
+ DEFAULT_VISUAL_DPI
6509
+ } from "@json-to-office/shared";
6510
+ var DEFAULT_RASTERIZE_SERVER_URL = "http://localhost:7802";
6511
+ var PIXELS_PER_INCH = 96;
6512
+ function buildVisualPresentation(props) {
6513
+ const { canvas, elements } = props;
6514
+ const presentationProps = {
6515
+ slideWidth: canvas.width,
6516
+ slideHeight: canvas.height
6517
+ };
6518
+ if (canvas.theme) presentationProps.theme = canvas.theme;
6519
+ const slideProps = {};
6520
+ if (canvas.background) slideProps.background = canvas.background;
6521
+ return {
6522
+ name: "pptx",
6523
+ props: presentationProps,
6524
+ children: [
6525
+ {
6526
+ name: "slide",
6527
+ props: slideProps,
6528
+ children: elements ?? []
6529
+ }
6530
+ ]
6531
+ };
6532
+ }
6533
+ function defaultVisualWidthPx(props) {
6534
+ return Math.round(props.canvas.width * PIXELS_PER_INCH);
6535
+ }
6536
+ function visualToImageOptions(props) {
6537
+ return {
6538
+ width: props.width ?? defaultVisualWidthPx(props),
6539
+ ...props.height !== void 0 && { height: props.height },
6540
+ alignment: props.alignment ?? "center",
6541
+ ...props.caption !== void 0 && { caption: props.caption },
6542
+ ...props.spacing !== void 0 && { spacing: props.spacing },
6543
+ ...props.floating !== void 0 && { floating: props.floating },
6544
+ ...props.keepNext !== void 0 && { keepNext: props.keepNext },
6545
+ ...props.keepLines !== void 0 && { keepLines: props.keepLines }
6546
+ };
6547
+ }
6548
+ async function rasterize(presentation, dpi, propsServerUrl, serviceConfig) {
6549
+ if (!isNodeEnvironment()) {
6550
+ throw new Error(
6551
+ "Visual rasterization requires a Node.js environment. It is not available in browser environments."
6552
+ );
6553
+ }
6554
+ if (serviceConfig?.render) {
6555
+ return serviceConfig.render({ presentation, dpi });
6556
+ }
6557
+ const serverUrl = resolveServiceUrl(
6558
+ propsServerUrl,
6559
+ serviceConfig?.serverUrl,
6560
+ DEFAULT_RASTERIZE_SERVER_URL
6561
+ );
6562
+ const response = await postJsonToService({
6563
+ url: serverUrl,
6564
+ path: "/rasterize",
6565
+ body: { presentation, dpi },
6566
+ headers: serviceConfig?.headers,
6567
+ serviceLabel: "PPTX rasterization service",
6568
+ onUnreachable: (url, cause) => `PPTX rasterization service is not reachable at ${url}. Configure services.pptx with a \`render\` callback or a running \`serverUrl\`.
6569
+ Cause: ${cause}`
6570
+ });
6571
+ let result;
6572
+ try {
6573
+ result = await response.json();
6574
+ } catch {
6575
+ throw new Error(
6576
+ "PPTX rasterization service returned a non-JSON response (expected { base64DataUri, width, height })."
6577
+ );
6578
+ }
6579
+ if (!result?.base64DataUri) {
6580
+ throw new Error(
6581
+ "PPTX rasterization service returned a malformed response (missing base64DataUri)."
6582
+ );
6583
+ }
6584
+ return result;
6585
+ }
6586
+ async function renderVisualComponent(component, theme, themeName, context) {
6587
+ if (!isVisualComponent(component)) return [];
6588
+ const props = component.props;
6589
+ const serviceConfig = context?.services?.pptx;
6590
+ const presentation = buildVisualPresentation(props);
6591
+ const dpi = clampVisualDpi(
6592
+ props.dpi ?? serviceConfig?.dpi ?? DEFAULT_VISUAL_DPI
6593
+ );
6594
+ const result = await rasterize(
6595
+ presentation,
6596
+ dpi,
6597
+ props.serverUrl,
6598
+ serviceConfig
6599
+ );
6600
+ return await createImage(
6601
+ result.base64DataUri,
6602
+ theme,
6603
+ themeName,
6604
+ visualToImageOptions(props)
6605
+ );
6606
+ }
6607
+
6455
6608
  // src/components/text-space-after.ts
6456
6609
  import { Type as Type2 } from "@sinclair/typebox";
6457
6610
 
@@ -6608,7 +6761,7 @@ async function renderDocument(structure, layout, options) {
6608
6761
  }
6609
6762
  });
6610
6763
  }
6611
- async function renderHeaderFooterComponents(components, theme, themeName, _context) {
6764
+ async function renderHeaderFooterComponents(components, theme, themeName, context) {
6612
6765
  if (!components || components.length === 0) {
6613
6766
  return [];
6614
6767
  }
@@ -6740,6 +6893,14 @@ async function renderHeaderFooterComponents(components, theme, themeName, _conte
6740
6893
  } else if (isTableComponent(component)) {
6741
6894
  const tables = await renderTableComponent(component, theme, themeName);
6742
6895
  elements.push(...tables);
6896
+ } else if (isVisualComponent(component)) {
6897
+ const visualEls = await renderVisualComponent(
6898
+ component,
6899
+ theme,
6900
+ themeName,
6901
+ context
6902
+ );
6903
+ elements.push(...visualEls);
6743
6904
  }
6744
6905
  }
6745
6906
  return elements;
@@ -6860,6 +7021,8 @@ async function renderComponent(component, theme, themeName, context) {
6860
7021
  themeName,
6861
7022
  context
6862
7023
  );
7024
+ } else if (isVisualComponent(component)) {
7025
+ return await renderVisualComponent(component, theme, themeName, context);
6863
7026
  } else if (isSectionComponent(component)) {
6864
7027
  return await renderSectionComponent(component, theme, themeName, context);
6865
7028
  }