docx-kit 0.2.0 → 0.4.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/README.md +10 -531
- package/dist/ai.d.ts +1 -0
- package/dist/ai.js +1 -0
- package/dist/browser.d.ts +65 -1812
- package/dist/browser.js +66 -2354
- package/dist/loader-browser.d.ts +1 -0
- package/dist/loader-browser.js +1 -0
- package/dist/loader-node.d.ts +1 -0
- package/dist/loader-node.js +2 -0
- package/dist/loader.d.ts +1 -0
- package/dist/loader.js +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +2 -0
- package/dist/node.d.ts +109 -0
- package/dist/node.js +122 -0
- package/dist/pdk.d.ts +1 -0
- package/dist/pdk.js +1 -0
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +2 -0
- package/package.json +67 -50
- package/dist/compileDocument-DIFdjo67.mjs +0 -1051
- package/dist/compileDocument-Vy7rz9_8.js +0 -1051
- package/dist/errors-c9CC63Ti.js +0 -63
- package/dist/errors-c9CC63Ti.mjs +0 -63
- package/dist/fs-BuKdtuxB.js +0 -30
- package/dist/fs-DF8ug9Wi.mjs +0 -43
- package/dist/image-CdfLh9GO.js +0 -76
- package/dist/image-CdfLh9GO.mjs +0 -76
- package/dist/node.d.mts +0 -1817
- package/dist/node.mjs +0 -2362
- package/dist/pack-Bkdfq0_u.mjs +0 -79
- package/dist/pack-u3tMQYCL.js +0 -79
package/dist/node.mjs
DELETED
|
@@ -1,2362 +0,0 @@
|
|
|
1
|
-
import { n as saveDocument } from "./fs-DF8ug9Wi.mjs";
|
|
2
|
-
import { n as dataUrlToUint8Array$1, t as createImageRun } from "./image-CdfLh9GO.mjs";
|
|
3
|
-
import { n as ERROR_CODES, t as DocxKitError } from "./errors-c9CC63Ti.mjs";
|
|
4
|
-
import { AlignmentType, BorderStyle, HeadingLevel, PageNumber, Paragraph, ShadingType, Table, TableCell, TableRow, TextRun, WidthType } from "docx";
|
|
5
|
-
//#region src/types/style.ts
|
|
6
|
-
/**
|
|
7
|
-
* Define a type-safe stylesheet.
|
|
8
|
-
*
|
|
9
|
-
* `fontSize` defaults to pt when passed as a bare number.
|
|
10
|
-
*
|
|
11
|
-
* @param styles - — The stylesheet object
|
|
12
|
-
* @returns The same stylesheet with `const` type inference
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* const styles = defineStyles({
|
|
17
|
-
* title: { fontSize: 28, fontWeight: 'bold' },
|
|
18
|
-
* body: { fontSize: 12, lineHeight: 1.5 },
|
|
19
|
-
* })
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
function defineStyles(styles) {
|
|
23
|
-
return styles;
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/types/plugin.ts
|
|
27
|
-
/**
|
|
28
|
-
* Define a type-safe plugin with full inference.
|
|
29
|
-
*
|
|
30
|
-
* @param plugin - — The plugin definition object
|
|
31
|
-
* @returns The same plugin with `const` type inference
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* const myPlugin = definePlugin<'badge', { text: string }>({
|
|
36
|
-
* name: 'badge',
|
|
37
|
-
* render: async (opts, ctx) => {
|
|
38
|
-
* // ... render logic
|
|
39
|
-
* return new Paragraph({ text: opts.text })
|
|
40
|
-
* },
|
|
41
|
-
* })
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
function definePlugin(plugin) {
|
|
45
|
-
return plugin;
|
|
46
|
-
}
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region src/plugins/qrcode/index.ts
|
|
49
|
-
/**
|
|
50
|
-
* QR Code plugin — embeds a QR code image in the document.
|
|
51
|
-
*
|
|
52
|
-
* Uses the `qrcode` package (peer dependency) to generate
|
|
53
|
-
* a QR code PNG and renders it as an inline `ImageRun`.
|
|
54
|
-
*
|
|
55
|
-
* @module plugins/qrcode
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* ```ts
|
|
59
|
-
* const doc = createDocx()
|
|
60
|
-
* .use(qrcodePlugin())
|
|
61
|
-
* .h1('Scan Me')
|
|
62
|
-
* .plugin('qrcode', { text: 'https://example.com', caption: 'Visit us' })
|
|
63
|
-
* .save('qrcode.docx')
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
/**
|
|
67
|
-
* Create a QRCode plugin instance.
|
|
68
|
-
*
|
|
69
|
-
* The plugin dynamically imports the `qrcode` package at render time,
|
|
70
|
-
* keeping it an optional peer dependency of docx-kit core.
|
|
71
|
-
*
|
|
72
|
-
* @returns A configured DocxPlugin for `'qrcode'`
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```ts
|
|
76
|
-
* import { createDocx, qrcodePlugin } from 'docx-kit'
|
|
77
|
-
*
|
|
78
|
-
* const doc = createDocx()
|
|
79
|
-
* .use(qrcodePlugin())
|
|
80
|
-
* .plugin('qrcode', {
|
|
81
|
-
* text: 'https://example.com',
|
|
82
|
-
* size: 256,
|
|
83
|
-
* errorCorrectionLevel: 'H',
|
|
84
|
-
* caption: 'Scan to visit',
|
|
85
|
-
* })
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
|
-
function qrcodePlugin() {
|
|
89
|
-
return definePlugin({
|
|
90
|
-
name: "qrcode",
|
|
91
|
-
async render(options) {
|
|
92
|
-
const QRCode = (await import("qrcode")).default ?? await import("qrcode");
|
|
93
|
-
const size = options.size ?? 128;
|
|
94
|
-
const paragraphs = [new Paragraph({ children: [createImageRun({
|
|
95
|
-
data: await dataUrlToUint8Array$1(await QRCode.toDataURL(options.text, {
|
|
96
|
-
errorCorrectionLevel: options.errorCorrectionLevel ?? "M",
|
|
97
|
-
margin: options.margin ?? 1,
|
|
98
|
-
width: size
|
|
99
|
-
})),
|
|
100
|
-
height: size,
|
|
101
|
-
type: "png",
|
|
102
|
-
width: size
|
|
103
|
-
})] })];
|
|
104
|
-
if (options.caption) paragraphs.push(new Paragraph({ text: options.caption }));
|
|
105
|
-
return paragraphs;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
//#endregion
|
|
110
|
-
//#region src/plugins/callout/index.ts
|
|
111
|
-
/**
|
|
112
|
-
* Callout plugin — colored info / warning / success / danger boxes.
|
|
113
|
-
*
|
|
114
|
-
* Renders a single `Paragraph` with a prominent left border, a tinted
|
|
115
|
-
* background, an emoji icon, an optional bold title, and body text.
|
|
116
|
-
*
|
|
117
|
-
* @module plugins/callout
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* ```ts
|
|
121
|
-
* const doc = createDocx()
|
|
122
|
-
* .use(calloutPlugin())
|
|
123
|
-
* .plugin('callout', {
|
|
124
|
-
* type: 'warning',
|
|
125
|
-
* title: '注意',
|
|
126
|
-
* content: '此操作不可撤销,请确认后再提交。',
|
|
127
|
-
* })
|
|
128
|
-
* .save('callout.docx')
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
131
|
-
const CALLOUT_PRESETS = {
|
|
132
|
-
danger: {
|
|
133
|
-
bg: "FCE4D6",
|
|
134
|
-
border: "FF0000",
|
|
135
|
-
icon: "⚠️"
|
|
136
|
-
},
|
|
137
|
-
info: {
|
|
138
|
-
bg: "D6E4F0",
|
|
139
|
-
border: "4472C4",
|
|
140
|
-
icon: "ℹ️"
|
|
141
|
-
},
|
|
142
|
-
success: {
|
|
143
|
-
bg: "E2F0D9",
|
|
144
|
-
border: "70AD47",
|
|
145
|
-
icon: "✅"
|
|
146
|
-
},
|
|
147
|
-
warning: {
|
|
148
|
-
bg: "FFF2CC",
|
|
149
|
-
border: "FFC000",
|
|
150
|
-
icon: "⚠️"
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
/**
|
|
154
|
-
* Create a Callout plugin instance.
|
|
155
|
-
*
|
|
156
|
-
* @returns A configured DocxPlugin for `'callout'`
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* ```ts
|
|
160
|
-
* import { createDocx, calloutPlugin } from 'docx-kit'
|
|
161
|
-
*
|
|
162
|
-
* const doc = createDocx()
|
|
163
|
-
* .use(calloutPlugin())
|
|
164
|
-
* .plugin('callout', { type: 'info', content: '系统将在今晚 22:00 升级。' })
|
|
165
|
-
* ```
|
|
166
|
-
*/
|
|
167
|
-
function calloutPlugin() {
|
|
168
|
-
return definePlugin({
|
|
169
|
-
name: "callout",
|
|
170
|
-
render(options) {
|
|
171
|
-
const preset = CALLOUT_PRESETS[options.type];
|
|
172
|
-
const runs = [new TextRun({ text: `${preset.icon} ` })];
|
|
173
|
-
if (options.title) runs.push(new TextRun({
|
|
174
|
-
bold: true,
|
|
175
|
-
text: `${options.title}\n`
|
|
176
|
-
}));
|
|
177
|
-
runs.push(new TextRun({ text: options.content }));
|
|
178
|
-
return new Paragraph({
|
|
179
|
-
children: runs,
|
|
180
|
-
spacing: {
|
|
181
|
-
after: 120,
|
|
182
|
-
before: 120
|
|
183
|
-
},
|
|
184
|
-
border: { left: {
|
|
185
|
-
color: preset.border,
|
|
186
|
-
size: 12,
|
|
187
|
-
space: 8,
|
|
188
|
-
style: BorderStyle.SINGLE
|
|
189
|
-
} },
|
|
190
|
-
shading: {
|
|
191
|
-
fill: preset.bg,
|
|
192
|
-
type: ShadingType.CLEAR
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region src/plugins/echarts/index.ts
|
|
200
|
-
/**
|
|
201
|
-
* ECharts plugin — embeds interactive ECharts charts as static images.
|
|
202
|
-
*
|
|
203
|
-
* Renders charts in the browser via the DOM. For Node.js rendering,
|
|
204
|
-
* a custom canvas/SVG implementation is needed (not built-in).
|
|
205
|
-
*
|
|
206
|
-
* @module plugins/echarts
|
|
207
|
-
*
|
|
208
|
-
* @example
|
|
209
|
-
* ```ts
|
|
210
|
-
* const doc = createDocx()
|
|
211
|
-
* .use(echartsPlugin())
|
|
212
|
-
* .h1('Sales Chart')
|
|
213
|
-
* .plugin('echarts', {
|
|
214
|
-
* option: {
|
|
215
|
-
* title: { text: 'Monthly Sales' },
|
|
216
|
-
* xAxis: { data: ['Jan', 'Feb', 'Mar'] },
|
|
217
|
-
* yAxis: {},
|
|
218
|
-
* series: [{ type: 'bar', data: [120, 200, 150] }],
|
|
219
|
-
* },
|
|
220
|
-
* width: 640,
|
|
221
|
-
* height: 360,
|
|
222
|
-
* caption: 'Figure 1: Monthly sales data',
|
|
223
|
-
* })
|
|
224
|
-
* .save('chart.docx')
|
|
225
|
-
* ```
|
|
226
|
-
*/
|
|
227
|
-
/**
|
|
228
|
-
* Create an ECharts plugin instance.
|
|
229
|
-
*
|
|
230
|
-
* The plugin renders charts using the browser DOM. In Node.js
|
|
231
|
-
* environments, it throws an error prompting the user to provide
|
|
232
|
-
* a server-side canvas implementation.
|
|
233
|
-
*
|
|
234
|
-
* @returns A configured DocxPlugin for `'echarts'`
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* ```ts
|
|
238
|
-
* import { createDocx, echartsPlugin } from 'docx-kit'
|
|
239
|
-
*
|
|
240
|
-
* const doc = createDocx()
|
|
241
|
-
* .use(echartsPlugin())
|
|
242
|
-
* .plugin('echarts', {
|
|
243
|
-
* option: {
|
|
244
|
-
* title: { text: 'Revenue' },
|
|
245
|
-
* xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] },
|
|
246
|
-
* yAxis: { type: 'value' },
|
|
247
|
-
* series: [{ data: [820, 932, 901, 1347], type: 'line' }],
|
|
248
|
-
* },
|
|
249
|
-
* caption: 'Quarterly revenue trend',
|
|
250
|
-
* })
|
|
251
|
-
* ```
|
|
252
|
-
*/
|
|
253
|
-
function echartsPlugin() {
|
|
254
|
-
return definePlugin({
|
|
255
|
-
name: "echarts",
|
|
256
|
-
async render(options) {
|
|
257
|
-
const width = options.width ?? 640;
|
|
258
|
-
const height = options.height ?? 360;
|
|
259
|
-
const imageType = options.imageType ?? "png";
|
|
260
|
-
const renderer = options.renderer ?? "canvas";
|
|
261
|
-
const image = await renderEChartsToImage(options.option, {
|
|
262
|
-
height,
|
|
263
|
-
imageType,
|
|
264
|
-
renderer,
|
|
265
|
-
width
|
|
266
|
-
});
|
|
267
|
-
const paragraphs = [new Paragraph({ children: [createImageRun({
|
|
268
|
-
data: image.data,
|
|
269
|
-
height,
|
|
270
|
-
type: image.type,
|
|
271
|
-
width
|
|
272
|
-
})] })];
|
|
273
|
-
if (options.caption) paragraphs.push(new Paragraph({ text: options.caption }));
|
|
274
|
-
return paragraphs;
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Render an ECharts option to an image.
|
|
280
|
-
*
|
|
281
|
-
* Automatically selects browser or Node.js rendering based on
|
|
282
|
-
* whether the `window` global is available.
|
|
283
|
-
*
|
|
284
|
-
* @param option - — Full ECharts option object
|
|
285
|
-
* @param config - — Render dimensions and format
|
|
286
|
-
* @returns Rendered image data and type
|
|
287
|
-
*/
|
|
288
|
-
async function renderEChartsToImage(option, config) {
|
|
289
|
-
if (typeof window !== "undefined") return renderInBrowser(option, config);
|
|
290
|
-
return renderInNode(option, config);
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Browser-side ECharts rendering.
|
|
294
|
-
*
|
|
295
|
-
* Creates a hidden DOM container, renders the chart, and extracts
|
|
296
|
-
* the image as a base64 data-URL.
|
|
297
|
-
*/
|
|
298
|
-
async function renderInBrowser(option, config) {
|
|
299
|
-
const echarts = await import("echarts");
|
|
300
|
-
const container = document.createElement("div");
|
|
301
|
-
container.style.cssText = `width:${config.width}px;height:${config.height}px;position:fixed;left:-99999px;`;
|
|
302
|
-
document.body.append(container);
|
|
303
|
-
const chart = echarts.init(container, void 0, {
|
|
304
|
-
height: config.height,
|
|
305
|
-
renderer: config.renderer,
|
|
306
|
-
width: config.width
|
|
307
|
-
});
|
|
308
|
-
chart.setOption(option);
|
|
309
|
-
const dataUrl = chart.getDataURL({
|
|
310
|
-
backgroundColor: "#ffffff",
|
|
311
|
-
pixelRatio: 2,
|
|
312
|
-
type: config.imageType === "svg" ? "svg" : "png"
|
|
313
|
-
});
|
|
314
|
-
chart.dispose();
|
|
315
|
-
container.remove();
|
|
316
|
-
return {
|
|
317
|
-
data: await dataUrlToUint8Array$1(dataUrl),
|
|
318
|
-
type: config.imageType
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Node.js-side ECharts rendering (not built-in).
|
|
323
|
-
*
|
|
324
|
-
* Requires a server-side canvas or SVG DOM implementation
|
|
325
|
-
* (e.g. `node-canvas` + `echarts`). Throws an error prompting
|
|
326
|
-
* the user to provide a custom render function when used
|
|
327
|
-
* outside the browser.
|
|
328
|
-
*/
|
|
329
|
-
async function renderInNode(_option, _config) {
|
|
330
|
-
throw new DocxKitError(ERROR_CODES.PLUGIN_RENDER_FAILED, "ECharts Node renderer is not built-in. Use a server-side canvas implementation (e.g. node-canvas + echarts) and provide a custom render function. See https://github.com/ntnyq/docx-kit for alternatives.");
|
|
331
|
-
}
|
|
332
|
-
//#endregion
|
|
333
|
-
//#region src/builder/DocxBuilder.ts
|
|
334
|
-
/**
|
|
335
|
-
* Fluent document builder.
|
|
336
|
-
*
|
|
337
|
-
* Use `createDocx()` to instantiate, then chain `.h1()`, `.p()`, `.table()`,
|
|
338
|
-
* etc. to build content. Call `.save()`, `.toBlob()`, `.toBuffer()`, or
|
|
339
|
-
* `.toBase64()` to export the document.
|
|
340
|
-
*
|
|
341
|
-
* @template TStyles — Inferred stylesheet type from `config.styles`
|
|
342
|
-
* @template TPlugins — Accumulated plugin registry (built via `.use()`)
|
|
343
|
-
*/
|
|
344
|
-
var DocxBuilder = class {
|
|
345
|
-
config;
|
|
346
|
-
nodes = [];
|
|
347
|
-
pendingSetups = [];
|
|
348
|
-
pluginMap = /* @__PURE__ */ new Map();
|
|
349
|
-
constructor(config = {}) {
|
|
350
|
-
this.config = config;
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Add a raw DSL node to the document.
|
|
354
|
-
*
|
|
355
|
-
* @param node - — Any block-level node
|
|
356
|
-
* @returns The builder (for chaining)
|
|
357
|
-
*
|
|
358
|
-
* @example
|
|
359
|
-
* ```ts
|
|
360
|
-
* doc.add({ type: 'heading', level: 2, text: 'Section' })
|
|
361
|
-
* doc.add({ type: 'pageBreak' })
|
|
362
|
-
* ```
|
|
363
|
-
*/
|
|
364
|
-
add(node) {
|
|
365
|
-
this.nodes.push(node);
|
|
366
|
-
return this;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Add a bullet (unordered) list.
|
|
370
|
-
*
|
|
371
|
-
* @param items - — List items (strings or structured items)
|
|
372
|
-
* @param options - — Optional bullet character, className, style, level
|
|
373
|
-
* @returns The builder (for chaining)
|
|
374
|
-
*
|
|
375
|
-
* @example
|
|
376
|
-
* ```ts
|
|
377
|
-
* doc.bulletList(['Item 1', 'Item 2', 'Item 3'])
|
|
378
|
-
* doc.bulletList([
|
|
379
|
-
* 'Simple item',
|
|
380
|
-
* { text: 'Rich item', className: 'highlight' },
|
|
381
|
-
* ], { bullet: '\u25CB' })
|
|
382
|
-
* ```
|
|
383
|
-
*/
|
|
384
|
-
bulletList(items, options = {}) {
|
|
385
|
-
return this.add({
|
|
386
|
-
items,
|
|
387
|
-
type: "bulletList",
|
|
388
|
-
...options
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Add a level-1 heading.
|
|
393
|
-
*
|
|
394
|
-
* @param text - — Heading text
|
|
395
|
-
* @param options - — Optional style overrides (className, id, style)
|
|
396
|
-
* @returns The builder (for chaining)
|
|
397
|
-
*
|
|
398
|
-
* @example
|
|
399
|
-
* ```ts
|
|
400
|
-
* doc.h1('Introduction', { className: 'title' })
|
|
401
|
-
* ```
|
|
402
|
-
*/
|
|
403
|
-
h1(text, options = {}) {
|
|
404
|
-
return this.add({
|
|
405
|
-
level: 1,
|
|
406
|
-
text,
|
|
407
|
-
type: "heading",
|
|
408
|
-
...options
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Add a level-2 heading.
|
|
413
|
-
*
|
|
414
|
-
* @param text - — Heading text
|
|
415
|
-
* @param options - — Optional style overrides
|
|
416
|
-
* @returns The builder (for chaining)
|
|
417
|
-
*/
|
|
418
|
-
h2(text, options = {}) {
|
|
419
|
-
return this.add({
|
|
420
|
-
level: 2,
|
|
421
|
-
text,
|
|
422
|
-
type: "heading",
|
|
423
|
-
...options
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Add a level-3 heading.
|
|
428
|
-
*
|
|
429
|
-
* @param text - — Heading text
|
|
430
|
-
* @param options - — Optional style overrides
|
|
431
|
-
* @returns The builder (for chaining)
|
|
432
|
-
*/
|
|
433
|
-
h3(text, options = {}) {
|
|
434
|
-
return this.add({
|
|
435
|
-
level: 3,
|
|
436
|
-
text,
|
|
437
|
-
type: "heading",
|
|
438
|
-
...options
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* Add a level-4 heading.
|
|
443
|
-
*
|
|
444
|
-
* @param text - — Heading text
|
|
445
|
-
* @param options - — Optional style overrides
|
|
446
|
-
* @returns The builder (for chaining)
|
|
447
|
-
*/
|
|
448
|
-
h4(text, options = {}) {
|
|
449
|
-
return this.add({
|
|
450
|
-
level: 4,
|
|
451
|
-
text,
|
|
452
|
-
type: "heading",
|
|
453
|
-
...options
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* Add a level-5 heading.
|
|
458
|
-
*
|
|
459
|
-
* @param text - — Heading text
|
|
460
|
-
* @param options - — Optional style overrides
|
|
461
|
-
* @returns The builder (for chaining)
|
|
462
|
-
*/
|
|
463
|
-
h5(text, options = {}) {
|
|
464
|
-
return this.add({
|
|
465
|
-
level: 5,
|
|
466
|
-
text,
|
|
467
|
-
type: "heading",
|
|
468
|
-
...options
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Add a level-6 heading.
|
|
473
|
-
*
|
|
474
|
-
* @param text - — Heading text
|
|
475
|
-
* @param options - — Optional style overrides
|
|
476
|
-
* @returns The builder (for chaining)
|
|
477
|
-
*/
|
|
478
|
-
h6(text, options = {}) {
|
|
479
|
-
return this.add({
|
|
480
|
-
level: 6,
|
|
481
|
-
text,
|
|
482
|
-
type: "heading",
|
|
483
|
-
...options
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Add a hyperlink.
|
|
488
|
-
*
|
|
489
|
-
* @param url - — Target URL
|
|
490
|
-
* @param text - — Display text
|
|
491
|
-
* @param options - — Optional style overrides
|
|
492
|
-
* @returns The builder (for chaining)
|
|
493
|
-
*
|
|
494
|
-
* @example
|
|
495
|
-
* ```ts
|
|
496
|
-
* doc.hyperlink('https://example.com', 'Click here')
|
|
497
|
-
* ```
|
|
498
|
-
*/
|
|
499
|
-
hyperlink(url, text, options = {}) {
|
|
500
|
-
return this.add({
|
|
501
|
-
children: [text],
|
|
502
|
-
type: "hyperlink",
|
|
503
|
-
url,
|
|
504
|
-
...options
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Add an image node.
|
|
509
|
-
*
|
|
510
|
-
* @param options - — Image node options (data, width, height, etc.)
|
|
511
|
-
* @returns The builder (for chaining)
|
|
512
|
-
*
|
|
513
|
-
* @example
|
|
514
|
-
* ```ts
|
|
515
|
-
* doc.image({ data: imageBytes, width: 400, height: 300 })
|
|
516
|
-
* ```
|
|
517
|
-
*/
|
|
518
|
-
image(options) {
|
|
519
|
-
return this.add({
|
|
520
|
-
type: "image",
|
|
521
|
-
...options
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* Add a numbered (ordered) list.
|
|
526
|
-
*
|
|
527
|
-
* @param items - — List items
|
|
528
|
-
* @param options - — Optional numbering format, start, className, style, level
|
|
529
|
-
* @returns The builder (for chaining)
|
|
530
|
-
*
|
|
531
|
-
* @example
|
|
532
|
-
* ```ts
|
|
533
|
-
* doc.numberedList(['First', 'Second', 'Third'])
|
|
534
|
-
* doc.numberedList(
|
|
535
|
-
* [{ text: 'Intro' }, { text: 'Body' }],
|
|
536
|
-
* { numberingFormat: 'upperRoman', start: 1 },
|
|
537
|
-
* )
|
|
538
|
-
* ```
|
|
539
|
-
*/
|
|
540
|
-
numberedList(items, options = {}) {
|
|
541
|
-
return this.add({
|
|
542
|
-
items,
|
|
543
|
-
type: "numberedList",
|
|
544
|
-
...options
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* Add a paragraph.
|
|
549
|
-
*
|
|
550
|
-
* @param text - — Paragraph text content
|
|
551
|
-
* @param options - — Optional style overrides (className, id, style)
|
|
552
|
-
* @returns The builder (for chaining)
|
|
553
|
-
*
|
|
554
|
-
* @example
|
|
555
|
-
* ```ts
|
|
556
|
-
* doc.p('Hello world', { className: 'body', style: { textAlign: 'center' } })
|
|
557
|
-
* ```
|
|
558
|
-
*/
|
|
559
|
-
p(text, options = {}) {
|
|
560
|
-
return this.add({
|
|
561
|
-
text,
|
|
562
|
-
type: "paragraph",
|
|
563
|
-
...options
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Add a forced page break.
|
|
568
|
-
*
|
|
569
|
-
* @returns The builder (for chaining)
|
|
570
|
-
*
|
|
571
|
-
* @example
|
|
572
|
-
* ```ts
|
|
573
|
-
* doc.h1('Chapter 1').pageBreak().h1('Chapter 2')
|
|
574
|
-
* ```
|
|
575
|
-
*/
|
|
576
|
-
pageBreak() {
|
|
577
|
-
return this.add({ type: "pageBreak" });
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* Invoke a registered plugin.
|
|
581
|
-
*
|
|
582
|
-
* @param name - — Plugin name (must match a previously registered plugin)
|
|
583
|
-
* @param options - — Plugin-specific options
|
|
584
|
-
* @param style - — Optional inline style for the plugin's container
|
|
585
|
-
* @returns The builder (for chaining)
|
|
586
|
-
*
|
|
587
|
-
* @example
|
|
588
|
-
* ```ts
|
|
589
|
-
* doc.use(qrcodePlugin()).plugin('qrcode', { text: 'https://example.com' })
|
|
590
|
-
* ```
|
|
591
|
-
*/
|
|
592
|
-
plugin(name, options, style) {
|
|
593
|
-
return this.add({
|
|
594
|
-
name,
|
|
595
|
-
options,
|
|
596
|
-
style,
|
|
597
|
-
type: "plugin"
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Save the document to a file (Node.js only).
|
|
602
|
-
*
|
|
603
|
-
* **⚠️ Not available in browser environments.**
|
|
604
|
-
* Use {@link toBlob} and trigger a download instead.
|
|
605
|
-
*
|
|
606
|
-
* @param filename - — Output file path (e.g. `"report.docx"`)
|
|
607
|
-
*
|
|
608
|
-
* @example
|
|
609
|
-
* ```ts
|
|
610
|
-
* await doc.save('output.docx')
|
|
611
|
-
* ```
|
|
612
|
-
*/
|
|
613
|
-
async save(filename) {
|
|
614
|
-
const { saveDocument } = await import("./fs-DF8ug9Wi.mjs").then((n) => n.t);
|
|
615
|
-
return saveDocument(await this.toDocument(), filename);
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Start a new document section.
|
|
619
|
-
*
|
|
620
|
-
* Each section can have its own page size, orientation, margins,
|
|
621
|
-
* headers, and footers. Content added after this call belongs to
|
|
622
|
-
* the new section.
|
|
623
|
-
*
|
|
624
|
-
* @param config - — Optional section-level page/header/footer overrides
|
|
625
|
-
* @returns The builder (for chaining)
|
|
626
|
-
*
|
|
627
|
-
* @example
|
|
628
|
-
* ```ts
|
|
629
|
-
* // Simple section break
|
|
630
|
-
* doc.p('Section 1 content').section().p('Section 2 content')
|
|
631
|
-
*
|
|
632
|
-
* // Section with custom page setup
|
|
633
|
-
* doc.section({ page: { size: 'A3', orientation: 'landscape' } })
|
|
634
|
-
* .h1('Wide table')
|
|
635
|
-
* .table({ columns: [...], data: [...] })
|
|
636
|
-
*
|
|
637
|
-
* // Section with header and footer
|
|
638
|
-
* doc.section({
|
|
639
|
-
* header: { default: { children: ['Chapter 2', 'Confidential'] } },
|
|
640
|
-
* footer: { default: { children: ['Page 2'] } },
|
|
641
|
-
* })
|
|
642
|
-
* ```
|
|
643
|
-
*/
|
|
644
|
-
section(config) {
|
|
645
|
-
this.nodes.push({
|
|
646
|
-
config,
|
|
647
|
-
type: "sectionBreak"
|
|
648
|
-
});
|
|
649
|
-
return this;
|
|
650
|
-
}
|
|
651
|
-
/**
|
|
652
|
-
* Add a table.
|
|
653
|
-
*
|
|
654
|
-
* @param options - — Table node options (columns, data, style, etc.)
|
|
655
|
-
* @returns The builder (for chaining)
|
|
656
|
-
*
|
|
657
|
-
* @example
|
|
658
|
-
* ```ts
|
|
659
|
-
* doc.table({
|
|
660
|
-
* columns: [
|
|
661
|
-
* { key: 'name', title: 'Name' },
|
|
662
|
-
* { key: 'value', title: 'Value', align: 'right' },
|
|
663
|
-
* ],
|
|
664
|
-
* data: [{ name: 'Revenue', value: '$1.2M' }],
|
|
665
|
-
* headerCellStyle: { fontWeight: 'bold' },
|
|
666
|
-
* })
|
|
667
|
-
* ```
|
|
668
|
-
*/
|
|
669
|
-
table(options) {
|
|
670
|
-
return this.add({
|
|
671
|
-
type: "table",
|
|
672
|
-
...options
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Export the document as a base64-encoded string.
|
|
677
|
-
*
|
|
678
|
-
* @returns Base64-encoded .docx data
|
|
679
|
-
*
|
|
680
|
-
* @example
|
|
681
|
-
* ```ts
|
|
682
|
-
* const b64 = await doc.toBase64()
|
|
683
|
-
* // Send b64 over HTTP or store in a database
|
|
684
|
-
* ```
|
|
685
|
-
*/
|
|
686
|
-
async toBase64() {
|
|
687
|
-
const { packToBase64String } = await import("./pack-Bkdfq0_u.mjs");
|
|
688
|
-
return packToBase64String(await this.toDocument());
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Export the document as a `Blob` (browser-friendly).
|
|
692
|
-
*
|
|
693
|
-
* @returns A `Blob` containing the .docx binary
|
|
694
|
-
*
|
|
695
|
-
* @example
|
|
696
|
-
* ```ts
|
|
697
|
-
* const blob = await doc.toBlob()
|
|
698
|
-
* const url = URL.createObjectURL(blob)
|
|
699
|
-
* ```
|
|
700
|
-
*/
|
|
701
|
-
async toBlob() {
|
|
702
|
-
const { packToBlob } = await import("./pack-Bkdfq0_u.mjs");
|
|
703
|
-
return packToBlob(await this.toDocument());
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Export the document as a `Uint8Array` (alias for {@link toUint8Array}).
|
|
707
|
-
*
|
|
708
|
-
* **Note:** Despite the name, this returns a standard `Uint8Array`,
|
|
709
|
-
* not a Node.js `Buffer`. Prefer using {@link toUint8Array} for clarity.
|
|
710
|
-
*
|
|
711
|
-
* @returns Raw .docx bytes
|
|
712
|
-
*
|
|
713
|
-
* @example
|
|
714
|
-
* ```ts
|
|
715
|
-
* const bytes = await doc.toBuffer()
|
|
716
|
-
* fs.writeFileSync('output.docx', bytes)
|
|
717
|
-
* ```
|
|
718
|
-
*/
|
|
719
|
-
async toBuffer() {
|
|
720
|
-
return this.toUint8Array();
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Compile and return the internal `docx` {@link Document} instance.
|
|
724
|
-
*
|
|
725
|
-
* Useful if you need to further manipulate the document with the
|
|
726
|
-
* raw `docx` library before packaging.
|
|
727
|
-
*
|
|
728
|
-
* @returns A `docx` `Document` object
|
|
729
|
-
*/
|
|
730
|
-
async toDocument() {
|
|
731
|
-
await Promise.all(this.pendingSetups);
|
|
732
|
-
this.pendingSetups.length = 0;
|
|
733
|
-
const { compileDocument } = await import("./compileDocument-DIFdjo67.mjs");
|
|
734
|
-
return compileDocument({
|
|
735
|
-
config: this.config,
|
|
736
|
-
nodes: this.nodes,
|
|
737
|
-
plugins: this.pluginMap
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Serialize the builder state to a JSON-friendly object.
|
|
742
|
-
*
|
|
743
|
-
* Useful for debugging, serialization, or AI-driven document generation.
|
|
744
|
-
*
|
|
745
|
-
* @returns The config + content node array as a plain object
|
|
746
|
-
*/
|
|
747
|
-
toJSON() {
|
|
748
|
-
return {
|
|
749
|
-
...this.config,
|
|
750
|
-
content: this.nodes
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
/**
|
|
754
|
-
* Export the document as a `Uint8Array` (browser & Node.js).
|
|
755
|
-
*
|
|
756
|
-
* This is the preferred cross-platform export method.
|
|
757
|
-
*
|
|
758
|
-
* @returns Raw .docx bytes
|
|
759
|
-
*
|
|
760
|
-
* @example
|
|
761
|
-
* ```ts
|
|
762
|
-
* const bytes = await doc.toUint8Array()
|
|
763
|
-
* // In Node.js: import { writeFileSync } from 'node:fs'
|
|
764
|
-
* // In browser: trigger a download
|
|
765
|
-
* ```
|
|
766
|
-
*/
|
|
767
|
-
async toUint8Array() {
|
|
768
|
-
const { packToBuffer } = await import("./pack-Bkdfq0_u.mjs");
|
|
769
|
-
return packToBuffer(await this.toDocument());
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Register a plugin.
|
|
773
|
-
*
|
|
774
|
-
* The plugin's name and options type are accumulated into the builder's
|
|
775
|
-
* type-level plugin registry, enabling type-safe `.plugin()` calls.
|
|
776
|
-
*
|
|
777
|
-
* @param plugin - — The plugin definition returned by `definePlugin()`
|
|
778
|
-
* @returns The builder with the plugin type merged into `TPlugins`
|
|
779
|
-
*
|
|
780
|
-
* @example
|
|
781
|
-
* ```ts
|
|
782
|
-
* const doc = createDocx()
|
|
783
|
-
* .use(qrcodePlugin())
|
|
784
|
-
* .plugin('qrcode', { text: 'https://example.com' })
|
|
785
|
-
* ```
|
|
786
|
-
*/
|
|
787
|
-
use(plugin) {
|
|
788
|
-
this.pluginMap.set(plugin.name, plugin);
|
|
789
|
-
if (plugin.setup) this.pendingSetups.push(Promise.resolve(plugin.setup()));
|
|
790
|
-
return this;
|
|
791
|
-
}
|
|
792
|
-
};
|
|
793
|
-
//#endregion
|
|
794
|
-
//#region src/plugins/timeline/index.ts
|
|
795
|
-
/**
|
|
796
|
-
* Timeline plugin — renders a chronological timeline as a styled table.
|
|
797
|
-
*
|
|
798
|
-
* Each event occupies one table row: a date cell, a visual connector
|
|
799
|
-
* cell, and a content cell (title + optional description).
|
|
800
|
-
*
|
|
801
|
-
* @module plugins/timeline
|
|
802
|
-
*
|
|
803
|
-
* @example
|
|
804
|
-
* ```ts
|
|
805
|
-
* const doc = createDocx()
|
|
806
|
-
* .use(timelinePlugin())
|
|
807
|
-
* .plugin('timeline', {
|
|
808
|
-
* events: [
|
|
809
|
-
* { date: '2026-01', title: '项目立项', description: '完成需求评审' },
|
|
810
|
-
* { date: '2026-03', title: 'MVP 发布', description: '核心功能上线' },
|
|
811
|
-
* ],
|
|
812
|
-
* })
|
|
813
|
-
* .save('timeline.docx')
|
|
814
|
-
* ```
|
|
815
|
-
*/
|
|
816
|
-
/** Default table column widths (in DXA, twips). */
|
|
817
|
-
const COL_WIDTHS = {
|
|
818
|
-
connector: 600,
|
|
819
|
-
content: 6400,
|
|
820
|
-
date: 1800
|
|
821
|
-
};
|
|
822
|
-
/**
|
|
823
|
-
* Create a Timeline plugin instance.
|
|
824
|
-
*
|
|
825
|
-
* @returns A configured DocxPlugin for `'timeline'`
|
|
826
|
-
*
|
|
827
|
-
* @example
|
|
828
|
-
* ```ts
|
|
829
|
-
* import { createDocx, timelinePlugin } from 'docx-kit'
|
|
830
|
-
*
|
|
831
|
-
* const doc = createDocx()
|
|
832
|
-
* .use(timelinePlugin())
|
|
833
|
-
* .plugin('timeline', {
|
|
834
|
-
* events: [
|
|
835
|
-
* { date: '2026-01', title: '项目启动', description: '团队组建完成' },
|
|
836
|
-
* { date: '2026-04', title: 'Beta 测试', description: '收集用户反馈' },
|
|
837
|
-
* { date: '2026-06', title: '正式发布' },
|
|
838
|
-
* ],
|
|
839
|
-
* })
|
|
840
|
-
* ```
|
|
841
|
-
*/
|
|
842
|
-
function timelinePlugin() {
|
|
843
|
-
return definePlugin({
|
|
844
|
-
name: "timeline",
|
|
845
|
-
render(options) {
|
|
846
|
-
const { accentColor = "4472C4", events, layout = "alternating" } = options;
|
|
847
|
-
if (events.length === 0) return new Paragraph({ text: "(No events)" });
|
|
848
|
-
const emptyCell = (width) => new TableCell({
|
|
849
|
-
children: [new Paragraph({ children: [] })],
|
|
850
|
-
width: {
|
|
851
|
-
size: width,
|
|
852
|
-
type: WidthType.DXA
|
|
853
|
-
},
|
|
854
|
-
borders: {
|
|
855
|
-
bottom: {
|
|
856
|
-
color: "FFFFFF",
|
|
857
|
-
size: 0,
|
|
858
|
-
style: BorderStyle.NONE
|
|
859
|
-
},
|
|
860
|
-
left: {
|
|
861
|
-
color: "FFFFFF",
|
|
862
|
-
size: 0,
|
|
863
|
-
style: BorderStyle.NONE
|
|
864
|
-
},
|
|
865
|
-
right: {
|
|
866
|
-
color: "FFFFFF",
|
|
867
|
-
size: 0,
|
|
868
|
-
style: BorderStyle.NONE
|
|
869
|
-
},
|
|
870
|
-
top: {
|
|
871
|
-
color: "FFFFFF",
|
|
872
|
-
size: 0,
|
|
873
|
-
style: BorderStyle.NONE
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
const connectorCell = () => new TableCell({
|
|
878
|
-
width: {
|
|
879
|
-
size: COL_WIDTHS.connector,
|
|
880
|
-
type: WidthType.DXA
|
|
881
|
-
},
|
|
882
|
-
borders: {
|
|
883
|
-
bottom: {
|
|
884
|
-
color: "FFFFFF",
|
|
885
|
-
size: 0,
|
|
886
|
-
style: BorderStyle.NONE
|
|
887
|
-
},
|
|
888
|
-
left: {
|
|
889
|
-
color: "FFFFFF",
|
|
890
|
-
size: 0,
|
|
891
|
-
style: BorderStyle.NONE
|
|
892
|
-
},
|
|
893
|
-
right: {
|
|
894
|
-
color: "FFFFFF",
|
|
895
|
-
size: 0,
|
|
896
|
-
style: BorderStyle.NONE
|
|
897
|
-
},
|
|
898
|
-
top: {
|
|
899
|
-
color: "FFFFFF",
|
|
900
|
-
size: 0,
|
|
901
|
-
style: BorderStyle.NONE
|
|
902
|
-
}
|
|
903
|
-
},
|
|
904
|
-
children: [new Paragraph({
|
|
905
|
-
alignment: AlignmentType.CENTER,
|
|
906
|
-
children: [new TextRun({
|
|
907
|
-
color: accentColor,
|
|
908
|
-
font: "Arial",
|
|
909
|
-
size: 24,
|
|
910
|
-
text: "●"
|
|
911
|
-
})]
|
|
912
|
-
})]
|
|
913
|
-
});
|
|
914
|
-
const dateCell = (date) => new TableCell({
|
|
915
|
-
width: {
|
|
916
|
-
size: COL_WIDTHS.date,
|
|
917
|
-
type: WidthType.DXA
|
|
918
|
-
},
|
|
919
|
-
borders: {
|
|
920
|
-
bottom: {
|
|
921
|
-
color: accentColor,
|
|
922
|
-
size: 1,
|
|
923
|
-
style: BorderStyle.SINGLE
|
|
924
|
-
},
|
|
925
|
-
left: {
|
|
926
|
-
color: "FFFFFF",
|
|
927
|
-
size: 0,
|
|
928
|
-
style: BorderStyle.NONE
|
|
929
|
-
},
|
|
930
|
-
right: {
|
|
931
|
-
color: "FFFFFF",
|
|
932
|
-
size: 0,
|
|
933
|
-
style: BorderStyle.NONE
|
|
934
|
-
},
|
|
935
|
-
top: {
|
|
936
|
-
color: "FFFFFF",
|
|
937
|
-
size: 0,
|
|
938
|
-
style: BorderStyle.NONE
|
|
939
|
-
}
|
|
940
|
-
},
|
|
941
|
-
children: [new Paragraph({
|
|
942
|
-
alignment: AlignmentType.RIGHT,
|
|
943
|
-
children: [new TextRun({
|
|
944
|
-
bold: true,
|
|
945
|
-
color: accentColor,
|
|
946
|
-
font: "Arial",
|
|
947
|
-
size: 22,
|
|
948
|
-
text: date
|
|
949
|
-
})]
|
|
950
|
-
})],
|
|
951
|
-
shading: {
|
|
952
|
-
fill: "F0F5FF",
|
|
953
|
-
type: ShadingType.CLEAR
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
const contentCell = (title, description) => {
|
|
957
|
-
const runs = [new TextRun({
|
|
958
|
-
bold: true,
|
|
959
|
-
font: "Arial",
|
|
960
|
-
size: 24,
|
|
961
|
-
text: title
|
|
962
|
-
})];
|
|
963
|
-
if (description) runs.push(new TextRun({
|
|
964
|
-
break: 1,
|
|
965
|
-
color: "555555",
|
|
966
|
-
font: "Arial",
|
|
967
|
-
size: 20,
|
|
968
|
-
text: description
|
|
969
|
-
}));
|
|
970
|
-
return new TableCell({
|
|
971
|
-
children: [new Paragraph({ children: runs })],
|
|
972
|
-
width: {
|
|
973
|
-
size: COL_WIDTHS.content,
|
|
974
|
-
type: WidthType.DXA
|
|
975
|
-
},
|
|
976
|
-
borders: {
|
|
977
|
-
left: {
|
|
978
|
-
color: "FFFFFF",
|
|
979
|
-
size: 0,
|
|
980
|
-
style: BorderStyle.NONE
|
|
981
|
-
},
|
|
982
|
-
right: {
|
|
983
|
-
color: "FFFFFF",
|
|
984
|
-
size: 0,
|
|
985
|
-
style: BorderStyle.NONE
|
|
986
|
-
},
|
|
987
|
-
top: {
|
|
988
|
-
color: "FFFFFF",
|
|
989
|
-
size: 0,
|
|
990
|
-
style: BorderStyle.NONE
|
|
991
|
-
},
|
|
992
|
-
bottom: {
|
|
993
|
-
color: "DDDDDD",
|
|
994
|
-
size: 1,
|
|
995
|
-
style: BorderStyle.SINGLE
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
});
|
|
999
|
-
};
|
|
1000
|
-
return new Table({
|
|
1001
|
-
rows: events.map((event, index) => {
|
|
1002
|
-
if (layout === "left" ? true : layout === "right" ? false : index % 2 === 0) return new TableRow({ children: [
|
|
1003
|
-
dateCell(event.date),
|
|
1004
|
-
connectorCell(),
|
|
1005
|
-
contentCell(event.title, event.description)
|
|
1006
|
-
] });
|
|
1007
|
-
return new TableRow({ children: [
|
|
1008
|
-
emptyCell(COL_WIDTHS.date),
|
|
1009
|
-
connectorCell(),
|
|
1010
|
-
contentCell(event.title, event.description)
|
|
1011
|
-
] });
|
|
1012
|
-
}),
|
|
1013
|
-
width: {
|
|
1014
|
-
size: 100,
|
|
1015
|
-
type: WidthType.PERCENTAGE
|
|
1016
|
-
},
|
|
1017
|
-
borders: {
|
|
1018
|
-
bottom: {
|
|
1019
|
-
color: "FFFFFF",
|
|
1020
|
-
size: 0,
|
|
1021
|
-
style: BorderStyle.NONE
|
|
1022
|
-
},
|
|
1023
|
-
left: {
|
|
1024
|
-
color: "FFFFFF",
|
|
1025
|
-
size: 0,
|
|
1026
|
-
style: BorderStyle.NONE
|
|
1027
|
-
},
|
|
1028
|
-
right: {
|
|
1029
|
-
color: "FFFFFF",
|
|
1030
|
-
size: 0,
|
|
1031
|
-
style: BorderStyle.NONE
|
|
1032
|
-
},
|
|
1033
|
-
top: {
|
|
1034
|
-
color: "FFFFFF",
|
|
1035
|
-
size: 0,
|
|
1036
|
-
style: BorderStyle.NONE
|
|
1037
|
-
},
|
|
1038
|
-
insideHorizontal: {
|
|
1039
|
-
color: "FFFFFF",
|
|
1040
|
-
size: 0,
|
|
1041
|
-
style: BorderStyle.NONE
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
//#endregion
|
|
1049
|
-
//#region src/plugins/watermark/index.ts
|
|
1050
|
-
/**
|
|
1051
|
-
* Watermark plugin — text watermark rendered as a styled paragraph.
|
|
1052
|
-
*
|
|
1053
|
-
* Renders a large, semi-transparent (gray) text paragraph that can be
|
|
1054
|
-
* placed in a section header or footer to simulate a watermark.
|
|
1055
|
-
*
|
|
1056
|
-
* @module plugins/watermark
|
|
1057
|
-
*
|
|
1058
|
-
* @example
|
|
1059
|
-
* ```ts
|
|
1060
|
-
* const doc = createDocx()
|
|
1061
|
-
* .use(watermarkPlugin())
|
|
1062
|
-
* .plugin('watermark', { text: 'CONFIDENTIAL', color: 'FF0000' })
|
|
1063
|
-
* .save('watermark.docx')
|
|
1064
|
-
* ```
|
|
1065
|
-
*/
|
|
1066
|
-
/**
|
|
1067
|
-
* Create a Watermark plugin instance.
|
|
1068
|
-
*
|
|
1069
|
-
* @returns A configured DocxPlugin for `'watermark'`
|
|
1070
|
-
*
|
|
1071
|
-
* @example
|
|
1072
|
-
* ```ts
|
|
1073
|
-
* import { createDocx, watermarkPlugin } from 'docx-kit'
|
|
1074
|
-
*
|
|
1075
|
-
* const doc = createDocx()
|
|
1076
|
-
* .use(watermarkPlugin())
|
|
1077
|
-
* .plugin('watermark', { text: 'DRAFT', color: 'FF0000' })
|
|
1078
|
-
* ```
|
|
1079
|
-
*/
|
|
1080
|
-
function watermarkPlugin() {
|
|
1081
|
-
return definePlugin({
|
|
1082
|
-
name: "watermark",
|
|
1083
|
-
render(options) {
|
|
1084
|
-
return new Paragraph({
|
|
1085
|
-
alignment: options.alignment ?? AlignmentType.CENTER,
|
|
1086
|
-
spacing: {
|
|
1087
|
-
after: 0,
|
|
1088
|
-
before: 0
|
|
1089
|
-
},
|
|
1090
|
-
children: [new TextRun({
|
|
1091
|
-
bold: true,
|
|
1092
|
-
color: options.color ?? "BFBFBF",
|
|
1093
|
-
font: "Arial",
|
|
1094
|
-
size: options.fontSize ?? 48,
|
|
1095
|
-
text: options.text
|
|
1096
|
-
})]
|
|
1097
|
-
});
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1100
|
-
}
|
|
1101
|
-
//#endregion
|
|
1102
|
-
//#region src/plugins/code-block/index.ts
|
|
1103
|
-
/**
|
|
1104
|
-
* Code Block plugin — renders source code with monospaced font and
|
|
1105
|
-
* optional line numbers and syntax highlighting.
|
|
1106
|
-
*
|
|
1107
|
-
* Syntax highlighting requires `highlight.js` as an optional peer dependency.
|
|
1108
|
-
* When unavailable (or when `language` is not provided), the plugin falls
|
|
1109
|
-
* back to plain monospaced rendering.
|
|
1110
|
-
*
|
|
1111
|
-
* @module plugins/code-block
|
|
1112
|
-
*
|
|
1113
|
-
* @example
|
|
1114
|
-
* ```ts
|
|
1115
|
-
* const doc = createDocx()
|
|
1116
|
-
* .use(codeBlockPlugin())
|
|
1117
|
-
* .plugin('codeBlock', {
|
|
1118
|
-
* code: `export function hello() {\n return "world"\n}`,
|
|
1119
|
-
* language: 'typescript',
|
|
1120
|
-
* showLineNumbers: true,
|
|
1121
|
-
* })
|
|
1122
|
-
* .save('code.docx')
|
|
1123
|
-
* ```
|
|
1124
|
-
*/
|
|
1125
|
-
/** Highlight.js token → `TextRun` color mapping for common token types. */
|
|
1126
|
-
const TOKEN_COLORS = {
|
|
1127
|
-
attr: "D4D4D4",
|
|
1128
|
-
built_in: "4EC9B0",
|
|
1129
|
-
comment: "6A9955",
|
|
1130
|
-
function: "DCDCAA",
|
|
1131
|
-
keyword: "569CD6",
|
|
1132
|
-
literal: "B5CEA8",
|
|
1133
|
-
meta: "9B9B9B",
|
|
1134
|
-
number: "B5CEA8",
|
|
1135
|
-
params: "D4D4D4",
|
|
1136
|
-
property: "9CDCFE",
|
|
1137
|
-
regexp: "D16969",
|
|
1138
|
-
string: "CE9178",
|
|
1139
|
-
title: "DCDCAA",
|
|
1140
|
-
type: "4EC9B0",
|
|
1141
|
-
variable: "9CDCFE"
|
|
1142
|
-
};
|
|
1143
|
-
const CODE_BG = "F5F5F5";
|
|
1144
|
-
const LINE_NO_COLOR = "999999";
|
|
1145
|
-
const MONO_FONT = "Courier New";
|
|
1146
|
-
const FONT_SIZE = 16;
|
|
1147
|
-
/**
|
|
1148
|
-
* Create a CodeBlock plugin instance.
|
|
1149
|
-
*
|
|
1150
|
-
* @returns A configured DocxPlugin for `'codeBlock'`
|
|
1151
|
-
*/
|
|
1152
|
-
function codeBlockPlugin() {
|
|
1153
|
-
return definePlugin({
|
|
1154
|
-
name: "codeBlock",
|
|
1155
|
-
async render(options) {
|
|
1156
|
-
const lines = options.code.split("\n");
|
|
1157
|
-
if (options.language) try {
|
|
1158
|
-
return tokensToParagraphs((await import("highlight.js")).default.highlight(options.code, { language: options.language }).value || options.code, options.showLineNumbers);
|
|
1159
|
-
} catch {}
|
|
1160
|
-
return linesToParagraphs(lines, options.showLineNumbers);
|
|
1161
|
-
}
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
/** Render plain (unhighlighted) code lines as Paragraphs. */
|
|
1165
|
-
function linesToParagraphs(lines, showLineNumbers) {
|
|
1166
|
-
const maxDigits = showLineNumbers ? String(lines.length).length : 0;
|
|
1167
|
-
return lines.map((line, i) => new Paragraph({
|
|
1168
|
-
shading: {
|
|
1169
|
-
fill: CODE_BG,
|
|
1170
|
-
type: ShadingType.CLEAR
|
|
1171
|
-
},
|
|
1172
|
-
spacing: {
|
|
1173
|
-
after: 0,
|
|
1174
|
-
before: 0
|
|
1175
|
-
},
|
|
1176
|
-
children: [...showLineNumbers ? [new TextRun({
|
|
1177
|
-
color: LINE_NO_COLOR,
|
|
1178
|
-
font: MONO_FONT,
|
|
1179
|
-
size: FONT_SIZE,
|
|
1180
|
-
text: `${String(i + 1).padStart(maxDigits, " ")} \u2502 `
|
|
1181
|
-
})] : [], new TextRun({
|
|
1182
|
-
font: MONO_FONT,
|
|
1183
|
-
size: FONT_SIZE,
|
|
1184
|
-
text: line
|
|
1185
|
-
})]
|
|
1186
|
-
}));
|
|
1187
|
-
}
|
|
1188
|
-
/** Parse a single line of Highlight.js HTML into TextRun tokens. */
|
|
1189
|
-
function parseHighlightHtml(html) {
|
|
1190
|
-
const results = [];
|
|
1191
|
-
const regex = /<span\s+class="hljs-(\w+)">(.*?)<\/span>/g;
|
|
1192
|
-
let lastIndex = 0;
|
|
1193
|
-
let match = regex.exec(html);
|
|
1194
|
-
while (match !== null) {
|
|
1195
|
-
if (match.index > lastIndex) {
|
|
1196
|
-
const before = html.slice(lastIndex, match.index);
|
|
1197
|
-
if (before) results.push(new TextRun({
|
|
1198
|
-
font: MONO_FONT,
|
|
1199
|
-
size: FONT_SIZE,
|
|
1200
|
-
text: before
|
|
1201
|
-
}));
|
|
1202
|
-
}
|
|
1203
|
-
const type = match[1];
|
|
1204
|
-
const text = match[2];
|
|
1205
|
-
const color = TOKEN_COLORS[type] ?? "D4D4D4";
|
|
1206
|
-
results.push(new TextRun({
|
|
1207
|
-
color,
|
|
1208
|
-
font: MONO_FONT,
|
|
1209
|
-
size: FONT_SIZE,
|
|
1210
|
-
text
|
|
1211
|
-
}));
|
|
1212
|
-
lastIndex = match.index + match[0].length;
|
|
1213
|
-
match = regex.exec(html);
|
|
1214
|
-
}
|
|
1215
|
-
if (lastIndex < html.length) {
|
|
1216
|
-
const after = html.slice(lastIndex);
|
|
1217
|
-
if (after) results.push(new TextRun({
|
|
1218
|
-
font: MONO_FONT,
|
|
1219
|
-
size: FONT_SIZE,
|
|
1220
|
-
text: after
|
|
1221
|
-
}));
|
|
1222
|
-
}
|
|
1223
|
-
if (results.length === 0 && html) results.push(new TextRun({
|
|
1224
|
-
font: MONO_FONT,
|
|
1225
|
-
size: FONT_SIZE,
|
|
1226
|
-
text: html
|
|
1227
|
-
}));
|
|
1228
|
-
return results;
|
|
1229
|
-
}
|
|
1230
|
-
/**
|
|
1231
|
-
* Convert Highlight.js HTML token output back to styled Paragraphs.
|
|
1232
|
-
*
|
|
1233
|
-
* Parses `<span class="hljs-xxx">…</span>` markup emitted by
|
|
1234
|
-
* `hljs.highlight().value`.
|
|
1235
|
-
*/
|
|
1236
|
-
function tokensToParagraphs(html, showLineNumbers) {
|
|
1237
|
-
const lineStrings = html.split("\n");
|
|
1238
|
-
const maxDigits = showLineNumbers ? String(lineStrings.length).length : 0;
|
|
1239
|
-
return lineStrings.map((lineHtml, i) => {
|
|
1240
|
-
const runs = parseHighlightHtml(lineHtml);
|
|
1241
|
-
return new Paragraph({
|
|
1242
|
-
shading: {
|
|
1243
|
-
fill: CODE_BG,
|
|
1244
|
-
type: ShadingType.CLEAR
|
|
1245
|
-
},
|
|
1246
|
-
spacing: {
|
|
1247
|
-
after: 0,
|
|
1248
|
-
before: 0
|
|
1249
|
-
},
|
|
1250
|
-
children: [...showLineNumbers ? [new TextRun({
|
|
1251
|
-
color: LINE_NO_COLOR,
|
|
1252
|
-
font: MONO_FONT,
|
|
1253
|
-
size: FONT_SIZE,
|
|
1254
|
-
text: `${String(i + 1).padStart(maxDigits, " ")} \u2502 `
|
|
1255
|
-
})] : [], ...runs]
|
|
1256
|
-
});
|
|
1257
|
-
});
|
|
1258
|
-
}
|
|
1259
|
-
//#endregion
|
|
1260
|
-
//#region src/plugins/cover-page/index.ts
|
|
1261
|
-
/**
|
|
1262
|
-
* Cover Page plugin — generates a professional title page.
|
|
1263
|
-
*
|
|
1264
|
-
* Renders a centered block with title, subtitle, author, date,
|
|
1265
|
-
* organization, and an optional decorative horizontal rule.
|
|
1266
|
-
*
|
|
1267
|
-
* @module plugins/cover-page
|
|
1268
|
-
*
|
|
1269
|
-
* @example
|
|
1270
|
-
* ```ts
|
|
1271
|
-
* const doc = createDocx()
|
|
1272
|
-
* .use(coverPagePlugin())
|
|
1273
|
-
* .plugin('coverPage', {
|
|
1274
|
-
* title: 'Q3 运营报告',
|
|
1275
|
-
* subtitle: '数据驱动 · 智能决策',
|
|
1276
|
-
* author: '数据分析部',
|
|
1277
|
-
* })
|
|
1278
|
-
* .save('report.docx')
|
|
1279
|
-
* ```
|
|
1280
|
-
*/
|
|
1281
|
-
/**
|
|
1282
|
-
* Create a Cover Page plugin instance.
|
|
1283
|
-
*
|
|
1284
|
-
* @returns A configured DocxPlugin for `'coverPage'`
|
|
1285
|
-
*
|
|
1286
|
-
* @example
|
|
1287
|
-
* ```ts
|
|
1288
|
-
* import { coverPagePlugin, createDocx } from 'docx-kit'
|
|
1289
|
-
*
|
|
1290
|
-
* const doc = createDocx()
|
|
1291
|
-
* .use(coverPagePlugin())
|
|
1292
|
-
* .plugin('coverPage', {
|
|
1293
|
-
* title: '年度报告',
|
|
1294
|
-
* subtitle: '2026',
|
|
1295
|
-
* author: '战略发展部',
|
|
1296
|
-
* organization: 'XX 科技集团',
|
|
1297
|
-
* })
|
|
1298
|
-
* ```
|
|
1299
|
-
*/
|
|
1300
|
-
function coverPagePlugin() {
|
|
1301
|
-
return definePlugin({
|
|
1302
|
-
name: "coverPage",
|
|
1303
|
-
render(options) {
|
|
1304
|
-
const alignment = options.alignment ?? AlignmentType.CENTER;
|
|
1305
|
-
const showRule = options.showRule !== false;
|
|
1306
|
-
const titlePara = new Paragraph({
|
|
1307
|
-
alignment,
|
|
1308
|
-
spacing: { after: 200 },
|
|
1309
|
-
children: [new TextRun({
|
|
1310
|
-
bold: true,
|
|
1311
|
-
font: "Arial",
|
|
1312
|
-
size: 56,
|
|
1313
|
-
text: options.title
|
|
1314
|
-
})],
|
|
1315
|
-
...options.backgroundColor ? { shading: {
|
|
1316
|
-
fill: options.backgroundColor,
|
|
1317
|
-
type: ShadingType.CLEAR
|
|
1318
|
-
} } : {}
|
|
1319
|
-
});
|
|
1320
|
-
return [
|
|
1321
|
-
new Paragraph({
|
|
1322
|
-
children: [],
|
|
1323
|
-
spacing: { before: 2400 }
|
|
1324
|
-
}),
|
|
1325
|
-
titlePara,
|
|
1326
|
-
...options.subtitle ? [new Paragraph({
|
|
1327
|
-
alignment,
|
|
1328
|
-
spacing: { after: 400 },
|
|
1329
|
-
children: [new TextRun({
|
|
1330
|
-
color: "555555",
|
|
1331
|
-
font: "Arial",
|
|
1332
|
-
size: 32,
|
|
1333
|
-
text: options.subtitle
|
|
1334
|
-
})]
|
|
1335
|
-
})] : [],
|
|
1336
|
-
...showRule ? [new Paragraph({
|
|
1337
|
-
alignment,
|
|
1338
|
-
children: [],
|
|
1339
|
-
spacing: { after: 400 },
|
|
1340
|
-
border: { bottom: {
|
|
1341
|
-
color: "4472C4",
|
|
1342
|
-
size: 6,
|
|
1343
|
-
space: 1,
|
|
1344
|
-
style: BorderStyle.SINGLE
|
|
1345
|
-
} }
|
|
1346
|
-
})] : [],
|
|
1347
|
-
...options.author ? [new Paragraph({
|
|
1348
|
-
alignment,
|
|
1349
|
-
spacing: { after: 120 },
|
|
1350
|
-
children: [new TextRun({
|
|
1351
|
-
font: "Arial",
|
|
1352
|
-
size: 28,
|
|
1353
|
-
text: options.author
|
|
1354
|
-
})]
|
|
1355
|
-
})] : [],
|
|
1356
|
-
...options.organization ? [new Paragraph({
|
|
1357
|
-
alignment,
|
|
1358
|
-
spacing: { after: 120 },
|
|
1359
|
-
children: [new TextRun({
|
|
1360
|
-
color: "333333",
|
|
1361
|
-
font: "Arial",
|
|
1362
|
-
size: 24,
|
|
1363
|
-
text: options.organization
|
|
1364
|
-
})]
|
|
1365
|
-
})] : [],
|
|
1366
|
-
...options.date ? [new Paragraph({
|
|
1367
|
-
alignment,
|
|
1368
|
-
spacing: { after: 0 },
|
|
1369
|
-
children: [new TextRun({
|
|
1370
|
-
color: "777777",
|
|
1371
|
-
font: "Arial",
|
|
1372
|
-
size: 24,
|
|
1373
|
-
text: options.date
|
|
1374
|
-
})]
|
|
1375
|
-
})] : []
|
|
1376
|
-
];
|
|
1377
|
-
}
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
//#endregion
|
|
1381
|
-
//#region src/plugins/data-table/index.ts
|
|
1382
|
-
/**
|
|
1383
|
-
* Data Table plugin — renders an array of objects as a styled table.
|
|
1384
|
-
*
|
|
1385
|
-
* Columns are auto-inferred from the first object’s keys. Column headers
|
|
1386
|
-
* can be localised via `labels`, value formatting is controlled by `format`,
|
|
1387
|
-
* and per-column alignment is controlled by `align`.
|
|
1388
|
-
*
|
|
1389
|
-
* @module plugins/data-table
|
|
1390
|
-
*
|
|
1391
|
-
* @example
|
|
1392
|
-
* ```ts
|
|
1393
|
-
* const doc = createDocx()
|
|
1394
|
-
* .use(dataTablePlugin())
|
|
1395
|
-
* .plugin('dataTable', {
|
|
1396
|
-
* data: [
|
|
1397
|
-
* { name: 'Alice', age: 30, salary: 85000 },
|
|
1398
|
-
* { name: 'Bob', age: 25, salary: 62000 },
|
|
1399
|
-
* ],
|
|
1400
|
-
* labels: { name: '姓名', age: '年龄', salary: '薪资' },
|
|
1401
|
-
* format: { salary: 'currency' },
|
|
1402
|
-
* striped: true,
|
|
1403
|
-
* })
|
|
1404
|
-
* .save('table.docx')
|
|
1405
|
-
* ```
|
|
1406
|
-
*/
|
|
1407
|
-
const HEADER_SHADING$1 = {
|
|
1408
|
-
fill: "4472C4",
|
|
1409
|
-
type: ShadingType.CLEAR
|
|
1410
|
-
};
|
|
1411
|
-
const STRIPE_SHADING = {
|
|
1412
|
-
fill: "F2F2F2",
|
|
1413
|
-
type: ShadingType.CLEAR
|
|
1414
|
-
};
|
|
1415
|
-
/**
|
|
1416
|
-
* Create a DataTable plugin instance.
|
|
1417
|
-
*
|
|
1418
|
-
* @returns A configured DocxPlugin for `'dataTable'`
|
|
1419
|
-
*/
|
|
1420
|
-
function dataTablePlugin() {
|
|
1421
|
-
return definePlugin({
|
|
1422
|
-
name: "dataTable",
|
|
1423
|
-
render(options) {
|
|
1424
|
-
const { align, bordered, data, format, labels, striped } = options;
|
|
1425
|
-
if (!data.length) return new Paragraph({
|
|
1426
|
-
alignment: AlignmentType.CENTER,
|
|
1427
|
-
children: [new TextRun({
|
|
1428
|
-
color: "999999",
|
|
1429
|
-
text: "(no data)"
|
|
1430
|
-
})]
|
|
1431
|
-
});
|
|
1432
|
-
const columns = Object.keys(data[0]);
|
|
1433
|
-
const displayedBordered = bordered !== false;
|
|
1434
|
-
const borderOptions = displayedBordered ? {
|
|
1435
|
-
bottom: {
|
|
1436
|
-
color: "CCCCCC",
|
|
1437
|
-
size: 1,
|
|
1438
|
-
style: BorderStyle.SINGLE
|
|
1439
|
-
},
|
|
1440
|
-
left: {
|
|
1441
|
-
color: "CCCCCC",
|
|
1442
|
-
size: 1,
|
|
1443
|
-
style: BorderStyle.SINGLE
|
|
1444
|
-
},
|
|
1445
|
-
right: {
|
|
1446
|
-
color: "CCCCCC",
|
|
1447
|
-
size: 1,
|
|
1448
|
-
style: BorderStyle.SINGLE
|
|
1449
|
-
},
|
|
1450
|
-
top: {
|
|
1451
|
-
color: "CCCCCC",
|
|
1452
|
-
size: 1,
|
|
1453
|
-
style: BorderStyle.SINGLE
|
|
1454
|
-
}
|
|
1455
|
-
} : {};
|
|
1456
|
-
const rows = [new TableRow({ children: columns.map((col) => {
|
|
1457
|
-
const label = labels?.[col] ?? col;
|
|
1458
|
-
return new TableCell({
|
|
1459
|
-
shading: HEADER_SHADING$1,
|
|
1460
|
-
children: [new Paragraph({
|
|
1461
|
-
alignment: AlignmentType.CENTER,
|
|
1462
|
-
children: [new TextRun({
|
|
1463
|
-
bold: true,
|
|
1464
|
-
color: "FFFFFF",
|
|
1465
|
-
text: label
|
|
1466
|
-
})]
|
|
1467
|
-
})],
|
|
1468
|
-
...displayedBordered ? { borders: borderOptions } : {}
|
|
1469
|
-
});
|
|
1470
|
-
}) })];
|
|
1471
|
-
for (let i = 0; i < data.length; i++) {
|
|
1472
|
-
const row = data[i];
|
|
1473
|
-
const cells = columns.map((col) => {
|
|
1474
|
-
const colAlign = align?.[col] ?? inferAlignment(col, data);
|
|
1475
|
-
const val = formatValue(row[col], format?.[col]);
|
|
1476
|
-
return new TableCell({
|
|
1477
|
-
shading: striped && i % 2 === 1 ? STRIPE_SHADING : void 0,
|
|
1478
|
-
children: [new Paragraph({
|
|
1479
|
-
alignment: alignmentToDocx(colAlign),
|
|
1480
|
-
children: [new TextRun({ text: val })]
|
|
1481
|
-
})],
|
|
1482
|
-
...displayedBordered ? { borders: borderOptions } : {}
|
|
1483
|
-
});
|
|
1484
|
-
});
|
|
1485
|
-
rows.push(new TableRow({ children: cells }));
|
|
1486
|
-
}
|
|
1487
|
-
return new Table({
|
|
1488
|
-
rows,
|
|
1489
|
-
width: {
|
|
1490
|
-
size: 100,
|
|
1491
|
-
type: WidthType.PERCENTAGE
|
|
1492
|
-
}
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
function alignmentToDocx(a) {
|
|
1498
|
-
return a === "right" ? AlignmentType.RIGHT : a === "center" ? AlignmentType.CENTER : AlignmentType.LEFT;
|
|
1499
|
-
}
|
|
1500
|
-
function formatValue(value, fmt) {
|
|
1501
|
-
if (value == null) return "";
|
|
1502
|
-
if (fmt === "currency") {
|
|
1503
|
-
const n = typeof value === "number" ? value : Number(value);
|
|
1504
|
-
return Number.isFinite(n) ? `\u00A5${n.toLocaleString("zh-CN", {
|
|
1505
|
-
maximumFractionDigits: 2,
|
|
1506
|
-
minimumFractionDigits: 2
|
|
1507
|
-
})}` : String(value);
|
|
1508
|
-
}
|
|
1509
|
-
if (fmt === "number") {
|
|
1510
|
-
const n = typeof value === "number" ? value : Number(value);
|
|
1511
|
-
return Number.isFinite(n) ? n.toLocaleString("zh-CN") : String(value);
|
|
1512
|
-
}
|
|
1513
|
-
if (fmt === "percent") {
|
|
1514
|
-
const n = typeof value === "number" ? value : Number(value);
|
|
1515
|
-
return Number.isFinite(n) ? `${(n * 100).toFixed(1)}%` : String(value);
|
|
1516
|
-
}
|
|
1517
|
-
if (fmt === "date") {
|
|
1518
|
-
if (value instanceof Date) return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`;
|
|
1519
|
-
const d = new Date(String(value));
|
|
1520
|
-
return Number.isNaN(d.getTime()) ? String(value) : `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
1521
|
-
}
|
|
1522
|
-
return String(value);
|
|
1523
|
-
}
|
|
1524
|
-
function inferAlignment(col, data) {
|
|
1525
|
-
return isColumnNumeric(col, data) ? "right" : "left";
|
|
1526
|
-
}
|
|
1527
|
-
function isColumnNumeric(col, data) {
|
|
1528
|
-
let hasNumeric = false;
|
|
1529
|
-
for (const row of data) {
|
|
1530
|
-
const v = row[col];
|
|
1531
|
-
if (v == null || v === "") continue;
|
|
1532
|
-
if (typeof v === "number") hasNumeric = true;
|
|
1533
|
-
else if (typeof v === "string" && Number.isFinite(Number(v))) hasNumeric = true;
|
|
1534
|
-
else return false;
|
|
1535
|
-
}
|
|
1536
|
-
return hasNumeric;
|
|
1537
|
-
}
|
|
1538
|
-
//#endregion
|
|
1539
|
-
//#region src/plugins/page-number/index.ts
|
|
1540
|
-
/**
|
|
1541
|
-
* Page Number plugin — generates a `Paragraph` containing a page number
|
|
1542
|
-
* field for placement inside section headers or footers.
|
|
1543
|
-
*
|
|
1544
|
-
* The paragraph can be inserted into a `Header` or `Footer` definition
|
|
1545
|
-
* and will render the current page number.
|
|
1546
|
-
*
|
|
1547
|
-
* @module plugins/page-number
|
|
1548
|
-
*
|
|
1549
|
-
* @example
|
|
1550
|
-
* ```ts
|
|
1551
|
-
* const pn = doc.plugin('pageNumber', { format: 'roman' })
|
|
1552
|
-
* doc.section({ footers: { default: [pn] } })
|
|
1553
|
-
* ```
|
|
1554
|
-
*/
|
|
1555
|
-
/**
|
|
1556
|
-
* Create a Page Number plugin instance.
|
|
1557
|
-
*
|
|
1558
|
-
* @returns A configured DocxPlugin for `'pageNumber'`
|
|
1559
|
-
*
|
|
1560
|
-
* @example
|
|
1561
|
-
* ```ts
|
|
1562
|
-
* import { createDocx, pageNumberPlugin } from 'docx-kit'
|
|
1563
|
-
*
|
|
1564
|
-
* const doc = createDocx()
|
|
1565
|
-
* .use(pageNumberPlugin())
|
|
1566
|
-
* // Use the page number content in a footer
|
|
1567
|
-
* const pn = doc.plugin('pageNumber', { alignment: 'center' })
|
|
1568
|
-
* ```
|
|
1569
|
-
*/
|
|
1570
|
-
function pageNumberPlugin() {
|
|
1571
|
-
return definePlugin({
|
|
1572
|
-
name: "pageNumber",
|
|
1573
|
-
render(options) {
|
|
1574
|
-
const alignment = options.alignment ?? AlignmentType.CENTER;
|
|
1575
|
-
const fontSize = options.fontSize ?? 20;
|
|
1576
|
-
if (options.showTotal) return new Paragraph({
|
|
1577
|
-
alignment,
|
|
1578
|
-
spacing: {
|
|
1579
|
-
after: 0,
|
|
1580
|
-
before: 0
|
|
1581
|
-
},
|
|
1582
|
-
children: [
|
|
1583
|
-
new TextRun({
|
|
1584
|
-
font: "Arial",
|
|
1585
|
-
size: fontSize,
|
|
1586
|
-
text: "Page "
|
|
1587
|
-
}),
|
|
1588
|
-
new TextRun({
|
|
1589
|
-
children: [PageNumber.CURRENT],
|
|
1590
|
-
font: "Arial",
|
|
1591
|
-
size: fontSize
|
|
1592
|
-
}),
|
|
1593
|
-
new TextRun({
|
|
1594
|
-
font: "Arial",
|
|
1595
|
-
size: fontSize,
|
|
1596
|
-
text: " of "
|
|
1597
|
-
}),
|
|
1598
|
-
new TextRun({
|
|
1599
|
-
children: [PageNumber.TOTAL_PAGES],
|
|
1600
|
-
font: "Arial",
|
|
1601
|
-
size: fontSize
|
|
1602
|
-
})
|
|
1603
|
-
]
|
|
1604
|
-
});
|
|
1605
|
-
return new Paragraph({
|
|
1606
|
-
alignment,
|
|
1607
|
-
spacing: {
|
|
1608
|
-
after: 0,
|
|
1609
|
-
before: 0
|
|
1610
|
-
},
|
|
1611
|
-
children: [new TextRun({
|
|
1612
|
-
children: [PageNumber.CURRENT],
|
|
1613
|
-
font: "Arial",
|
|
1614
|
-
size: fontSize
|
|
1615
|
-
})]
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
});
|
|
1619
|
-
}
|
|
1620
|
-
//#endregion
|
|
1621
|
-
//#region src/builder/createDocx.ts
|
|
1622
|
-
/**
|
|
1623
|
-
* Factory functions for creating `DocxBuilder` instances.
|
|
1624
|
-
*
|
|
1625
|
-
* Two entry points are provided:
|
|
1626
|
-
* - `createDocx()` — Fluent builder API with chaining
|
|
1627
|
-
* - `renderDocx()` — JSON-schema driven (AI-friendly / serializable)
|
|
1628
|
-
*
|
|
1629
|
-
* @module builder/createDocx
|
|
1630
|
-
*/
|
|
1631
|
-
/**
|
|
1632
|
-
* Create a new DocxBuilder with optional configuration.
|
|
1633
|
-
*
|
|
1634
|
-
* This is the primary entry point for the fluent builder API.
|
|
1635
|
-
* Chain `.h1()`, `.p()`, `.table()`, etc. and call `.save()`
|
|
1636
|
-
* or `.toBlob()` to export.
|
|
1637
|
-
*
|
|
1638
|
-
* @param config - — Document configuration (page, styles, metadata, theme, defaults)
|
|
1639
|
-
* @returns A new `DocxBuilder` instance
|
|
1640
|
-
*
|
|
1641
|
-
* @example
|
|
1642
|
-
* ```ts
|
|
1643
|
-
* const styles = defineStyles({
|
|
1644
|
-
* title: { fontSize: 28, fontWeight: 'bold' },
|
|
1645
|
-
* body: { fontSize: 12, lineHeight: 1.5 },
|
|
1646
|
-
* })
|
|
1647
|
-
*
|
|
1648
|
-
* const doc = createDocx({
|
|
1649
|
-
* styles,
|
|
1650
|
-
* page: { size: 'A4', margin: '20mm' },
|
|
1651
|
-
* metadata: { title: 'Report', creator: 'docx-kit' },
|
|
1652
|
-
* })
|
|
1653
|
-
*
|
|
1654
|
-
* await doc
|
|
1655
|
-
* .h1('Annual Report', { className: 'title' })
|
|
1656
|
-
* .p('Lorem ipsum...', { className: 'body' })
|
|
1657
|
-
* .save('report.docx')
|
|
1658
|
-
* ```
|
|
1659
|
-
*/
|
|
1660
|
-
function createDocx(config = {}) {
|
|
1661
|
-
return new DocxBuilder(config);
|
|
1662
|
-
}
|
|
1663
|
-
/**
|
|
1664
|
-
* Render a document from a JSON schema (AI-friendly / serializable DSL).
|
|
1665
|
-
*
|
|
1666
|
-
* Unlike `createDocx()`, this accepts a single JSON-serializable object
|
|
1667
|
-
* with `content` (node array), optional `styles`, and optional `page` config.
|
|
1668
|
-
* Ideal for AI-driven document generation or API integrations.
|
|
1669
|
-
*
|
|
1670
|
-
* @param schema - — The `DocxSchema` object
|
|
1671
|
-
* @returns A `DocxBuilder` instance (ready to export)
|
|
1672
|
-
*
|
|
1673
|
-
* @example
|
|
1674
|
-
* ```ts
|
|
1675
|
-
* const blob = await renderDocx({
|
|
1676
|
-
* page: { size: 'A4', margin: '20mm' },
|
|
1677
|
-
* styles: {
|
|
1678
|
-
* h1: { fontSize: 24, fontWeight: 'bold' },
|
|
1679
|
-
* p: { fontSize: 12 },
|
|
1680
|
-
* },
|
|
1681
|
-
* content: [
|
|
1682
|
-
* { type: 'heading', level: 1, text: 'Report', className: 'h1' },
|
|
1683
|
-
* { type: 'paragraph', text: 'This is a report generated via JSON DSL.', className: 'p' },
|
|
1684
|
-
* { type: 'pageBreak' },
|
|
1685
|
-
* {
|
|
1686
|
-
* type: 'table',
|
|
1687
|
-
* columns: [{ key: 'name', title: 'Name' }, { key: 'value', title: 'Value' }],
|
|
1688
|
-
* data: [{ name: 'Revenue', value: '$1.2M' }],
|
|
1689
|
-
* },
|
|
1690
|
-
* ],
|
|
1691
|
-
* }).toBlob()
|
|
1692
|
-
* ```
|
|
1693
|
-
*/
|
|
1694
|
-
function renderDocx(schema) {
|
|
1695
|
-
const { content, ...config } = schema;
|
|
1696
|
-
const builder = new DocxBuilder(config);
|
|
1697
|
-
for (const node of content) builder.add(node);
|
|
1698
|
-
return builder;
|
|
1699
|
-
}
|
|
1700
|
-
//#endregion
|
|
1701
|
-
//#region src/plugins/property-table/index.ts
|
|
1702
|
-
/**
|
|
1703
|
-
* Property Table plugin — renders key-value pairs as a styled 2-column table.
|
|
1704
|
-
*
|
|
1705
|
-
* The key column is right-aligned with gray background; the value column
|
|
1706
|
-
* is left-aligned. Ideal for config tables, parameter docs, and spec sheets.
|
|
1707
|
-
*
|
|
1708
|
-
* @module plugins/property-table
|
|
1709
|
-
*
|
|
1710
|
-
* @example
|
|
1711
|
-
* ```ts
|
|
1712
|
-
* const doc = createDocx()
|
|
1713
|
-
* .use(propertyTablePlugin())
|
|
1714
|
-
* .plugin('propertyTable', {
|
|
1715
|
-
* items: [
|
|
1716
|
-
* { key: '项目名称', value: 'XX管理系统' },
|
|
1717
|
-
* { key: '技术栈', value: 'React + Node.js + PostgreSQL' },
|
|
1718
|
-
* ],
|
|
1719
|
-
* keyBold: true,
|
|
1720
|
-
* })
|
|
1721
|
-
* .save('props.docx')
|
|
1722
|
-
* ```
|
|
1723
|
-
*/
|
|
1724
|
-
const KEY_SHADING = {
|
|
1725
|
-
fill: "F2F2F2",
|
|
1726
|
-
type: ShadingType.CLEAR
|
|
1727
|
-
};
|
|
1728
|
-
const VALUE_SHADING = {
|
|
1729
|
-
fill: "F2F2F2",
|
|
1730
|
-
type: ShadingType.CLEAR
|
|
1731
|
-
};
|
|
1732
|
-
/**
|
|
1733
|
-
* Create a PropertyTable plugin instance.
|
|
1734
|
-
*
|
|
1735
|
-
* @returns A configured DocxPlugin for `'propertyTable'`
|
|
1736
|
-
*/
|
|
1737
|
-
function propertyTablePlugin() {
|
|
1738
|
-
return definePlugin({
|
|
1739
|
-
name: "propertyTable",
|
|
1740
|
-
render(options) {
|
|
1741
|
-
const { items, keyBold = true, striped = true } = options;
|
|
1742
|
-
if (!items.length) return new Paragraph({
|
|
1743
|
-
alignment: AlignmentType.CENTER,
|
|
1744
|
-
children: [new TextRun({
|
|
1745
|
-
color: "999999",
|
|
1746
|
-
text: "(no items)"
|
|
1747
|
-
})]
|
|
1748
|
-
});
|
|
1749
|
-
return new Table({
|
|
1750
|
-
rows: items.map((item, i) => {
|
|
1751
|
-
const isEven = i % 2 === 1;
|
|
1752
|
-
const cellShading = striped && isEven ? VALUE_SHADING : void 0;
|
|
1753
|
-
return new TableRow({ children: [new TableCell({
|
|
1754
|
-
shading: striped && isEven ? KEY_SHADING : void 0,
|
|
1755
|
-
width: {
|
|
1756
|
-
size: 3500,
|
|
1757
|
-
type: WidthType.DXA
|
|
1758
|
-
},
|
|
1759
|
-
children: [new Paragraph({
|
|
1760
|
-
alignment: AlignmentType.RIGHT,
|
|
1761
|
-
children: [new TextRun({
|
|
1762
|
-
bold: keyBold,
|
|
1763
|
-
text: item.key
|
|
1764
|
-
})]
|
|
1765
|
-
})]
|
|
1766
|
-
}), new TableCell({
|
|
1767
|
-
shading: cellShading,
|
|
1768
|
-
children: [new Paragraph({ children: [new TextRun({ text: item.value })] })]
|
|
1769
|
-
})] });
|
|
1770
|
-
}),
|
|
1771
|
-
width: {
|
|
1772
|
-
size: 100,
|
|
1773
|
-
type: WidthType.PERCENTAGE
|
|
1774
|
-
}
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
});
|
|
1778
|
-
}
|
|
1779
|
-
//#endregion
|
|
1780
|
-
//#region src/plugins/meeting-minutes/index.ts
|
|
1781
|
-
/**
|
|
1782
|
-
* Meeting Minutes plugin — structured meeting notes with header, meta info,
|
|
1783
|
-
* and an agenda table.
|
|
1784
|
-
*
|
|
1785
|
-
* Renders a title paragraph, a date/attendees summary line, and a 4-column
|
|
1786
|
-
* agenda table (Topic | Discussion | Decision | Owner).
|
|
1787
|
-
*
|
|
1788
|
-
* @module plugins/meeting-minutes
|
|
1789
|
-
*
|
|
1790
|
-
* @example
|
|
1791
|
-
* ```ts
|
|
1792
|
-
* const doc = createDocx()
|
|
1793
|
-
* .use(meetingMinutesPlugin())
|
|
1794
|
-
* .plugin('meetingMinutes', {
|
|
1795
|
-
* title: '项目周会纪要',
|
|
1796
|
-
* date: '2026-06-11',
|
|
1797
|
-
* attendees: ['张三', '李四', '王五'],
|
|
1798
|
-
* agenda: [
|
|
1799
|
-
* { topic: '项目进度', discussion: '模块A已完成80%', decision: '下周一上线', owner: '张三' },
|
|
1800
|
-
* { topic: '风险项', discussion: '第三方API不稳定', decision: '增加重试机制', owner: '李四' },
|
|
1801
|
-
* ],
|
|
1802
|
-
* })
|
|
1803
|
-
* .save('minutes.docx')
|
|
1804
|
-
* ```
|
|
1805
|
-
*/
|
|
1806
|
-
const HEADER_SHADING = {
|
|
1807
|
-
fill: "4472C4",
|
|
1808
|
-
type: ShadingType.CLEAR
|
|
1809
|
-
};
|
|
1810
|
-
const BORDER = {
|
|
1811
|
-
bottom: {
|
|
1812
|
-
color: "CCCCCC",
|
|
1813
|
-
size: 1,
|
|
1814
|
-
style: BorderStyle.SINGLE
|
|
1815
|
-
},
|
|
1816
|
-
left: {
|
|
1817
|
-
color: "CCCCCC",
|
|
1818
|
-
size: 1,
|
|
1819
|
-
style: BorderStyle.SINGLE
|
|
1820
|
-
},
|
|
1821
|
-
right: {
|
|
1822
|
-
color: "CCCCCC",
|
|
1823
|
-
size: 1,
|
|
1824
|
-
style: BorderStyle.SINGLE
|
|
1825
|
-
},
|
|
1826
|
-
top: {
|
|
1827
|
-
color: "CCCCCC",
|
|
1828
|
-
size: 1,
|
|
1829
|
-
style: BorderStyle.SINGLE
|
|
1830
|
-
}
|
|
1831
|
-
};
|
|
1832
|
-
const AGENDA_COLUMNS = [
|
|
1833
|
-
"议题",
|
|
1834
|
-
"讨论",
|
|
1835
|
-
"决议",
|
|
1836
|
-
"负责人"
|
|
1837
|
-
];
|
|
1838
|
-
/**
|
|
1839
|
-
* Create a MeetingMinutes plugin instance.
|
|
1840
|
-
*
|
|
1841
|
-
* @returns A configured DocxPlugin for `'meetingMinutes'`
|
|
1842
|
-
*/
|
|
1843
|
-
function meetingMinutesPlugin() {
|
|
1844
|
-
return definePlugin({
|
|
1845
|
-
name: "meetingMinutes",
|
|
1846
|
-
render(options) {
|
|
1847
|
-
const titlePara = new Paragraph({
|
|
1848
|
-
children: [new TextRun({
|
|
1849
|
-
bold: true,
|
|
1850
|
-
size: 32,
|
|
1851
|
-
text: options.title
|
|
1852
|
-
})],
|
|
1853
|
-
heading: HeadingLevel.HEADING_1,
|
|
1854
|
-
spacing: { after: 200 }
|
|
1855
|
-
});
|
|
1856
|
-
const metaPara = new Paragraph({
|
|
1857
|
-
spacing: { after: 300 },
|
|
1858
|
-
children: [
|
|
1859
|
-
new TextRun({
|
|
1860
|
-
bold: true,
|
|
1861
|
-
text: "日期:"
|
|
1862
|
-
}),
|
|
1863
|
-
new TextRun({ text: options.date }),
|
|
1864
|
-
new TextRun({ text: " " }),
|
|
1865
|
-
new TextRun({
|
|
1866
|
-
bold: true,
|
|
1867
|
-
text: "参会人员:"
|
|
1868
|
-
}),
|
|
1869
|
-
new TextRun({ text: options.attendees.join("、") })
|
|
1870
|
-
]
|
|
1871
|
-
});
|
|
1872
|
-
if (options.agenda.length === 0) return [titlePara, metaPara];
|
|
1873
|
-
const headerCells = AGENDA_COLUMNS.map((col) => new TableCell({
|
|
1874
|
-
borders: BORDER,
|
|
1875
|
-
shading: HEADER_SHADING,
|
|
1876
|
-
children: [new Paragraph({
|
|
1877
|
-
alignment: AlignmentType.CENTER,
|
|
1878
|
-
children: [new TextRun({
|
|
1879
|
-
bold: true,
|
|
1880
|
-
color: "FFFFFF",
|
|
1881
|
-
text: col
|
|
1882
|
-
})]
|
|
1883
|
-
})]
|
|
1884
|
-
}));
|
|
1885
|
-
const agendaRows = options.agenda.map((item) => {
|
|
1886
|
-
return new TableRow({ children: [
|
|
1887
|
-
item.topic,
|
|
1888
|
-
item.discussion,
|
|
1889
|
-
item.decision ?? "",
|
|
1890
|
-
item.owner ?? ""
|
|
1891
|
-
].map((val) => new TableCell({
|
|
1892
|
-
borders: BORDER,
|
|
1893
|
-
children: [new Paragraph({ children: [new TextRun({ text: val })] })]
|
|
1894
|
-
})) });
|
|
1895
|
-
});
|
|
1896
|
-
return [
|
|
1897
|
-
titlePara,
|
|
1898
|
-
metaPara,
|
|
1899
|
-
new Table({
|
|
1900
|
-
rows: [new TableRow({ children: headerCells }), ...agendaRows],
|
|
1901
|
-
width: {
|
|
1902
|
-
size: 100,
|
|
1903
|
-
type: WidthType.PERCENTAGE
|
|
1904
|
-
}
|
|
1905
|
-
})
|
|
1906
|
-
];
|
|
1907
|
-
}
|
|
1908
|
-
});
|
|
1909
|
-
}
|
|
1910
|
-
//#endregion
|
|
1911
|
-
//#region src/plugins/signature-block/index.ts
|
|
1912
|
-
/**
|
|
1913
|
-
* Signature Block plugin — renders signature lines for contracts and approvals.
|
|
1914
|
-
*
|
|
1915
|
-
* Each party gets a cell in a borderless table with a bold label,
|
|
1916
|
-
* a signature line (underlined placeholder), and an optional date.
|
|
1917
|
-
*
|
|
1918
|
-
* @module plugins/signature-block
|
|
1919
|
-
*
|
|
1920
|
-
* @example
|
|
1921
|
-
* ```ts
|
|
1922
|
-
* const doc = createDocx()
|
|
1923
|
-
* .use(signatureBlockPlugin())
|
|
1924
|
-
* .plugin('signatureBlock', {
|
|
1925
|
-
* parties: [
|
|
1926
|
-
* { label: '甲方(盖章)', date: '2026年 月 日' },
|
|
1927
|
-
* { label: '乙方(盖章)', date: '2026年 月 日' },
|
|
1928
|
-
* ],
|
|
1929
|
-
* columns: 2,
|
|
1930
|
-
* })
|
|
1931
|
-
* .save('signature.docx')
|
|
1932
|
-
* ```
|
|
1933
|
-
*/
|
|
1934
|
-
/**
|
|
1935
|
-
* Create a SignatureBlock plugin instance.
|
|
1936
|
-
*
|
|
1937
|
-
* @returns A configured DocxPlugin for `'signatureBlock'`
|
|
1938
|
-
*/
|
|
1939
|
-
function signatureBlockPlugin() {
|
|
1940
|
-
return definePlugin({
|
|
1941
|
-
name: "signatureBlock",
|
|
1942
|
-
render(options) {
|
|
1943
|
-
const { columns = 2, parties } = options;
|
|
1944
|
-
if (!parties.length) return new Paragraph({
|
|
1945
|
-
alignment: AlignmentType.CENTER,
|
|
1946
|
-
children: [new TextRun({
|
|
1947
|
-
color: "999999",
|
|
1948
|
-
text: "(no signatures)"
|
|
1949
|
-
})]
|
|
1950
|
-
});
|
|
1951
|
-
function buildPartyCell(party) {
|
|
1952
|
-
return new TableCell({ children: [
|
|
1953
|
-
new Paragraph({
|
|
1954
|
-
children: [new TextRun({
|
|
1955
|
-
bold: true,
|
|
1956
|
-
text: party.label
|
|
1957
|
-
})],
|
|
1958
|
-
spacing: { after: 200 }
|
|
1959
|
-
}),
|
|
1960
|
-
party.name ? new Paragraph({
|
|
1961
|
-
children: [new TextRun({
|
|
1962
|
-
text: party.name,
|
|
1963
|
-
underline: {}
|
|
1964
|
-
})],
|
|
1965
|
-
spacing: { after: 80 }
|
|
1966
|
-
}) : new Paragraph({
|
|
1967
|
-
spacing: { after: 80 },
|
|
1968
|
-
children: [new TextRun({
|
|
1969
|
-
text: "_____________________________",
|
|
1970
|
-
underline: {}
|
|
1971
|
-
})]
|
|
1972
|
-
}),
|
|
1973
|
-
new Paragraph({ children: [new TextRun({
|
|
1974
|
-
color: "666666",
|
|
1975
|
-
size: 18,
|
|
1976
|
-
text: party.date ?? ""
|
|
1977
|
-
})] })
|
|
1978
|
-
] });
|
|
1979
|
-
}
|
|
1980
|
-
const partyRows = [];
|
|
1981
|
-
for (let i = 0; i < parties.length; i += columns) partyRows[partyRows.length] = parties.slice(i, i + columns);
|
|
1982
|
-
return new Table({
|
|
1983
|
-
rows: partyRows.map((rowParties) => {
|
|
1984
|
-
let rowCells = rowParties.map(buildPartyCell);
|
|
1985
|
-
if (rowCells.length < columns) {
|
|
1986
|
-
const emptyCell = new TableCell({ children: [new Paragraph("")] });
|
|
1987
|
-
const padCount = columns - rowCells.length;
|
|
1988
|
-
const padding = Array.from({ length: padCount }).map(() => emptyCell);
|
|
1989
|
-
rowCells = [...rowCells, ...padding];
|
|
1990
|
-
}
|
|
1991
|
-
return new TableRow({ children: rowCells });
|
|
1992
|
-
}),
|
|
1993
|
-
width: {
|
|
1994
|
-
size: 100,
|
|
1995
|
-
type: WidthType.PERCENTAGE
|
|
1996
|
-
}
|
|
1997
|
-
});
|
|
1998
|
-
}
|
|
1999
|
-
});
|
|
2000
|
-
}
|
|
2001
|
-
//#endregion
|
|
2002
|
-
//#region src/presets/academic.ts
|
|
2003
|
-
const academicPreset = {
|
|
2004
|
-
id: "academic",
|
|
2005
|
-
name: "Academic",
|
|
2006
|
-
config: {
|
|
2007
|
-
defaults: {
|
|
2008
|
-
image: {
|
|
2009
|
-
marginBottom: 12,
|
|
2010
|
-
marginTop: 12,
|
|
2011
|
-
textAlign: "center"
|
|
2012
|
-
},
|
|
2013
|
-
paragraph: {
|
|
2014
|
-
fontFamily: "Times New Roman",
|
|
2015
|
-
fontSize: 12,
|
|
2016
|
-
lineHeight: 2,
|
|
2017
|
-
marginBottom: 0,
|
|
2018
|
-
marginTop: 0,
|
|
2019
|
-
textAlign: "justify",
|
|
2020
|
-
textIndent: "24pt"
|
|
2021
|
-
}
|
|
2022
|
-
},
|
|
2023
|
-
styles: {
|
|
2024
|
-
h1: {
|
|
2025
|
-
color: "#000000",
|
|
2026
|
-
fontFamily: "Times New Roman",
|
|
2027
|
-
fontSize: 16,
|
|
2028
|
-
fontWeight: "bold",
|
|
2029
|
-
lineHeight: 1.5,
|
|
2030
|
-
marginBottom: 12,
|
|
2031
|
-
marginTop: 24,
|
|
2032
|
-
textAlign: "center"
|
|
2033
|
-
},
|
|
2034
|
-
h2: {
|
|
2035
|
-
color: "#000000",
|
|
2036
|
-
fontFamily: "Times New Roman",
|
|
2037
|
-
fontSize: 14,
|
|
2038
|
-
fontWeight: "bold",
|
|
2039
|
-
lineHeight: 1.5,
|
|
2040
|
-
marginBottom: 8,
|
|
2041
|
-
marginTop: 18
|
|
2042
|
-
},
|
|
2043
|
-
h3: {
|
|
2044
|
-
color: "#000000",
|
|
2045
|
-
fontFamily: "Times New Roman",
|
|
2046
|
-
fontSize: 12,
|
|
2047
|
-
fontWeight: "bold",
|
|
2048
|
-
lineHeight: 1.5,
|
|
2049
|
-
marginBottom: 6,
|
|
2050
|
-
marginTop: 14
|
|
2051
|
-
},
|
|
2052
|
-
h4: {
|
|
2053
|
-
color: "#000000",
|
|
2054
|
-
fontFamily: "Times New Roman",
|
|
2055
|
-
fontSize: 12,
|
|
2056
|
-
fontStyle: "italic",
|
|
2057
|
-
fontWeight: "bold",
|
|
2058
|
-
lineHeight: 1.5,
|
|
2059
|
-
marginBottom: 5,
|
|
2060
|
-
marginTop: 12
|
|
2061
|
-
},
|
|
2062
|
-
h5: {
|
|
2063
|
-
color: "#000000",
|
|
2064
|
-
fontFamily: "Times New Roman",
|
|
2065
|
-
fontSize: 11,
|
|
2066
|
-
fontWeight: "bold",
|
|
2067
|
-
lineHeight: 1.5,
|
|
2068
|
-
marginBottom: 4,
|
|
2069
|
-
marginTop: 10
|
|
2070
|
-
},
|
|
2071
|
-
h6: {
|
|
2072
|
-
color: "#333333",
|
|
2073
|
-
fontFamily: "Times New Roman",
|
|
2074
|
-
fontSize: 10,
|
|
2075
|
-
fontWeight: "bold",
|
|
2076
|
-
lineHeight: 1.5,
|
|
2077
|
-
marginBottom: 4,
|
|
2078
|
-
marginTop: 8
|
|
2079
|
-
},
|
|
2080
|
-
p: {
|
|
2081
|
-
fontFamily: "Times New Roman",
|
|
2082
|
-
fontSize: 12,
|
|
2083
|
-
lineHeight: 2,
|
|
2084
|
-
marginBottom: 0,
|
|
2085
|
-
marginTop: 0,
|
|
2086
|
-
textAlign: "justify",
|
|
2087
|
-
textIndent: "24pt"
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
},
|
|
2091
|
-
description: "Formal academic style with Times New Roman, double spacing, and justified text."
|
|
2092
|
-
};
|
|
2093
|
-
//#endregion
|
|
2094
|
-
//#region src/presets/classic.ts
|
|
2095
|
-
const classicPreset = {
|
|
2096
|
-
id: "classic",
|
|
2097
|
-
name: "Classic",
|
|
2098
|
-
config: {
|
|
2099
|
-
defaults: {
|
|
2100
|
-
image: {
|
|
2101
|
-
marginBottom: 8,
|
|
2102
|
-
marginTop: 8,
|
|
2103
|
-
textAlign: "center"
|
|
2104
|
-
},
|
|
2105
|
-
paragraph: {
|
|
2106
|
-
fontFamily: "SimSun",
|
|
2107
|
-
fontSize: 14,
|
|
2108
|
-
lineHeight: 1.5,
|
|
2109
|
-
marginBottom: 6,
|
|
2110
|
-
marginTop: 0,
|
|
2111
|
-
textIndent: "28pt"
|
|
2112
|
-
}
|
|
2113
|
-
},
|
|
2114
|
-
styles: {
|
|
2115
|
-
h1: {
|
|
2116
|
-
color: "#000000",
|
|
2117
|
-
fontFamily: "SimHei",
|
|
2118
|
-
fontSize: 22,
|
|
2119
|
-
fontWeight: "bold",
|
|
2120
|
-
lineHeight: 1.5,
|
|
2121
|
-
marginBottom: 12,
|
|
2122
|
-
marginTop: 20,
|
|
2123
|
-
textAlign: "center"
|
|
2124
|
-
},
|
|
2125
|
-
h2: {
|
|
2126
|
-
color: "#000000",
|
|
2127
|
-
fontFamily: "SimHei",
|
|
2128
|
-
fontSize: 16,
|
|
2129
|
-
fontWeight: "bold",
|
|
2130
|
-
lineHeight: 1.5,
|
|
2131
|
-
marginBottom: 8,
|
|
2132
|
-
marginTop: 16
|
|
2133
|
-
},
|
|
2134
|
-
h3: {
|
|
2135
|
-
color: "#000000",
|
|
2136
|
-
fontFamily: "KaiTi",
|
|
2137
|
-
fontSize: 16,
|
|
2138
|
-
fontWeight: "bold",
|
|
2139
|
-
lineHeight: 1.5,
|
|
2140
|
-
marginBottom: 6,
|
|
2141
|
-
marginTop: 12
|
|
2142
|
-
},
|
|
2143
|
-
h4: {
|
|
2144
|
-
color: "#000000",
|
|
2145
|
-
fontFamily: "KaiTi",
|
|
2146
|
-
fontSize: 14,
|
|
2147
|
-
fontWeight: "bold",
|
|
2148
|
-
lineHeight: 1.4,
|
|
2149
|
-
marginBottom: 5,
|
|
2150
|
-
marginTop: 10
|
|
2151
|
-
},
|
|
2152
|
-
h5: {
|
|
2153
|
-
color: "#000000",
|
|
2154
|
-
fontFamily: "SimSun",
|
|
2155
|
-
fontSize: 14,
|
|
2156
|
-
fontWeight: "bold",
|
|
2157
|
-
lineHeight: 1.4,
|
|
2158
|
-
marginBottom: 4,
|
|
2159
|
-
marginTop: 8
|
|
2160
|
-
},
|
|
2161
|
-
h6: {
|
|
2162
|
-
color: "#000000",
|
|
2163
|
-
fontFamily: "SimSun",
|
|
2164
|
-
fontSize: 12,
|
|
2165
|
-
fontWeight: "bold",
|
|
2166
|
-
lineHeight: 1.4,
|
|
2167
|
-
marginBottom: 4,
|
|
2168
|
-
marginTop: 8
|
|
2169
|
-
},
|
|
2170
|
-
p: {
|
|
2171
|
-
fontFamily: "SimSun",
|
|
2172
|
-
fontSize: 14,
|
|
2173
|
-
lineHeight: 1.5,
|
|
2174
|
-
marginBottom: 6,
|
|
2175
|
-
marginTop: 0,
|
|
2176
|
-
textIndent: "28pt"
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
},
|
|
2180
|
-
description: "Formal official-document style with SimHei headings, SimSun body, and two-character indent."
|
|
2181
|
-
};
|
|
2182
|
-
//#endregion
|
|
2183
|
-
//#region src/presets/modern.ts
|
|
2184
|
-
const modernPreset = {
|
|
2185
|
-
id: "modern",
|
|
2186
|
-
name: "Modern",
|
|
2187
|
-
config: {
|
|
2188
|
-
defaults: {
|
|
2189
|
-
image: {
|
|
2190
|
-
marginBottom: 10,
|
|
2191
|
-
marginTop: 10,
|
|
2192
|
-
textAlign: "center"
|
|
2193
|
-
},
|
|
2194
|
-
paragraph: {
|
|
2195
|
-
fontFamily: "Calibri",
|
|
2196
|
-
fontSize: 11,
|
|
2197
|
-
lineHeight: 1.5,
|
|
2198
|
-
marginBottom: 8,
|
|
2199
|
-
marginTop: 0
|
|
2200
|
-
}
|
|
2201
|
-
},
|
|
2202
|
-
styles: {
|
|
2203
|
-
h1: {
|
|
2204
|
-
borderBottom: {
|
|
2205
|
-
color: "#2E75B6",
|
|
2206
|
-
style: "single",
|
|
2207
|
-
width: "1.5pt"
|
|
2208
|
-
},
|
|
2209
|
-
color: "#1B2A4A",
|
|
2210
|
-
fontFamily: "Calibri",
|
|
2211
|
-
fontSize: 26,
|
|
2212
|
-
fontWeight: "bold",
|
|
2213
|
-
lineHeight: 1.3,
|
|
2214
|
-
marginBottom: 12,
|
|
2215
|
-
marginTop: 24
|
|
2216
|
-
},
|
|
2217
|
-
h2: {
|
|
2218
|
-
color: "#2E75B6",
|
|
2219
|
-
fontFamily: "Calibri",
|
|
2220
|
-
fontSize: 20,
|
|
2221
|
-
fontWeight: "bold",
|
|
2222
|
-
lineHeight: 1.3,
|
|
2223
|
-
marginBottom: 10,
|
|
2224
|
-
marginTop: 20
|
|
2225
|
-
},
|
|
2226
|
-
h3: {
|
|
2227
|
-
color: "#2E75B6",
|
|
2228
|
-
fontFamily: "Calibri",
|
|
2229
|
-
fontSize: 16,
|
|
2230
|
-
fontWeight: "bold",
|
|
2231
|
-
lineHeight: 1.35,
|
|
2232
|
-
marginBottom: 8,
|
|
2233
|
-
marginTop: 16
|
|
2234
|
-
},
|
|
2235
|
-
h4: {
|
|
2236
|
-
color: "#404040",
|
|
2237
|
-
fontFamily: "Calibri",
|
|
2238
|
-
fontSize: 14,
|
|
2239
|
-
fontWeight: "bold",
|
|
2240
|
-
lineHeight: 1.35,
|
|
2241
|
-
marginBottom: 6,
|
|
2242
|
-
marginTop: 12
|
|
2243
|
-
},
|
|
2244
|
-
h5: {
|
|
2245
|
-
color: "#404040",
|
|
2246
|
-
fontFamily: "Calibri",
|
|
2247
|
-
fontSize: 12,
|
|
2248
|
-
fontWeight: "bold",
|
|
2249
|
-
lineHeight: 1.35,
|
|
2250
|
-
marginBottom: 5,
|
|
2251
|
-
marginTop: 10
|
|
2252
|
-
},
|
|
2253
|
-
h6: {
|
|
2254
|
-
color: "#666666",
|
|
2255
|
-
fontFamily: "Calibri",
|
|
2256
|
-
fontSize: 11,
|
|
2257
|
-
fontWeight: "bold",
|
|
2258
|
-
lineHeight: 1.35,
|
|
2259
|
-
marginBottom: 4,
|
|
2260
|
-
marginTop: 8
|
|
2261
|
-
},
|
|
2262
|
-
p: {
|
|
2263
|
-
fontFamily: "Calibri",
|
|
2264
|
-
fontSize: 11,
|
|
2265
|
-
lineHeight: 1.5,
|
|
2266
|
-
marginBottom: 8,
|
|
2267
|
-
marginTop: 0
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
},
|
|
2271
|
-
description: "Clean business style with Calibri, blue accent headings, and generous spacing."
|
|
2272
|
-
};
|
|
2273
|
-
//#endregion
|
|
2274
|
-
//#region src/presets/index.ts
|
|
2275
|
-
/**
|
|
2276
|
-
* Style presets — reusable `DocxKitConfig` fragments for instant styling.
|
|
2277
|
-
*
|
|
2278
|
-
* Three built-in presets are provided:
|
|
2279
|
-
* - **Classic** — Formal official-document style (SimHei / SimSun)
|
|
2280
|
-
* - **Modern** — Clean business style with blue accents (Calibri)
|
|
2281
|
-
* - **Academic** — Formal thesis / paper style (Times New Roman)
|
|
2282
|
-
*
|
|
2283
|
-
* ## Usage
|
|
2284
|
-
*
|
|
2285
|
-
* ```ts
|
|
2286
|
-
* import { createDocx, modernPreset } from 'docx-kit'
|
|
2287
|
-
*
|
|
2288
|
-
* // Spread a preset into createDocx — user config wins on conflict
|
|
2289
|
-
* const doc = createDocx({
|
|
2290
|
-
* ...modernPreset.config,
|
|
2291
|
-
* metadata: { title: 'Quarterly Report' },
|
|
2292
|
-
* })
|
|
2293
|
-
* ```
|
|
2294
|
-
*
|
|
2295
|
-
* Or use `usePreset()` for lookup by ID:
|
|
2296
|
-
*
|
|
2297
|
-
* ```ts
|
|
2298
|
-
* import { usePreset } from 'docx-kit'
|
|
2299
|
-
*
|
|
2300
|
-
* const preset = usePreset('modern')
|
|
2301
|
-
* const doc = createDocx(preset!.config)
|
|
2302
|
-
* ```
|
|
2303
|
-
*
|
|
2304
|
-
* @module presets
|
|
2305
|
-
*/
|
|
2306
|
-
/** All built-in presets, keyed by ID. */
|
|
2307
|
-
const BUILTIN_PRESETS = new Map([
|
|
2308
|
-
[academicPreset.id, academicPreset],
|
|
2309
|
-
[classicPreset.id, classicPreset],
|
|
2310
|
-
[modernPreset.id, modernPreset]
|
|
2311
|
-
]);
|
|
2312
|
-
/** Ordered list of built-in presets (for UI selectors). */
|
|
2313
|
-
const PRESET_LIST = [
|
|
2314
|
-
classicPreset,
|
|
2315
|
-
modernPreset,
|
|
2316
|
-
academicPreset
|
|
2317
|
-
];
|
|
2318
|
-
/**
|
|
2319
|
-
* Look up a built-in preset by ID.
|
|
2320
|
-
*
|
|
2321
|
-
* @param id - — Preset identifier (`"classic"`, `"modern"`, or `"academic"`)
|
|
2322
|
-
* @returns The matching preset, or `undefined` if not found
|
|
2323
|
-
*
|
|
2324
|
-
* @example
|
|
2325
|
-
* ```ts
|
|
2326
|
-
* const preset = usePreset('modern')
|
|
2327
|
-
* const doc = createDocx(preset!.config)
|
|
2328
|
-
* ```
|
|
2329
|
-
*/
|
|
2330
|
-
function usePreset(id) {
|
|
2331
|
-
return BUILTIN_PRESETS.get(id);
|
|
2332
|
-
}
|
|
2333
|
-
//#endregion
|
|
2334
|
-
//#region src/node/dataUrl.ts
|
|
2335
|
-
/**
|
|
2336
|
-
* Node.js base64 data-URL decoder (uses `Buffer` from `node:buffer`).
|
|
2337
|
-
*
|
|
2338
|
-
* @module node/dataUrl
|
|
2339
|
-
*/
|
|
2340
|
-
/**
|
|
2341
|
-
* Decode a base64 data-URL to raw bytes using Node.js `Buffer`.
|
|
2342
|
-
*
|
|
2343
|
-
* Strips the `"data:*;base64,"` prefix and decodes via
|
|
2344
|
-
* `Buffer.from(base64, 'base64')`.
|
|
2345
|
-
*
|
|
2346
|
-
* @param dataUrl - — A base64 data-URI string (e.g. `"data:image/png;base64,iVBO..."`)
|
|
2347
|
-
* @returns Raw bytes as `Uint8Array`
|
|
2348
|
-
*
|
|
2349
|
-
* @example
|
|
2350
|
-
* ```ts
|
|
2351
|
-
* import { dataUrlToUint8Array } from 'docx-kit/node'
|
|
2352
|
-
*
|
|
2353
|
-
* const bytes = await dataUrlToUint8Array('data:image/png;base64,iVBORw...')
|
|
2354
|
-
* ```
|
|
2355
|
-
*/
|
|
2356
|
-
async function dataUrlToUint8Array(dataUrl) {
|
|
2357
|
-
const { Buffer } = await import("node:buffer");
|
|
2358
|
-
const base64 = dataUrl.split(",")[1];
|
|
2359
|
-
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
2360
|
-
}
|
|
2361
|
-
//#endregion
|
|
2362
|
-
export { DocxBuilder, DocxKitError, ERROR_CODES, PRESET_LIST, academicPreset, calloutPlugin, classicPreset, codeBlockPlugin, coverPagePlugin, createDocx, dataTablePlugin, dataUrlToUint8Array, definePlugin, defineStyles, echartsPlugin, meetingMinutesPlugin, modernPreset, pageNumberPlugin, propertyTablePlugin, qrcodePlugin, renderDocx, saveDocument, signatureBlockPlugin, timelinePlugin, usePreset, watermarkPlugin };
|