@json-to-office/core-docx 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/visual.d.ts +27 -0
- package/dist/components/visual.d.ts.map +1 -1
- package/dist/core/flattenVisuals.d.ts +10 -2
- package/dist/core/flattenVisuals.d.ts.map +1 -1
- package/dist/core/prerasterizeVisuals.d.ts +44 -0
- package/dist/core/prerasterizeVisuals.d.ts.map +1 -0
- package/dist/core/render.d.ts.map +1 -1
- package/dist/index.js +270 -29
- package/dist/index.js.map +1 -1
- package/dist/plugin/example/index.js +240 -7
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +7 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/promiseLimiter.d.ts +7 -0
- package/dist/utils/promiseLimiter.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -12,6 +12,8 @@ import { ComponentDefinition, RenderContext } from '../types';
|
|
|
12
12
|
import { ThemeConfig } from '../styles';
|
|
13
13
|
import { type ImageOptions } from '../core/content';
|
|
14
14
|
import type { VisualProps } from '@json-to-office/shared-docx';
|
|
15
|
+
import { type PptxServiceConfig, type PptxRasterizeResult } from '@json-to-office/shared';
|
|
16
|
+
export declare const DEFAULT_RASTERIZE_SERVER_URL = "http://localhost:7802";
|
|
15
17
|
/**
|
|
16
18
|
* Build a single-slide pptx presentation component definition from visual props.
|
|
17
19
|
* The shape mirrors a normal `.pptx.json` document so the pptx engine renders
|
|
@@ -35,6 +37,31 @@ export declare function visualToImageOptions(props: VisualProps): ImageOptions;
|
|
|
35
37
|
* `flattenVisuals` transform to produce a portable, service-free document).
|
|
36
38
|
*/
|
|
37
39
|
export declare function visualToImageProps(props: VisualProps, base64DataUri: string): Record<string, unknown>;
|
|
40
|
+
/**
|
|
41
|
+
* Identity of one visual rasterization: content + resolution + (for HTTP
|
|
42
|
+
* services) the target server. Keys the per-document pre-rasterization map
|
|
43
|
+
* (#153); the pre-pass and the render-time lookup MUST use this one function
|
|
44
|
+
* so the two sides cannot drift.
|
|
45
|
+
*/
|
|
46
|
+
export declare function visualRasterKey(presentation: unknown, dpi: number, serverUrl?: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* The serverUrl that actually differentiates a visual's rasterization. An
|
|
49
|
+
* in-process `render` callback takes precedence over any serverUrl —
|
|
50
|
+
* per-visual or config — so overrides only matter when rasterization would
|
|
51
|
+
* go over HTTP. Deliberately keyed on `render` alone (NOT `renderBatch`):
|
|
52
|
+
* per-visual fallbacks always go through `rasterizeVisualSlide`, which only
|
|
53
|
+
* consults `render`, and pre-pass inclusion must match what a fallback would
|
|
54
|
+
* actually do or the same visual could rasterize against two different
|
|
55
|
+
* services depending on cache luck.
|
|
56
|
+
*/
|
|
57
|
+
export declare function effectiveVisualServerUrl(props: VisualProps, serviceConfig: PptxServiceConfig | undefined): string | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Rasterize a single-slide presentation to a PNG via the configured service.
|
|
60
|
+
* An in-process `render` callback takes precedence over an HTTP `serverUrl`.
|
|
61
|
+
* Exported for the pre-rasterization pass, whose per-visual fallback must
|
|
62
|
+
* behave exactly like render-time rasterization.
|
|
63
|
+
*/
|
|
64
|
+
export declare function rasterizeVisualSlide(presentation: Record<string, unknown>, dpi: number, propsServerUrl: string | undefined, serviceConfig: PptxServiceConfig | undefined, baseDir: string | undefined): Promise<PptxRasterizeResult>;
|
|
38
65
|
/**
|
|
39
66
|
* Render visual component
|
|
40
67
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visual.d.ts","sourceRoot":"","sources":["../../src/components/visual.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"visual.d.ts","sourceRoot":"","sources":["../../src/components/visual.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,aAAa,EAEd,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,4BAA4B,0BAA0B,CAAC;AAIpE;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,WAAW,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAWrE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,OAAO,EACrB,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAIR;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,iBAAiB,GAAG,SAAS,GAC3C,MAAM,GAAG,SAAS,CAGpB;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,aAAa,EAAE,iBAAiB,GAAG,SAAS,EAC5C,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,OAAO,CAAC,mBAAmB,CAAC,CA6C9B;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,mBAAmB,EAC9B,KAAK,EAAE,WAAW,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC,CAkDhC"}
|
|
@@ -13,10 +13,16 @@
|
|
|
13
13
|
* just `children`: section `props.header`/`props.footer`, and table
|
|
14
14
|
* `props.columns[].cells[].content` / `props.columns[].header.content`.
|
|
15
15
|
*/
|
|
16
|
-
import { type PptxRasterizer } from '@json-to-office/shared';
|
|
16
|
+
import { type PptxRasterizer, type PptxBatchRasterizer } from '@json-to-office/shared';
|
|
17
17
|
export interface FlattenVisualsOptions {
|
|
18
18
|
/** Rasterizer that turns a single-slide pptx presentation into a PNG. */
|
|
19
19
|
rasterize: PptxRasterizer;
|
|
20
|
+
/**
|
|
21
|
+
* Batch rasterizer (#153). When provided, all visuals are collected and
|
|
22
|
+
* rasterized together up front; `rasterize` remains the per-visual
|
|
23
|
+
* fallback for batch failures and anything the collection missed.
|
|
24
|
+
*/
|
|
25
|
+
rasterizeBatch?: PptxBatchRasterizer;
|
|
20
26
|
/** Default DPI applied when a `visual` does not specify one (default 200). */
|
|
21
27
|
dpi?: number;
|
|
22
28
|
/** Max concurrent rasterizations (default 4). */
|
|
@@ -29,7 +35,9 @@ export interface FlattenVisualsOptions {
|
|
|
29
35
|
}
|
|
30
36
|
/**
|
|
31
37
|
* Return a deep copy of `doc` with every `visual` node replaced by the `image`
|
|
32
|
-
* node it rasterizes to.
|
|
38
|
+
* node it rasterizes to. With `rasterizeBatch`, visuals are pre-rasterized in
|
|
39
|
+
* batches and the tree walk resolves from the batch results; otherwise (and
|
|
40
|
+
* for any batch miss) visuals rasterize per-node with bounded concurrency. A
|
|
33
41
|
* `visual` with `enabled: false` is left untouched (it's filtered out at render
|
|
34
42
|
* anyway, so rasterizing it would be wasted work). Non-visual nodes are copied
|
|
35
43
|
* structurally and otherwise unchanged.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flattenVisuals.d.ts","sourceRoot":"","sources":["../../src/core/flattenVisuals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAGL,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"flattenVisuals.d.ts","sourceRoot":"","sources":["../../src/core/flattenVisuals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,wBAAwB,CAAC;AAYhC,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,SAAS,EAAE,cAAc,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AASD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,CAAC,GAAG,OAAO,EAC9C,GAAG,EAAE,CAAC,EACN,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,CAAC,CAAC,CA0CZ"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prerasterizeVisuals — per-document visual rasterization pre-pass (#153).
|
|
3
|
+
*
|
|
4
|
+
* The renderer walks components strictly in order, so each `visual` used to
|
|
5
|
+
* await its own rasterization round trip: one HTTP call + one LibreOffice
|
|
6
|
+
* launch per visual, ~25 of each for the bundled annual-report templates.
|
|
7
|
+
* This pass runs once per document before rendering: it collects every
|
|
8
|
+
* enabled visual, dedupes identical ones, and rasterizes them together —
|
|
9
|
+
* through an in-process batch rasterizer, a batched HTTP call, or (failing
|
|
10
|
+
* both) bounded-concurrency per-visual calls.
|
|
11
|
+
*
|
|
12
|
+
* Results land in a map keyed by `visualRasterKey`; `renderVisualComponent`
|
|
13
|
+
* consults the map first and falls back to per-visual rasterization on any
|
|
14
|
+
* miss. The pre-pass is therefore purely an accelerator: over-collection
|
|
15
|
+
* wastes a little work, under-collection and every failure mode degrade to
|
|
16
|
+
* the sequential status quo, never to a broken render.
|
|
17
|
+
*/
|
|
18
|
+
import { type PptxServiceConfig, type PptxRasterizeBatchSlideResult } from '@json-to-office/shared';
|
|
19
|
+
import type { VisualProps } from '@json-to-office/shared-docx';
|
|
20
|
+
export interface PrerasterizeOptions {
|
|
21
|
+
/** Directory relative asset paths resolve against (#142). */
|
|
22
|
+
baseDir?: string;
|
|
23
|
+
/** Max concurrent per-visual fallback rasterizations (default 4). */
|
|
24
|
+
concurrency?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Collect the props of every enabled `visual` component reachable from
|
|
28
|
+
* `root`. The walk is generic — every array element and object value is
|
|
29
|
+
* visited — so visuals nested anywhere (columns, table cells, section
|
|
30
|
+
* headers/footers, plugin containers) are found without enumerating
|
|
31
|
+
* container shapes. Disabled component subtrees are pruned: they never
|
|
32
|
+
* render, so rasterizing them is wasted work. Over-collection is harmless
|
|
33
|
+
* (an unused map entry); under-collection is harmless (render-time
|
|
34
|
+
* fallback) — which is what makes the generic walk safe.
|
|
35
|
+
*/
|
|
36
|
+
export declare function collectVisualProps(root: unknown): VisualProps[];
|
|
37
|
+
/**
|
|
38
|
+
* Batch-rasterize a document's visuals ahead of rendering. Returns a map of
|
|
39
|
+
* `visualRasterKey` → per-visual result (pixels or a recorded error). Never
|
|
40
|
+
* throws for per-visual problems; a thrown error here indicates a pre-pass
|
|
41
|
+
* bug and the caller should continue without the map.
|
|
42
|
+
*/
|
|
43
|
+
export declare function prerasterizeVisuals(root: unknown, serviceConfig: PptxServiceConfig | undefined, options?: PrerasterizeOptions): Promise<Map<string, PptxRasterizeBatchSlideResult>>;
|
|
44
|
+
//# sourceMappingURL=prerasterizeVisuals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prerasterizeVisuals.d.ts","sourceRoot":"","sources":["../../src/core/prerasterizeVisuals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAIL,KAAK,iBAAiB,EAEtB,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAkB/D,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AASD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,EAAE,CA4B/D;AAsDD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,OAAO,EACb,aAAa,EAAE,iBAAiB,GAAG,SAAS,EAC5C,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,6BAA6B,CAAC,CAAC,CAmJrD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,QAAQ,EACR,SAAS,EACT,KAAK,EACL,eAAe,EAEf,eAAe,EAKf,OAAO,EACR,MAAM,MAAM,CAAC;AAcd,OAAO,EACL,mBAAmB,EACnB,aAAa,EAed,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAIxC,OAAO,EAEL,iBAAiB,EAElB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKrD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,QAAQ,EACR,SAAS,EACT,KAAK,EACL,eAAe,EAEf,eAAe,EAKf,OAAO,EACR,MAAM,MAAM,CAAC;AAcd,OAAO,EACL,mBAAmB,EACnB,aAAa,EAed,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAIxC,OAAO,EAEL,iBAAiB,EAElB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKrD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AA+B7D,UAAU,qBAAqB;IAC7B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8DD;;GAEG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,iBAAiB,EAC5B,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CAYnB;AAkZD;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,MAAM,EACvB,aAAa,CAAC,EAAE,OAAO,EACvB,WAAW,UAAQ,GAClB,OAAO,CAAC,eAAe,CAAC,CAiH1B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,mBAAmB,EAC9B,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,CAAC,SAAS,GAAG,KAAK,GAAG,eAAe,GAAG,OAAO,CAAC,EAAE,CAAC,CAmC5D"}
|
package/dist/index.js
CHANGED
|
@@ -7013,6 +7013,7 @@ async function renderHighchartsComponent(component, theme, themeName, context) {
|
|
|
7013
7013
|
}
|
|
7014
7014
|
|
|
7015
7015
|
// src/components/visual.ts
|
|
7016
|
+
import { createHash as createHash4 } from "crypto";
|
|
7016
7017
|
import {
|
|
7017
7018
|
clampVisualDpi,
|
|
7018
7019
|
DEFAULT_VISUAL_DPI
|
|
@@ -7062,7 +7063,14 @@ function visualToImageProps(props, base64DataUri) {
|
|
|
7062
7063
|
...props.alt !== void 0 && { alt: props.alt }
|
|
7063
7064
|
};
|
|
7064
7065
|
}
|
|
7065
|
-
|
|
7066
|
+
function visualRasterKey(presentation, dpi, serverUrl) {
|
|
7067
|
+
return createHash4("sha256").update(JSON.stringify({ p: presentation, d: dpi, u: serverUrl ?? null })).digest("hex");
|
|
7068
|
+
}
|
|
7069
|
+
function effectiveVisualServerUrl(props, serviceConfig) {
|
|
7070
|
+
if (serviceConfig?.render) return void 0;
|
|
7071
|
+
return props.serverUrl;
|
|
7072
|
+
}
|
|
7073
|
+
async function rasterizeVisualSlide(presentation, dpi, propsServerUrl, serviceConfig, baseDir) {
|
|
7066
7074
|
if (!isNodeEnvironment()) {
|
|
7067
7075
|
throw new Error(
|
|
7068
7076
|
"Visual rasterization requires a Node.js environment. It is not available in browser environments."
|
|
@@ -7108,13 +7116,26 @@ async function renderVisualComponent(component, theme, themeName, context) {
|
|
|
7108
7116
|
const dpi = clampVisualDpi(
|
|
7109
7117
|
props.dpi ?? serviceConfig?.dpi ?? DEFAULT_VISUAL_DPI
|
|
7110
7118
|
);
|
|
7111
|
-
const
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7119
|
+
const preRasterized = context?.visualRasterResults?.get(
|
|
7120
|
+
visualRasterKey(
|
|
7121
|
+
presentation,
|
|
7122
|
+
dpi,
|
|
7123
|
+
effectiveVisualServerUrl(props, serviceConfig)
|
|
7124
|
+
)
|
|
7117
7125
|
);
|
|
7126
|
+
let result;
|
|
7127
|
+
if (preRasterized) {
|
|
7128
|
+
if (!preRasterized.ok) throw new Error(preRasterized.error);
|
|
7129
|
+
result = preRasterized;
|
|
7130
|
+
} else {
|
|
7131
|
+
result = await rasterizeVisualSlide(
|
|
7132
|
+
presentation,
|
|
7133
|
+
dpi,
|
|
7134
|
+
props.serverUrl,
|
|
7135
|
+
serviceConfig,
|
|
7136
|
+
getBaseDir()
|
|
7137
|
+
);
|
|
7138
|
+
}
|
|
7118
7139
|
return await createImage(
|
|
7119
7140
|
result.base64DataUri,
|
|
7120
7141
|
theme,
|
|
@@ -7176,6 +7197,203 @@ var textSpaceAfterComponent = createComponent({
|
|
|
7176
7197
|
|
|
7177
7198
|
// src/core/render.ts
|
|
7178
7199
|
init_docxImagePositioning();
|
|
7200
|
+
|
|
7201
|
+
// src/core/prerasterizeVisuals.ts
|
|
7202
|
+
import {
|
|
7203
|
+
clampVisualDpi as clampVisualDpi2,
|
|
7204
|
+
DEFAULT_VISUAL_DPI as DEFAULT_VISUAL_DPI2,
|
|
7205
|
+
MAX_RASTERIZE_BATCH_SLIDES
|
|
7206
|
+
} from "@json-to-office/shared";
|
|
7207
|
+
|
|
7208
|
+
// src/utils/promiseLimiter.ts
|
|
7209
|
+
function createLimiter(max) {
|
|
7210
|
+
let active = 0;
|
|
7211
|
+
const queue = [];
|
|
7212
|
+
const release = () => {
|
|
7213
|
+
active--;
|
|
7214
|
+
queue.shift()?.();
|
|
7215
|
+
};
|
|
7216
|
+
return async function limit(fn) {
|
|
7217
|
+
while (active >= max)
|
|
7218
|
+
await new Promise((resolve4) => queue.push(resolve4));
|
|
7219
|
+
active++;
|
|
7220
|
+
try {
|
|
7221
|
+
return await fn();
|
|
7222
|
+
} finally {
|
|
7223
|
+
release();
|
|
7224
|
+
}
|
|
7225
|
+
};
|
|
7226
|
+
}
|
|
7227
|
+
|
|
7228
|
+
// src/core/prerasterizeVisuals.ts
|
|
7229
|
+
var DEFAULT_FALLBACK_CONCURRENCY = 4;
|
|
7230
|
+
var BATCH_TIMEOUT_BASE_MS = 3e4;
|
|
7231
|
+
var BATCH_TIMEOUT_PER_SLIDE_MS = 1e4;
|
|
7232
|
+
function collectVisualProps(root) {
|
|
7233
|
+
const found = [];
|
|
7234
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
7235
|
+
const visit = (node) => {
|
|
7236
|
+
if (!node || typeof node !== "object") return;
|
|
7237
|
+
if (seen.has(node)) return;
|
|
7238
|
+
seen.add(node);
|
|
7239
|
+
if (Array.isArray(node)) {
|
|
7240
|
+
for (const item of node) visit(item);
|
|
7241
|
+
return;
|
|
7242
|
+
}
|
|
7243
|
+
const obj = node;
|
|
7244
|
+
if (typeof obj.name === "string" && obj.enabled === false) return;
|
|
7245
|
+
if (obj.name === "visual" && obj.props && typeof obj.props === "object") {
|
|
7246
|
+
found.push(obj.props);
|
|
7247
|
+
return;
|
|
7248
|
+
}
|
|
7249
|
+
for (const value of Object.values(obj)) visit(value);
|
|
7250
|
+
};
|
|
7251
|
+
visit(root);
|
|
7252
|
+
return found;
|
|
7253
|
+
}
|
|
7254
|
+
function toErrorMessage(error) {
|
|
7255
|
+
return error instanceof Error ? error.message : String(error);
|
|
7256
|
+
}
|
|
7257
|
+
function* chunksOf(items, size) {
|
|
7258
|
+
for (let i = 0; i < items.length; i += size) yield items.slice(i, i + size);
|
|
7259
|
+
}
|
|
7260
|
+
function applyBatchResponse(chunk, response, map) {
|
|
7261
|
+
const results = response?.results;
|
|
7262
|
+
if (!Array.isArray(results) || results.length !== chunk.length) return false;
|
|
7263
|
+
const entries = [];
|
|
7264
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
7265
|
+
const item = results[i];
|
|
7266
|
+
if (item && item.ok === true && typeof item.base64DataUri === "string" && item.base64DataUri.length > 0 && typeof item.width === "number" && typeof item.height === "number") {
|
|
7267
|
+
entries.push([
|
|
7268
|
+
chunk[i].key,
|
|
7269
|
+
{
|
|
7270
|
+
ok: true,
|
|
7271
|
+
base64DataUri: item.base64DataUri,
|
|
7272
|
+
width: item.width,
|
|
7273
|
+
height: item.height
|
|
7274
|
+
}
|
|
7275
|
+
]);
|
|
7276
|
+
} else if (item && item.ok === false && typeof item.error === "string") {
|
|
7277
|
+
entries.push([chunk[i].key, { ok: false, error: item.error }]);
|
|
7278
|
+
} else {
|
|
7279
|
+
return false;
|
|
7280
|
+
}
|
|
7281
|
+
}
|
|
7282
|
+
for (const [key, value] of entries) map.set(key, value);
|
|
7283
|
+
return true;
|
|
7284
|
+
}
|
|
7285
|
+
async function prerasterizeVisuals(root, serviceConfig, options = {}) {
|
|
7286
|
+
const map = /* @__PURE__ */ new Map();
|
|
7287
|
+
if (!isNodeEnvironment() && !serviceConfig?.renderBatch && !serviceConfig?.render) {
|
|
7288
|
+
return map;
|
|
7289
|
+
}
|
|
7290
|
+
const visuals = collectVisualProps(root);
|
|
7291
|
+
if (visuals.length === 0) return map;
|
|
7292
|
+
const targets = /* @__PURE__ */ new Map();
|
|
7293
|
+
for (const props of visuals) {
|
|
7294
|
+
try {
|
|
7295
|
+
const serverUrl2 = effectiveVisualServerUrl(props, serviceConfig);
|
|
7296
|
+
if (serverUrl2 !== void 0) continue;
|
|
7297
|
+
const presentation = buildVisualPresentation(props);
|
|
7298
|
+
const dpi = clampVisualDpi2(
|
|
7299
|
+
props.dpi ?? serviceConfig?.dpi ?? DEFAULT_VISUAL_DPI2
|
|
7300
|
+
);
|
|
7301
|
+
const key = visualRasterKey(presentation, dpi, serverUrl2);
|
|
7302
|
+
if (!targets.has(key)) targets.set(key, { key, presentation, dpi });
|
|
7303
|
+
} catch {
|
|
7304
|
+
continue;
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7307
|
+
if (targets.size === 0) return map;
|
|
7308
|
+
const unique = [...targets.values()];
|
|
7309
|
+
const limit = createLimiter(
|
|
7310
|
+
Math.max(1, options.concurrency ?? DEFAULT_FALLBACK_CONCURRENCY)
|
|
7311
|
+
);
|
|
7312
|
+
const rasterizeIndividually = async (chunk) => {
|
|
7313
|
+
await Promise.all(
|
|
7314
|
+
chunk.map(
|
|
7315
|
+
(target) => limit(async () => {
|
|
7316
|
+
try {
|
|
7317
|
+
const result = await rasterizeVisualSlide(
|
|
7318
|
+
target.presentation,
|
|
7319
|
+
target.dpi,
|
|
7320
|
+
void 0,
|
|
7321
|
+
serviceConfig,
|
|
7322
|
+
options.baseDir
|
|
7323
|
+
);
|
|
7324
|
+
map.set(target.key, { ok: true, ...result });
|
|
7325
|
+
} catch (error) {
|
|
7326
|
+
map.set(target.key, { ok: false, error: toErrorMessage(error) });
|
|
7327
|
+
}
|
|
7328
|
+
})
|
|
7329
|
+
)
|
|
7330
|
+
);
|
|
7331
|
+
};
|
|
7332
|
+
if (serviceConfig?.renderBatch) {
|
|
7333
|
+
for (const chunk of chunksOf(unique, MAX_RASTERIZE_BATCH_SLIDES)) {
|
|
7334
|
+
try {
|
|
7335
|
+
const response = await serviceConfig.renderBatch({
|
|
7336
|
+
slides: chunk.map((target) => ({
|
|
7337
|
+
presentation: target.presentation,
|
|
7338
|
+
dpi: target.dpi
|
|
7339
|
+
})),
|
|
7340
|
+
...options.baseDir !== void 0 && { baseDir: options.baseDir }
|
|
7341
|
+
});
|
|
7342
|
+
if (!applyBatchResponse(chunk, response, map)) {
|
|
7343
|
+
throw new Error(
|
|
7344
|
+
"batch rasterizer returned a malformed result (expected index-aligned results[])"
|
|
7345
|
+
);
|
|
7346
|
+
}
|
|
7347
|
+
} catch (error) {
|
|
7348
|
+
if (serviceConfig.render || serviceConfig.serverUrl) {
|
|
7349
|
+
await rasterizeIndividually(chunk);
|
|
7350
|
+
} else {
|
|
7351
|
+
const message = toErrorMessage(error);
|
|
7352
|
+
for (const target of chunk) {
|
|
7353
|
+
map.set(target.key, { ok: false, error: message });
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
return map;
|
|
7359
|
+
}
|
|
7360
|
+
if (serviceConfig?.render) {
|
|
7361
|
+
await rasterizeIndividually(unique);
|
|
7362
|
+
return map;
|
|
7363
|
+
}
|
|
7364
|
+
const serverUrl = resolveServiceUrl(
|
|
7365
|
+
void 0,
|
|
7366
|
+
serviceConfig?.serverUrl,
|
|
7367
|
+
DEFAULT_RASTERIZE_SERVER_URL
|
|
7368
|
+
);
|
|
7369
|
+
for (const chunk of chunksOf(unique, MAX_RASTERIZE_BATCH_SLIDES)) {
|
|
7370
|
+
let applied = false;
|
|
7371
|
+
try {
|
|
7372
|
+
const response = await postJsonToService({
|
|
7373
|
+
url: serverUrl,
|
|
7374
|
+
path: "/rasterize/batch",
|
|
7375
|
+
body: {
|
|
7376
|
+
slides: chunk.map((target) => ({
|
|
7377
|
+
presentation: target.presentation,
|
|
7378
|
+
dpi: target.dpi
|
|
7379
|
+
})),
|
|
7380
|
+
...options.baseDir !== void 0 && { baseDir: options.baseDir }
|
|
7381
|
+
},
|
|
7382
|
+
headers: serviceConfig?.headers,
|
|
7383
|
+
timeoutMs: BATCH_TIMEOUT_BASE_MS + BATCH_TIMEOUT_PER_SLIDE_MS * chunk.length,
|
|
7384
|
+
serviceLabel: "PPTX batch rasterization service",
|
|
7385
|
+
onUnreachable: (url, cause) => `PPTX rasterization service is not reachable at ${url}. Cause: ${cause}`
|
|
7386
|
+
});
|
|
7387
|
+
applied = applyBatchResponse(chunk, await response.json(), map);
|
|
7388
|
+
} catch {
|
|
7389
|
+
applied = false;
|
|
7390
|
+
}
|
|
7391
|
+
if (!applied) await rasterizeIndividually(chunk);
|
|
7392
|
+
}
|
|
7393
|
+
return map;
|
|
7394
|
+
}
|
|
7395
|
+
|
|
7396
|
+
// src/core/render.ts
|
|
7179
7397
|
function getAlignment3(alignment) {
|
|
7180
7398
|
switch (alignment) {
|
|
7181
7399
|
case "center":
|
|
@@ -7232,6 +7450,21 @@ async function renderDocumentScoped(structure, layout, options) {
|
|
|
7232
7450
|
structure.themeName
|
|
7233
7451
|
);
|
|
7234
7452
|
context.services = options?.services;
|
|
7453
|
+
try {
|
|
7454
|
+
const visualRasterResults = await prerasterizeVisuals(
|
|
7455
|
+
layout.sections,
|
|
7456
|
+
options?.services?.pptx,
|
|
7457
|
+
{ baseDir: getBaseDir() }
|
|
7458
|
+
);
|
|
7459
|
+
if (visualRasterResults.size > 0) {
|
|
7460
|
+
context.visualRasterResults = visualRasterResults;
|
|
7461
|
+
}
|
|
7462
|
+
} catch (error) {
|
|
7463
|
+
console.warn(
|
|
7464
|
+
"[core-docx] Visual pre-rasterization failed; falling back to per-visual rasterization:",
|
|
7465
|
+
error instanceof Error ? error.message : error
|
|
7466
|
+
);
|
|
7467
|
+
}
|
|
7235
7468
|
let sectionBookmarkCounter = 0;
|
|
7236
7469
|
let previousHeader = void 0;
|
|
7237
7470
|
let previousFooter = void 0;
|
|
@@ -8015,31 +8248,39 @@ var DocumentGenerator = {
|
|
|
8015
8248
|
|
|
8016
8249
|
// src/core/flattenVisuals.ts
|
|
8017
8250
|
import {
|
|
8018
|
-
clampVisualDpi as
|
|
8019
|
-
DEFAULT_VISUAL_DPI as
|
|
8251
|
+
clampVisualDpi as clampVisualDpi3,
|
|
8252
|
+
DEFAULT_VISUAL_DPI as DEFAULT_VISUAL_DPI3
|
|
8020
8253
|
} from "@json-to-office/shared";
|
|
8021
8254
|
var DEFAULT_CONCURRENCY = 4;
|
|
8022
|
-
function createLimiter(max) {
|
|
8023
|
-
let active = 0;
|
|
8024
|
-
const queue = [];
|
|
8025
|
-
const release = () => {
|
|
8026
|
-
active--;
|
|
8027
|
-
queue.shift()?.();
|
|
8028
|
-
};
|
|
8029
|
-
return async function limit(fn) {
|
|
8030
|
-
if (active >= max)
|
|
8031
|
-
await new Promise((resolve4) => queue.push(resolve4));
|
|
8032
|
-
active++;
|
|
8033
|
-
try {
|
|
8034
|
-
return await fn();
|
|
8035
|
-
} finally {
|
|
8036
|
-
release();
|
|
8037
|
-
}
|
|
8038
|
-
};
|
|
8039
|
-
}
|
|
8040
8255
|
async function flattenVisuals(doc, options) {
|
|
8256
|
+
let rasterize = options.rasterize;
|
|
8257
|
+
if (options.rasterizeBatch) {
|
|
8258
|
+
const preRasterized = await prerasterizeVisuals(
|
|
8259
|
+
doc,
|
|
8260
|
+
{
|
|
8261
|
+
render: options.rasterize,
|
|
8262
|
+
renderBatch: options.rasterizeBatch,
|
|
8263
|
+
dpi: options.dpi
|
|
8264
|
+
},
|
|
8265
|
+
{ baseDir: options.baseDir, concurrency: options.concurrency }
|
|
8266
|
+
).catch(() => /* @__PURE__ */ new Map());
|
|
8267
|
+
rasterize = async (request) => {
|
|
8268
|
+
const hit = preRasterized.get(
|
|
8269
|
+
visualRasterKey(request.presentation, request.dpi)
|
|
8270
|
+
);
|
|
8271
|
+
if (hit) {
|
|
8272
|
+
if (!hit.ok) throw new Error(hit.error);
|
|
8273
|
+
return {
|
|
8274
|
+
base64DataUri: hit.base64DataUri,
|
|
8275
|
+
width: hit.width,
|
|
8276
|
+
height: hit.height
|
|
8277
|
+
};
|
|
8278
|
+
}
|
|
8279
|
+
return options.rasterize(request);
|
|
8280
|
+
};
|
|
8281
|
+
}
|
|
8041
8282
|
const ctx = {
|
|
8042
|
-
rasterize
|
|
8283
|
+
rasterize,
|
|
8043
8284
|
dpi: options.dpi,
|
|
8044
8285
|
baseDir: options.baseDir,
|
|
8045
8286
|
limit: createLimiter(
|
|
@@ -8050,7 +8291,7 @@ async function flattenVisuals(doc, options) {
|
|
|
8050
8291
|
}
|
|
8051
8292
|
async function rasterizeVisual(obj, ctx) {
|
|
8052
8293
|
const props = obj.props;
|
|
8053
|
-
const dpi =
|
|
8294
|
+
const dpi = clampVisualDpi3(props.dpi ?? ctx.dpi ?? DEFAULT_VISUAL_DPI3);
|
|
8054
8295
|
const result = await ctx.limit(
|
|
8055
8296
|
() => ctx.rasterize({
|
|
8056
8297
|
presentation: buildVisualPresentation(props),
|