Files
mantle-ai-trader/skills/docx/references/docx-js-core.md
2026-06-06 05:21:10 +00:00

9.5 KiB
Executable File

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

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

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

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)
// ⚠️ 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

// 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.

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.

// ✅ 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

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.

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.

// 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

new Paragraph({
  bullet: { level: 0 },
  children: [new TextRun({ text: "Bullet item" })],
})
new ExternalHyperlink({
  children: [new TextRun({ text: "Click here", style: "Hyperlink" })],
  link: "https://example.com",
})
// 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

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" (™)