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