Initial commit
This commit is contained in:
333
skills/docx/references/docx-js-core.md
Executable file
333
skills/docx/references/docx-js-core.md
Executable file
@@ -0,0 +1,333 @@
|
||||
# docx-js API Reference
|
||||
|
||||
Complete API for creating .docx documents with the `docx` npm package. For advanced features (TOC details, footnotes, PDF conversion), see `docx-js-advanced.md`.
|
||||
|
||||
## Setup
|
||||
|
||||
```js
|
||||
const {
|
||||
Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
|
||||
ImageRun, PageBreak, Header, Footer, PageNumber, NumberFormat,
|
||||
AlignmentType, HeadingLevel, WidthType, BorderStyle, ShadingType,
|
||||
PageOrientation, TabStopType, TabStopPosition, ExternalHyperlink,
|
||||
InternalHyperlink, Bookmark, LevelFormat, TableOfContents,
|
||||
} = require("docx");
|
||||
const fs = require("fs");
|
||||
```
|
||||
|
||||
## Document Creation + Export
|
||||
|
||||
```js
|
||||
const doc = new Document({
|
||||
styles: { /* see Styles section */ },
|
||||
numbering: { config: [ /* see Lists section */ ] },
|
||||
sections: [{
|
||||
properties: {
|
||||
page: {
|
||||
size: { width: 11906, height: 16838 },
|
||||
margin: { top: 1417, bottom: 1417, left: 1701, right: 1417 },
|
||||
},
|
||||
},
|
||||
headers: { default: new Header({ children: [/* */] }) },
|
||||
footers: { default: new Footer({ children: [/* */] }) },
|
||||
children: [ /* Paragraphs, Tables, etc. */ ],
|
||||
}],
|
||||
});
|
||||
|
||||
const buffer = await Packer.toBuffer(doc);
|
||||
fs.writeFileSync("output.docx", buffer);
|
||||
```
|
||||
|
||||
## Paragraph + TextRun
|
||||
|
||||
```js
|
||||
new Paragraph({
|
||||
heading: HeadingLevel.HEADING_1, // or HEADING_2, HEADING_3
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
spacing: { before: 240, after: 120, line: 312 }, // 1.3x mandatory
|
||||
indent: { firstLine: 480 }, // 2-char CJK indent (480 SimSun / 420 YaHei)
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "Hello",
|
||||
bold: true,
|
||||
italics: true,
|
||||
size: 24, // 12pt = Xiao Si
|
||||
font: { ascii: "Calibri", eastAsia: "Microsoft YaHei" },
|
||||
color: "000000", // Pure black for Profile A; for Profile B use palette.body
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// Additional text formatting options
|
||||
new TextRun({ text: "Underlined", underline: { type: UnderlineType.SINGLE } })
|
||||
new TextRun({ text: "Highlighted", highlight: "yellow" })
|
||||
new TextRun({ text: "Strikethrough", strike: true })
|
||||
new TextRun({ text: "x²", superScript: true })
|
||||
new TextRun({ text: "H₂O", subScript: true })
|
||||
new SymbolRun({ char: "2022", font: "Symbol" }) // Bullet •
|
||||
```
|
||||
|
||||
## Table
|
||||
|
||||
**⚠️ CRITICAL**: Always set `margins` on TableCell (or at Table level for global default). Without margins, text touches borders.
|
||||
|
||||
**⚠️ CRITICAL**: Use `ShadingType.CLEAR` — never `ShadingType.SOLID` (causes black cells).
|
||||
|
||||
**⚠️ CRITICAL — Table Cross-Page Control**:
|
||||
- Header row MUST set `tableHeader: true` (auto-repeat header on page break)
|
||||
- All rows MUST set `cantSplit: true` (prevent row content split across pages)
|
||||
- Title paragraph before table MUST set `keepNext: true` (keep title with table)
|
||||
|
||||
```js
|
||||
// ⚠️ Title before table — keepNext keeps title with table
|
||||
new Paragraph({
|
||||
keepNext: true, // ← critical
|
||||
children: [new TextRun({ text: "Table 1 Feature Comparison", bold: true, size: 21 })],
|
||||
}),
|
||||
|
||||
new Table({
|
||||
width: { size: 100, type: WidthType.PERCENTAGE },
|
||||
borders: {
|
||||
top: { style: BorderStyle.SINGLE, size: 2, color: "9AA6B2" },
|
||||
bottom: { style: BorderStyle.SINGLE, size: 2, color: "9AA6B2" },
|
||||
left: { style: BorderStyle.NONE },
|
||||
right: { style: BorderStyle.NONE },
|
||||
insideHorizontal: { style: BorderStyle.SINGLE, size: 1, color: "D0D0D0" },
|
||||
insideVertical: { style: BorderStyle.NONE },
|
||||
},
|
||||
rows: [
|
||||
// ⚠️ Header row — tableHeader + cantSplit
|
||||
new TableRow({
|
||||
tableHeader: true, // auto-repeat on page break
|
||||
cantSplit: true, // prevent row split
|
||||
children: ["Header 1", "Header 2"].map(text =>
|
||||
new TableCell({
|
||||
children: [new Paragraph({ children: [new TextRun({ text, bold: true, size: 21 })] })],
|
||||
shading: { type: ShadingType.CLEAR, fill: "F1F5F9" },
|
||||
margins: { top: 60, bottom: 60, left: 120, right: 120 },
|
||||
width: { size: 50, type: WidthType.PERCENTAGE },
|
||||
})
|
||||
),
|
||||
}),
|
||||
// ⚠️ Data rows — cantSplit
|
||||
new TableRow({
|
||||
cantSplit: true, // prevent row split
|
||||
children: ["Data 1", "Data 2"].map(text =>
|
||||
new TableCell({
|
||||
children: [new Paragraph({ children: [new TextRun({ text, size: 21 })] })],
|
||||
margins: { top: 60, bottom: 60, left: 120, right: 120 },
|
||||
width: { size: 50, type: WidthType.PERCENTAGE },
|
||||
})
|
||||
),
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Column Widths
|
||||
|
||||
```js
|
||||
// Fixed widths (twips)
|
||||
width: { size: 3000, type: WidthType.DXA }
|
||||
// Percentage
|
||||
width: { size: 50, type: WidthType.PERCENTAGE }
|
||||
```
|
||||
|
||||
## ImageRun
|
||||
|
||||
**⚠️ CRITICAL**: Always include `type` parameter. Always preserve aspect ratio.
|
||||
|
||||
```js
|
||||
const imageBuffer = fs.readFileSync("chart.png");
|
||||
// Calculate dimensions preserving aspect ratio
|
||||
const displayWidth = 500;
|
||||
const aspectRatio = originalHeight / originalWidth;
|
||||
const displayHeight = Math.round(displayWidth * aspectRatio);
|
||||
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new ImageRun({
|
||||
data: imageBuffer,
|
||||
transformation: { width: displayWidth, height: displayHeight },
|
||||
type: "png", // REQUIRED: "png", "jpg", "gif", "bmp"
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## PageBreak
|
||||
|
||||
**⚠️ CRITICAL**: PageBreak MUST be inside a Paragraph. Standalone PageBreak crashes Word.
|
||||
|
||||
**⚠️ Best Practice**: Attach PageBreak to the end of a **paragraph with text content**. Avoid empty paragraph + PageBreak (may cause blank pages). If using multi-section structure, prefer section breaks over PageBreak.
|
||||
|
||||
```js
|
||||
// ✅ Recommended — PageBreak attached to content paragraph
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({ text: "End of section" }),
|
||||
new PageBreak()
|
||||
]
|
||||
})
|
||||
|
||||
// ✅ Acceptable — but prefer section breaks
|
||||
new Paragraph({ children: [new PageBreak()] })
|
||||
|
||||
// ✅ Best — use section breaks instead of PageBreak
|
||||
// Place content in different sections — auto page break
|
||||
```
|
||||
|
||||
## Headers & Footers + Page Numbers
|
||||
|
||||
```js
|
||||
headers: {
|
||||
default: new Header({
|
||||
children: [
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun({ text: "Document Title", size: 18, color: "888888" })],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
footers: {
|
||||
default: new Footer({
|
||||
children: [
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ children: [PageNumber.CURRENT], size: 18 }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
```
|
||||
|
||||
> ⚠️ **Denominator FORBIDDEN** — never use `PageNumber.TOTAL_PAGES` or "X / Y" format. Show only current page number.
|
||||
|
||||
## Styles Definition
|
||||
|
||||
The example below is for **Chinese documents** (default). For **English documents**, replace `font` with `"Times New Roman"` throughout.
|
||||
|
||||
```js
|
||||
styles: {
|
||||
default: {
|
||||
document: {
|
||||
run: {
|
||||
font: { ascii: "Calibri", eastAsia: "Microsoft YaHei" },
|
||||
size: 24, color: "000000", // Pure black for Profile A; for Profile B use palette.body
|
||||
},
|
||||
paragraph: {
|
||||
spacing: { line: 312 }, // 1.3x mandatory
|
||||
},
|
||||
},
|
||||
heading1: {
|
||||
run: { font: { ascii: "Calibri", eastAsia: "SimHei" }, size: 32, bold: true, color: "0B1220" },
|
||||
paragraph: { spacing: { before: 360, after: 160, line: 312 } },
|
||||
},
|
||||
heading2: {
|
||||
run: { font: { ascii: "Calibri", eastAsia: "SimHei" }, size: 28, bold: true, color: "0B1220" },
|
||||
paragraph: { spacing: { before: 240, after: 120, line: 312 } },
|
||||
},
|
||||
heading3: {
|
||||
run: { font: { ascii: "Calibri", eastAsia: "SimHei" }, size: 24, bold: true, color: "0B1220" },
|
||||
paragraph: { spacing: { before: 200, after: 100, line: 312 } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
||||
**⚠️ CRITICAL**: Each separate numbered list MUST use a unique `reference` name. Reusing the same reference causes numbering to continue instead of restarting.
|
||||
|
||||
```js
|
||||
// In Document numbering config
|
||||
numbering: {
|
||||
config: [
|
||||
{
|
||||
reference: "list-features", // unique name!
|
||||
levels: [{
|
||||
level: 0,
|
||||
format: LevelFormat.DECIMAL,
|
||||
text: "%1.",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } },
|
||||
}],
|
||||
},
|
||||
{
|
||||
reference: "list-benefits", // different name for second list!
|
||||
levels: [{ /* same config */ }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Usage in paragraphs
|
||||
new Paragraph({
|
||||
numbering: { reference: "list-features", level: 0 },
|
||||
children: [new TextRun({ text: "First item" })],
|
||||
})
|
||||
```
|
||||
|
||||
### Bullet Lists
|
||||
|
||||
```js
|
||||
new Paragraph({
|
||||
bullet: { level: 0 },
|
||||
children: [new TextRun({ text: "Bullet item" })],
|
||||
})
|
||||
```
|
||||
|
||||
## Hyperlinks
|
||||
|
||||
### External Link
|
||||
|
||||
```js
|
||||
new ExternalHyperlink({
|
||||
children: [new TextRun({ text: "Click here", style: "Hyperlink" })],
|
||||
link: "https://example.com",
|
||||
})
|
||||
```
|
||||
|
||||
### Internal Link (Bookmark)
|
||||
|
||||
```js
|
||||
// Define bookmark at target
|
||||
new Paragraph({
|
||||
children: [
|
||||
new Bookmark({ id: "section1", children: [new TextRun("Section 1")] }),
|
||||
],
|
||||
})
|
||||
|
||||
// Link to bookmark
|
||||
new InternalHyperlink({
|
||||
children: [new TextRun({ text: "Go to Section 1", style: "Hyperlink" })],
|
||||
anchor: "section1",
|
||||
})
|
||||
```
|
||||
## Table of Contents (TOC)
|
||||
|
||||
**→ See `references/toc.md` for the complete TOC reference.**
|
||||
|
||||
Quick reminder: (1) Add `TableOfContents` element + PageBreak, (2) Run `python3 "$DOCX_SCRIPTS/add_toc_placeholders.py" output.docx --auto`, (3) Check exit code.
|
||||
|
||||
## Tabs
|
||||
|
||||
```js
|
||||
new Paragraph({
|
||||
tabStops: [
|
||||
{ type: TabStopType.RIGHT, position: TabStopPosition.MAX },
|
||||
],
|
||||
children: [new TextRun("Left"), new TextRun("\t"), new TextRun("Right")]
|
||||
})
|
||||
```
|
||||
|
||||
## Constants Quick Reference
|
||||
|
||||
- **Underlines:** `SINGLE`, `DOUBLE`, `WAVY`, `DASH`
|
||||
- **Borders:** `SINGLE`, `DOUBLE`, `DASHED`, `DOTTED`
|
||||
- **Numbering:** `DECIMAL` (1,2,3), `UPPER_ROMAN` (I,II,III), `LOWER_LETTER` (a,b,c)
|
||||
- **Symbols:** `"2022"` (•), `"00A9"` (©), `"00AE"` (®), `"2122"` (™)
|
||||
|
||||
Reference in New Issue
Block a user