2821 lines
92 KiB
Markdown
2821 lines
92 KiB
Markdown
# OpenXML SDK 3.x Complete Reference Encyclopedia — Part 2
|
||
|
||
**Target:** DocumentFormat.OpenXml 3.x / .NET 8+ / C# 12
|
||
**Last Updated:** 2026-03-22
|
||
|
||
This document covers page setup, tables, headers/footers, section breaks, document properties, and printing/compatibility settings. Every code block is ready to copy-paste.
|
||
|
||
---
|
||
|
||
## Namespace Aliases Used Throughout
|
||
|
||
```csharp
|
||
using DocumentFormat.OpenXml;
|
||
using DocumentFormat.OpenXml.Packaging;
|
||
using DocumentFormat.OpenXml.Wordprocessing;
|
||
using A = DocumentFormat.OpenXml.Drawing;
|
||
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
|
||
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
|
||
```
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Page Setup](#1-page-setup)
|
||
2. [Tables — Comprehensive](#2-tables--comprehensive)
|
||
3. [Headers, Footers & Page Numbers](#3-headers-footers--page-numbers)
|
||
4. [Section Breaks & Multi-Section](#4-section-breaks--multi-section)
|
||
5. [Document Properties](#5-document-properties)
|
||
6. [Printing & Compatibility Settings](#6-printing--compatibility-settings)
|
||
|
||
---
|
||
|
||
## 1. Page Setup
|
||
|
||
Page setup is controlled via `SectionProperties`, which is placed as the **last child element** of `Body` for the final (or only) section, or inside `ParagraphProperties` for mid-document section breaks.
|
||
|
||
### 1.1 PageSize — Standard Paper Sizes
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// PAGE SIZE — STANDARD PAPER SIZES
|
||
// =============================================================================
|
||
// PageSize Width and Height are specified in DXA (twentieths of a point).
|
||
// 1 inch = 1440 DXA. 1 cm ≈ 567 DXA. 1 mm ≈ 56.7 DXA.
|
||
//
|
||
// Common paper sizes (portrait orientation):
|
||
// Letter: 12240 × 15840 (8.5" × 11")
|
||
// A4: 11906 × 16838 (210mm × 297mm)
|
||
// Legal: 12240 × 20160 (8.5" × 14")
|
||
// A3: 16838 × 23811 (297mm × 420mm)
|
||
// B5: 10318 × 14570 (182mm × 257mm)
|
||
// 16K: 10318 × 14570 (approximate, varies by region)
|
||
|
||
var sectionProps = new SectionProperties();
|
||
|
||
// --- Letter (US default) ---
|
||
sectionProps.AppendChild(new PageSize
|
||
{
|
||
Width = 12240U, // 8.5 inches
|
||
Height = 15840U // 11 inches
|
||
});
|
||
// Produces XML: <w:pgSz w:w="12240" w:h="15840" />
|
||
|
||
// --- A4 (ISO default) ---
|
||
var a4Size = new PageSize
|
||
{
|
||
Width = 11906U, // 210mm
|
||
Height = 16838U // 297mm
|
||
};
|
||
|
||
// --- Legal ---
|
||
var legalSize = new PageSize
|
||
{
|
||
Width = 12240U, // 8.5 inches
|
||
Height = 20160U // 14 inches
|
||
};
|
||
|
||
// --- A3 ---
|
||
var a3Size = new PageSize
|
||
{
|
||
Width = 16838U, // 297mm
|
||
Height = 23811U // 420mm
|
||
};
|
||
|
||
body.AppendChild(sectionProps);
|
||
```
|
||
|
||
### 1.2 PageOrientation — CRITICAL Landscape Handling
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// PAGE ORIENTATION — LANDSCAPE
|
||
// =============================================================================
|
||
// CRITICAL GOTCHA: Setting Orient = Landscape is NOT enough!
|
||
// You MUST ALSO swap Width and Height values. Word uses the numeric dimensions
|
||
// to determine actual page size; Orient only controls the print driver rotation.
|
||
//
|
||
// If you set Orient = Landscape but don't swap dimensions, Word will
|
||
// display portrait but print rotated — a confusing bug.
|
||
|
||
// --- Portrait (default, explicit) ---
|
||
var portraitSize = new PageSize
|
||
{
|
||
Width = 12240U,
|
||
Height = 15840U
|
||
// Orient is omitted for portrait (it's the default)
|
||
};
|
||
// Produces XML: <w:pgSz w:w="12240" w:h="15840" />
|
||
|
||
// --- Landscape — CORRECT way ---
|
||
var landscapeSize = new PageSize
|
||
{
|
||
Width = 15840U, // SWAPPED: was Height
|
||
Height = 12240U, // SWAPPED: was Width
|
||
Orient = PageOrientationValues.Landscape // AND set Orient
|
||
};
|
||
// Produces XML: <w:pgSz w:w="15840" w:h="12240" w:orient="landscape" />
|
||
|
||
// --- Helper method for safe orientation switching ---
|
||
static PageSize CreatePageSize(uint shortEdge, uint longEdge, bool landscape)
|
||
{
|
||
return landscape
|
||
? new PageSize
|
||
{
|
||
Width = longEdge, // Long edge becomes width
|
||
Height = shortEdge, // Short edge becomes height
|
||
Orient = PageOrientationValues.Landscape
|
||
}
|
||
: new PageSize
|
||
{
|
||
Width = shortEdge,
|
||
Height = longEdge
|
||
};
|
||
}
|
||
|
||
// Usage:
|
||
var letterLandscape = CreatePageSize(12240, 15840, landscape: true);
|
||
var a4Portrait = CreatePageSize(11906, 16838, landscape: false);
|
||
```
|
||
|
||
### 1.3 PageMargin — Common Presets
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// PAGE MARGIN — ALL PROPERTIES
|
||
// =============================================================================
|
||
// All margin values are in DXA (twentieths of a point). 1 inch = 1440 DXA.
|
||
//
|
||
// Properties:
|
||
// Top, Bottom — signed Int32 (can be negative for overlap)
|
||
// Left, Right — unsigned UInt32
|
||
// Header, Footer — unsigned UInt32 (distance from page edge to header/footer)
|
||
// Gutter — unsigned UInt32 (extra margin for binding; added to Left
|
||
// unless GutterAtTop is set in Settings)
|
||
|
||
// --- Normal / Standard (1" all around) ---
|
||
var normalMargins = new PageMargin
|
||
{
|
||
Top = 1440, // 1 inch
|
||
Bottom = 1440, // 1 inch
|
||
Left = 1440U, // 1 inch
|
||
Right = 1440U, // 1 inch
|
||
Header = 720U, // 0.5 inch (distance from page edge)
|
||
Footer = 720U, // 0.5 inch
|
||
Gutter = 0U // No binding gutter
|
||
};
|
||
// Produces XML:
|
||
// <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
|
||
// w:header="720" w:footer="720" w:gutter="0" />
|
||
|
||
// --- Narrow (0.5" all around) ---
|
||
var narrowMargins = new PageMargin
|
||
{
|
||
Top = 720,
|
||
Bottom = 720,
|
||
Left = 720U,
|
||
Right = 720U,
|
||
Header = 720U,
|
||
Footer = 720U,
|
||
Gutter = 0U
|
||
};
|
||
|
||
// --- Moderate (1" top/bottom, 0.75" left/right) ---
|
||
var moderateMargins = new PageMargin
|
||
{
|
||
Top = 1440,
|
||
Bottom = 1440,
|
||
Left = 1080U, // 0.75 inch
|
||
Right = 1080U, // 0.75 inch
|
||
Header = 720U,
|
||
Footer = 720U,
|
||
Gutter = 0U
|
||
};
|
||
|
||
// --- Wide (1" top/bottom, 2" left/right) ---
|
||
var wideMargins = new PageMargin
|
||
{
|
||
Top = 1440,
|
||
Bottom = 1440,
|
||
Left = 2880U, // 2 inches
|
||
Right = 2880U, // 2 inches
|
||
Header = 720U,
|
||
Footer = 720U,
|
||
Gutter = 0U
|
||
};
|
||
|
||
// --- Chinese Government Document 公文 standard (GB/T 9704-2012) ---
|
||
// Top: 37mm (2098 DXA), Bottom: 35mm (1984 DXA)
|
||
// Left: 28mm (1588 DXA), Right: 26mm (1474 DXA)
|
||
var chineseGovMargins = new PageMargin
|
||
{
|
||
Top = 2098, // 37mm
|
||
Bottom = 1984, // 35mm
|
||
Left = 1588U, // 28mm
|
||
Right = 1474U, // 26mm
|
||
Header = 851U, // 15mm
|
||
Footer = 567U, // 10mm (approx)
|
||
Gutter = 0U
|
||
};
|
||
|
||
// --- With binding gutter (e.g., for book printing) ---
|
||
var gutterMargins = new PageMargin
|
||
{
|
||
Top = 1440,
|
||
Bottom = 1440,
|
||
Left = 1440U,
|
||
Right = 1440U,
|
||
Header = 720U,
|
||
Footer = 720U,
|
||
Gutter = 720U // 0.5 inch added to left margin for binding
|
||
};
|
||
```
|
||
|
||
### 1.4 PageBorders
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// PAGE BORDERS
|
||
// =============================================================================
|
||
// PageBorders defines borders around the entire page.
|
||
// OffsetFrom controls whether border distance is from page edge or text margin.
|
||
// PageBorderOffsetValues.Page = from page edge
|
||
// PageBorderOffsetValues.Text = from text margin
|
||
//
|
||
// Each border: Val (style), Size (eighth-points: 4=0.5pt, 8=1pt, 12=1.5pt),
|
||
// Color (hex RGB), Space (distance in points from text/page edge)
|
||
|
||
var pageBorders = new PageBorders
|
||
{
|
||
OffsetFrom = PageBorderOffsetValues.Page
|
||
};
|
||
|
||
// Top border: single line, 1pt, black
|
||
pageBorders.AppendChild(new TopBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 8U, // 8 eighth-points = 1pt
|
||
Color = "000000",
|
||
Space = 24U // 24 points from page edge
|
||
});
|
||
|
||
// Bottom border
|
||
pageBorders.AppendChild(new BottomBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 8U,
|
||
Color = "000000",
|
||
Space = 24U
|
||
});
|
||
|
||
// Left border
|
||
pageBorders.AppendChild(new LeftBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 8U,
|
||
Color = "000000",
|
||
Space = 24U
|
||
});
|
||
|
||
// Right border
|
||
pageBorders.AppendChild(new RightBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 8U,
|
||
Color = "000000",
|
||
Space = 24U
|
||
});
|
||
|
||
// --- Double border example ---
|
||
var doubleBorder = new TopBorder
|
||
{
|
||
Val = BorderValues.Double, // Double line
|
||
Size = 4U, // 0.5pt per line
|
||
Color = "0000FF", // Blue
|
||
Space = 24U
|
||
};
|
||
|
||
// --- Common BorderValues ---
|
||
// Single, Double, Triple, DotDash, Dashed, Dotted, Thick, ThinThickSmallGap,
|
||
// ThickThinSmallGap, Wave, DoubleWave, BasicBlackDots, None
|
||
|
||
sectionProps.AppendChild(pageBorders);
|
||
```
|
||
|
||
### 1.5 Columns — Multi-Column Layout
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// COLUMNS — MULTI-COLUMN LAYOUTS
|
||
// =============================================================================
|
||
// Columns element defines the column layout within a section.
|
||
// Space is measured in DXA. EqualWidth=true makes all columns equal.
|
||
|
||
// --- 2 equal columns ---
|
||
var twoCols = new Columns
|
||
{
|
||
EqualWidth = true,
|
||
ColumnCount = 2,
|
||
Space = "720" // 0.5 inch gap between columns
|
||
};
|
||
// Produces XML: <w:cols w:num="2" w:space="720" w:equalWidth="1" />
|
||
|
||
// --- 3 equal columns with separator ---
|
||
var threeColsSep = new Columns
|
||
{
|
||
EqualWidth = true,
|
||
ColumnCount = 3,
|
||
Space = "360", // 0.25 inch gap
|
||
Separator = true // Vertical line between columns
|
||
};
|
||
// Produces XML: <w:cols w:num="3" w:space="360" w:equalWidth="1" w:sep="1" />
|
||
|
||
// --- Custom unequal columns (e.g., 2/3 + 1/3 of content width) ---
|
||
// When using unequal widths, EqualWidth must be false and you define
|
||
// each column explicitly with Column elements.
|
||
// For Letter page with 1" margins: content width = 12240 - 1440 - 1440 = 9360 DXA
|
||
// Column 1: 6000 DXA wide, 360 DXA space after
|
||
// Column 2: 3000 DXA wide (6000 + 360 + 3000 = 9360)
|
||
|
||
var unequalCols = new Columns { EqualWidth = false };
|
||
unequalCols.AppendChild(new Column
|
||
{
|
||
Width = "6000",
|
||
Space = "360"
|
||
});
|
||
unequalCols.AppendChild(new Column
|
||
{
|
||
Width = "3000"
|
||
// No Space on last column
|
||
});
|
||
// Produces XML:
|
||
// <w:cols w:equalWidth="0">
|
||
// <w:col w:w="6000" w:space="360" />
|
||
// <w:col w:w="3000" />
|
||
// </w:cols>
|
||
|
||
sectionProps.AppendChild(unequalCols);
|
||
```
|
||
|
||
### 1.6 LineNumbering
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// LINE NUMBERING
|
||
// =============================================================================
|
||
// LineNumbering adds line numbers in the margin. Useful for legal/academic docs.
|
||
// CountBy = show every Nth number (1 = every line, 5 = every 5th)
|
||
// Start = starting number
|
||
// Distance = distance from text in DXA
|
||
// Restart: NewPage, NewSection, Continuous
|
||
|
||
var lineNums = new LineNumbering
|
||
{
|
||
CountBy = 5, // Show every 5th line number
|
||
Start = 1, // Start at line 1
|
||
Distance = 360U, // 0.25 inch from text
|
||
Restart = LineNumberRestartValues.NewPage // Restart each page
|
||
};
|
||
// Produces XML:
|
||
// <w:lnNumType w:countBy="5" w:start="1" w:distance="360" w:restart="newPage" />
|
||
|
||
sectionProps.AppendChild(lineNums);
|
||
```
|
||
|
||
### 1.7 DocGrid — CJK Document Grid
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// DOCUMENT GRID — CJK LAYOUT
|
||
// =============================================================================
|
||
// DocGrid specifies a document grid (character grid) primarily for CJK documents.
|
||
// Type: Default (no grid), Lines (line grid), LinesAndChars (line+char grid),
|
||
// SnapToChars (snap to character grid)
|
||
// LinePitch = distance between lines in DXA (e.g., 312 for standard Chinese docs)
|
||
// CharacterSpace = extra spacing between characters in DXA
|
||
|
||
// --- Chinese document grid: 28 lines × 28 chars per line (common for 公文) ---
|
||
var cjkGrid = new DocGrid
|
||
{
|
||
Type = DocGridValues.LinesAndChars,
|
||
LinePitch = 579, // DXA between lines (≈ 28 lines on A4 with std margins)
|
||
CharacterSpace = 0 // Default character spacing
|
||
};
|
||
// Produces XML:
|
||
// <w:docGrid w:type="linesAndChars" w:linePitch="579" w:charSpace="0" />
|
||
|
||
// --- Simple line-only grid ---
|
||
var lineGrid = new DocGrid
|
||
{
|
||
Type = DocGridValues.Lines,
|
||
LinePitch = 360 // Standard line pitch
|
||
};
|
||
|
||
sectionProps.AppendChild(cjkGrid);
|
||
```
|
||
|
||
### 1.8 VerticalTextAlignmentOnPage
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// VERTICAL TEXT ALIGNMENT ON PAGE
|
||
// =============================================================================
|
||
// Controls vertical alignment of text within the page (above bottom margin).
|
||
// Values: Top (default), Center, Both (justified), Bottom
|
||
|
||
var vertAlign = new VerticalTextAlignmentOnPage
|
||
{
|
||
Val = VerticalJustificationValues.Center
|
||
};
|
||
// Produces XML: <w:vAlign w:val="center" />
|
||
// Use case: Title pages, certificate pages
|
||
|
||
sectionProps.AppendChild(vertAlign);
|
||
```
|
||
|
||
### 1.9 Complete Page Setup Example
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// COMPLETE PAGE SETUP — PUTTING IT ALL TOGETHER
|
||
// =============================================================================
|
||
// A4 portrait, moderate margins, 2-column layout with separator
|
||
|
||
using var doc = WordprocessingDocument.Create(
|
||
"PageSetupDemo.docx", WordprocessingDocumentType.Document);
|
||
|
||
var mainPart = doc.MainDocumentPart!;
|
||
var body = mainPart.Document.Body!;
|
||
|
||
// Add content paragraphs ...
|
||
body.AppendChild(new Paragraph(
|
||
new Run(new Text("This is a two-column document on A4 paper."))));
|
||
|
||
// Section properties — MUST be last child of Body
|
||
var sectPr = new SectionProperties();
|
||
|
||
// Page size: A4 portrait
|
||
sectPr.AppendChild(new PageSize
|
||
{
|
||
Width = 11906U,
|
||
Height = 16838U
|
||
});
|
||
|
||
// Margins: moderate
|
||
sectPr.AppendChild(new PageMargin
|
||
{
|
||
Top = 1440, Bottom = 1440,
|
||
Left = 1080U, Right = 1080U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
});
|
||
|
||
// 2-column with separator
|
||
sectPr.AppendChild(new Columns
|
||
{
|
||
EqualWidth = true,
|
||
ColumnCount = 2,
|
||
Space = "720",
|
||
Separator = true
|
||
});
|
||
|
||
// Line grid for CJK
|
||
sectPr.AppendChild(new DocGrid
|
||
{
|
||
Type = DocGridValues.Lines,
|
||
LinePitch = 312
|
||
});
|
||
|
||
body.AppendChild(sectPr);
|
||
mainPart.Document.Save();
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Tables — Comprehensive
|
||
|
||
### 2.1 TableProperties — Width, Layout, Alignment, Indent
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE PROPERTIES — WIDTH, LAYOUT, ALIGNMENT
|
||
// =============================================================================
|
||
// Table width can be specified in three ways:
|
||
// Pct: percentage of page width. 5000 = 100%. (multiply % by 50)
|
||
// Dxa: absolute width in DXA (twentieths of a point). 1 inch = 1440 DXA.
|
||
// Auto: table auto-sizes to content.
|
||
//
|
||
// TableLayout:
|
||
// Fixed: columns maintain exact widths. Required for complex cell sizing.
|
||
// Autofit: columns adjust to content (default).
|
||
//
|
||
// TableJustification: Left (default), Center, Right
|
||
|
||
var table = new Table();
|
||
|
||
var tblProps = new TableProperties();
|
||
|
||
// --- Width: 100% of page ---
|
||
tblProps.AppendChild(new TableWidth
|
||
{
|
||
Width = "5000", // 5000 = 100%
|
||
Type = TableWidthUnitValues.Pct
|
||
});
|
||
// Produces XML: <w:tblW w:w="5000" w:type="pct" />
|
||
|
||
// --- Width: fixed DXA ---
|
||
// var tblWidth = new TableWidth { Width = "9360", Type = TableWidthUnitValues.Dxa };
|
||
|
||
// --- Width: auto ---
|
||
// var tblWidth = new TableWidth { Width = "0", Type = TableWidthUnitValues.Auto };
|
||
|
||
// Layout: fixed column widths
|
||
tblProps.AppendChild(new TableLayout
|
||
{
|
||
Type = TableLayoutValues.Fixed
|
||
});
|
||
|
||
// Center the table on the page
|
||
tblProps.AppendChild(new TableJustification
|
||
{
|
||
Val = TableRowAlignmentValues.Center
|
||
});
|
||
|
||
// Table indent (left offset when not centered) — in DXA
|
||
// tblProps.AppendChild(new TableIndentation
|
||
// {
|
||
// Width = 720, // 0.5 inch indent
|
||
// Type = TableWidthUnitValues.Dxa
|
||
// });
|
||
|
||
table.AppendChild(tblProps);
|
||
```
|
||
|
||
### 2.2 TableBorders — All Border Properties
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE BORDERS
|
||
// =============================================================================
|
||
// TableBorders defines default borders for the entire table.
|
||
// Each border has:
|
||
// Val — BorderValues enum (Single, Double, Dotted, Dashed, None, etc.)
|
||
// Size — in eighth-points: 4 = 0.5pt, 8 = 1pt, 12 = 1.5pt, 16 = 2pt, 24 = 3pt
|
||
// Color — hex RGB string (e.g., "000000" for black, "FF0000" for red)
|
||
// Space — space between border and content in points
|
||
//
|
||
// Border types: TopBorder, BottomBorder, LeftBorder, RightBorder,
|
||
// InsideHorizontalBorder, InsideVerticalBorder
|
||
|
||
var tblBorders = new TableBorders();
|
||
|
||
// Top border: single, 1pt, black
|
||
tblBorders.AppendChild(new TopBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 8U, // 1pt (8 eighth-points)
|
||
Color = "000000",
|
||
Space = 0U
|
||
});
|
||
|
||
// Bottom border
|
||
tblBorders.AppendChild(new BottomBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 8U,
|
||
Color = "000000",
|
||
Space = 0U
|
||
});
|
||
|
||
// Left border
|
||
tblBorders.AppendChild(new LeftBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 8U,
|
||
Color = "000000",
|
||
Space = 0U
|
||
});
|
||
|
||
// Right border
|
||
tblBorders.AppendChild(new RightBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 8U,
|
||
Color = "000000",
|
||
Space = 0U
|
||
});
|
||
|
||
// Inside horizontal borders (between rows)
|
||
tblBorders.AppendChild(new InsideHorizontalBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 4U, // 0.5pt — thinner than outer borders
|
||
Color = "000000",
|
||
Space = 0U
|
||
});
|
||
|
||
// Inside vertical borders (between columns)
|
||
tblBorders.AppendChild(new InsideVerticalBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 4U,
|
||
Color = "000000",
|
||
Space = 0U
|
||
});
|
||
|
||
// Produces XML:
|
||
// <w:tblBorders>
|
||
// <w:top w:val="single" w:sz="8" w:color="000000" w:space="0" />
|
||
// <w:bottom w:val="single" w:sz="8" w:color="000000" w:space="0" />
|
||
// <w:left w:val="single" w:sz="8" w:color="000000" w:space="0" />
|
||
// <w:right w:val="single" w:sz="8" w:color="000000" w:space="0" />
|
||
// <w:insideH w:val="single" w:sz="4" w:color="000000" w:space="0" />
|
||
// <w:insideV w:val="single" w:sz="4" w:color="000000" w:space="0" />
|
||
// </w:tblBorders>
|
||
|
||
tblProps.AppendChild(tblBorders);
|
||
```
|
||
|
||
### 2.3 TableGrid — Column Definitions
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE GRID — COLUMN WIDTH DEFINITIONS
|
||
// =============================================================================
|
||
// TableGrid defines the column structure of the table. Each GridColumn
|
||
// specifies the width in DXA. The number of GridColumn elements determines
|
||
// the number of columns.
|
||
//
|
||
// IMPORTANT: GridColumn widths should sum to the table width.
|
||
// For a 100% table on Letter with 1" margins: 12240 - 1440 - 1440 = 9360 DXA
|
||
|
||
var tableGrid = new TableGrid();
|
||
|
||
// 3 columns: 3000 + 3000 + 3360 = 9360 DXA
|
||
tableGrid.AppendChild(new GridColumn { Width = "3000" });
|
||
tableGrid.AppendChild(new GridColumn { Width = "3000" });
|
||
tableGrid.AppendChild(new GridColumn { Width = "3360" });
|
||
|
||
// Produces XML:
|
||
// <w:tblGrid>
|
||
// <w:gridCol w:w="3000" />
|
||
// <w:gridCol w:w="3000" />
|
||
// <w:gridCol w:w="3360" />
|
||
// </w:tblGrid>
|
||
|
||
table.AppendChild(tableGrid);
|
||
```
|
||
|
||
### 2.4 TableCellProperties — Width, Alignment, Direction, Shading
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE CELL PROPERTIES
|
||
// =============================================================================
|
||
// TableCellProperties controls individual cell appearance.
|
||
|
||
var cellProps = new TableCellProperties();
|
||
|
||
// Cell width — should match the GridColumn width
|
||
cellProps.AppendChild(new TableCellWidth
|
||
{
|
||
Width = "3000",
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
|
||
// Vertical alignment within cell: Top (default), Center, Bottom
|
||
cellProps.AppendChild(new TableCellVerticalAlignment
|
||
{
|
||
Val = TableVerticalAlignmentValues.Center
|
||
});
|
||
|
||
// Text direction (for vertical text in CJK, etc.)
|
||
// Values: LefToRight (default), TopToBottom (text rotated 90° CW),
|
||
// TopToBottomV (vertical CJK), BottomToTopV
|
||
cellProps.AppendChild(new TextDirection
|
||
{
|
||
Val = TextDirectionValues.TopToBottom
|
||
});
|
||
|
||
// NoWrap — prevents text from wrapping within the cell
|
||
cellProps.AppendChild(new NoWrap());
|
||
|
||
// Cell shading (background color)
|
||
cellProps.AppendChild(new Shading
|
||
{
|
||
Val = ShadingPatternValues.Clear,
|
||
Color = "auto",
|
||
Fill = "D9E2F3" // Light blue background
|
||
});
|
||
// Produces XML: <w:shd w:val="clear" w:color="auto" w:fill="D9E2F3" />
|
||
```
|
||
|
||
### 2.5 TableCellMargin — Table-Level Default vs Per-Cell Override
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE CELL MARGINS
|
||
// =============================================================================
|
||
// There are TWO levels of cell margin control:
|
||
// 1. Table-level default: TableCellMarginDefault (inside TableProperties)
|
||
// 2. Per-cell override: TableCellMargin (inside TableCellProperties)
|
||
//
|
||
// All values are in DXA. Default Word cell margins are approximately:
|
||
// Top: 0, Bottom: 0, Left: 108 (0.075"), Right: 108
|
||
|
||
// --- Table-level default margins ---
|
||
var defaultMargins = new TableCellMarginDefault();
|
||
defaultMargins.AppendChild(new TopMargin
|
||
{
|
||
Width = "72", // ~0.05 inch
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
defaultMargins.AppendChild(new BottomMargin
|
||
{
|
||
Width = "72",
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
defaultMargins.AppendChild(new StartMargin
|
||
{
|
||
Width = "108", // ~0.075 inch (default)
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
defaultMargins.AppendChild(new EndMargin
|
||
{
|
||
Width = "108",
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
|
||
tblProps.AppendChild(defaultMargins);
|
||
// Produces XML:
|
||
// <w:tblCellMar>
|
||
// <w:top w:w="72" w:type="dxa" />
|
||
// <w:bottom w:w="72" w:type="dxa" />
|
||
// <w:start w:w="108" w:type="dxa" />
|
||
// <w:end w:w="108" w:type="dxa" />
|
||
// </w:tblCellMar>
|
||
|
||
// --- Per-cell override (larger padding for a specific cell) ---
|
||
var cellMarginOverride = new TableCellMargin();
|
||
cellMarginOverride.AppendChild(new TopMargin
|
||
{
|
||
Width = "144", // 0.1 inch
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
cellMarginOverride.AppendChild(new BottomMargin
|
||
{
|
||
Width = "144",
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
cellMarginOverride.AppendChild(new StartMargin
|
||
{
|
||
Width = "216", // 0.15 inch
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
cellMarginOverride.AppendChild(new EndMargin
|
||
{
|
||
Width = "216",
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
|
||
cellProps.AppendChild(cellMarginOverride);
|
||
```
|
||
|
||
### 2.6 Row Height
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE ROW HEIGHT
|
||
// =============================================================================
|
||
// TableRowHeight is set inside TableRowProperties.
|
||
// Val = height in DXA
|
||
// HeightRuleType:
|
||
// Exact: row is exactly this height (content may be clipped)
|
||
// AtLeast: row is at least this height, grows for content (default behavior)
|
||
// Auto: height determined by content
|
||
|
||
var rowProps = new TableRowProperties();
|
||
|
||
// Row height: at least 0.5 inch
|
||
rowProps.AppendChild(new TableRowHeight
|
||
{
|
||
Val = 720U, // 0.5 inch in DXA
|
||
HeightRule = HeightRuleValues.AtLeast
|
||
});
|
||
// Produces XML: <w:trHeight w:val="720" w:hRule="atLeast" />
|
||
|
||
// Exact height (content may clip):
|
||
// rowProps.AppendChild(new TableRowHeight
|
||
// {
|
||
// Val = 720U,
|
||
// HeightRule = HeightRuleValues.Exact
|
||
// });
|
||
|
||
var row = new TableRow();
|
||
row.AppendChild(rowProps);
|
||
```
|
||
|
||
### 2.7 Header Row Repeat (Repeat on Every Page)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// HEADER ROW REPEAT
|
||
// =============================================================================
|
||
// TableHeader on TableRowProperties marks a row to repeat at the top
|
||
// of each page when the table spans multiple pages.
|
||
// IMPORTANT: Only works for rows at the TOP of the table (contiguous from first row).
|
||
// You cannot repeat a row in the middle of a table.
|
||
|
||
var headerRowProps = new TableRowProperties();
|
||
headerRowProps.AppendChild(new TableHeader()); // No value needed — presence = true
|
||
|
||
// Produces XML:
|
||
// <w:trPr>
|
||
// <w:tblHeader />
|
||
// </w:trPr>
|
||
|
||
var headerRow = new TableRow();
|
||
headerRow.AppendChild(headerRowProps);
|
||
// ... add cells to headerRow ...
|
||
```
|
||
|
||
### 2.8 Per-Cell Border Override
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// PER-CELL BORDER OVERRIDE
|
||
// =============================================================================
|
||
// TableCellBorders inside TableCellProperties overrides table-level borders
|
||
// for a specific cell. Useful for special formatting, merging visual areas, etc.
|
||
|
||
var cellBorders = new TableCellBorders();
|
||
|
||
// Remove bottom border on this cell
|
||
cellBorders.AppendChild(new BottomBorder
|
||
{
|
||
Val = BorderValues.None,
|
||
Size = 0U,
|
||
Color = "auto",
|
||
Space = 0U
|
||
});
|
||
|
||
// Add thick right border
|
||
cellBorders.AppendChild(new RightBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 24U, // 3pt thick
|
||
Color = "FF0000", // Red
|
||
Space = 0U
|
||
});
|
||
|
||
// Produces XML:
|
||
// <w:tcBorders>
|
||
// <w:bottom w:val="none" w:sz="0" w:color="auto" w:space="0" />
|
||
// <w:right w:val="single" w:sz="24" w:color="FF0000" w:space="0" />
|
||
// </w:tcBorders>
|
||
|
||
cellProps.AppendChild(cellBorders);
|
||
```
|
||
|
||
### 2.9 Horizontal Merge (GridSpan)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// HORIZONTAL MERGE — GRIDSPAN
|
||
// =============================================================================
|
||
// To merge cells horizontally, use GridSpan on the first cell's properties.
|
||
// The cell spans across multiple grid columns. You do NOT add extra cells
|
||
// for the spanned columns — only one cell covers the span.
|
||
//
|
||
// Example: 3-column table, first row merges columns 1+2
|
||
|
||
var mergedRow = new TableRow();
|
||
|
||
// Cell spanning columns 1 and 2
|
||
var spanCell = new TableCell();
|
||
var spanCellProps = new TableCellProperties();
|
||
spanCellProps.AppendChild(new GridSpan { Val = 2 }); // Span 2 columns
|
||
spanCellProps.AppendChild(new TableCellWidth
|
||
{
|
||
Width = "6000", // Combined width: 3000 + 3000
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
spanCell.AppendChild(spanCellProps);
|
||
spanCell.AppendChild(new Paragraph(new Run(new Text("Spans columns 1-2"))));
|
||
mergedRow.AppendChild(spanCell);
|
||
|
||
// Cell in column 3 (normal)
|
||
var normalCell = new TableCell();
|
||
normalCell.AppendChild(new TableCellProperties(
|
||
new TableCellWidth { Width = "3360", Type = TableWidthUnitValues.Dxa }));
|
||
normalCell.AppendChild(new Paragraph(new Run(new Text("Column 3"))));
|
||
mergedRow.AppendChild(normalCell);
|
||
|
||
// Produces XML for the merged cell:
|
||
// <w:tc>
|
||
// <w:tcPr>
|
||
// <w:gridSpan w:val="2" />
|
||
// <w:tcW w:w="6000" w:type="dxa" />
|
||
// </w:tcPr>
|
||
// <w:p><w:r><w:t>Spans columns 1-2</w:t></w:r></w:p>
|
||
// </w:tc>
|
||
```
|
||
|
||
### 2.10 Vertical Merge
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// VERTICAL MERGE
|
||
// =============================================================================
|
||
// To merge cells vertically across rows, use VerticalMerge:
|
||
// First row: VerticalMerge with Val = Restart (starts the merge)
|
||
// Subsequent rows: VerticalMerge with Val = Continue (or omit Val — Continue is default)
|
||
//
|
||
// Each row still needs the cell placeholder, but continue cells should contain
|
||
// an empty paragraph only.
|
||
|
||
// Row 1: start of vertical merge
|
||
var vRow1 = new TableRow();
|
||
var vCell1 = new TableCell();
|
||
var vCellProps1 = new TableCellProperties();
|
||
vCellProps1.AppendChild(new VerticalMerge { Val = MergedCellValues.Restart });
|
||
vCellProps1.AppendChild(new TableCellWidth { Width = "3000", Type = TableWidthUnitValues.Dxa });
|
||
vCell1.AppendChild(vCellProps1);
|
||
vCell1.AppendChild(new Paragraph(new Run(new Text("Merged vertically"))));
|
||
vRow1.AppendChild(vCell1);
|
||
// ... add remaining cells in row 1
|
||
|
||
// Row 2: continue the vertical merge
|
||
var vRow2 = new TableRow();
|
||
var vCell2 = new TableCell();
|
||
var vCellProps2 = new TableCellProperties();
|
||
vCellProps2.AppendChild(new VerticalMerge()); // Val omitted = Continue
|
||
vCellProps2.AppendChild(new TableCellWidth { Width = "3000", Type = TableWidthUnitValues.Dxa });
|
||
vCell2.AppendChild(vCellProps2);
|
||
vCell2.AppendChild(new Paragraph()); // Empty paragraph required — cell must have content
|
||
vRow2.AppendChild(vCell2);
|
||
// ... add remaining cells in row 2
|
||
|
||
// Row 3: if no VerticalMerge, the merge stops and this cell is independent.
|
||
|
||
// Produces XML:
|
||
// Row 1 cell: <w:tcPr><w:vMerge w:val="restart" /></w:tcPr>
|
||
// Row 2 cell: <w:tcPr><w:vMerge /></w:tcPr> (continue is the default)
|
||
```
|
||
|
||
### 2.11 Nested Tables
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// NESTED TABLES
|
||
// =============================================================================
|
||
// A table can be nested inside a table cell. Simply add a Table element
|
||
// as a child of a TableCell (alongside the required Paragraph).
|
||
// IMPORTANT: Every TableCell MUST contain at least one Paragraph, even if
|
||
// it also contains a nested table. The Paragraph should come AFTER the table.
|
||
|
||
var outerTable = new Table();
|
||
// ... outer table properties and grid ...
|
||
|
||
var containerCell = new TableCell();
|
||
containerCell.AppendChild(new TableCellProperties(
|
||
new TableCellWidth { Width = "6000", Type = TableWidthUnitValues.Dxa }));
|
||
|
||
// Build inner (nested) table
|
||
var innerTable = new Table();
|
||
var innerProps = new TableProperties();
|
||
innerProps.AppendChild(new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct });
|
||
innerProps.AppendChild(new TableBorders(
|
||
new TopBorder { Val = BorderValues.Single, Size = 4U, Color = "999999" },
|
||
new BottomBorder { Val = BorderValues.Single, Size = 4U, Color = "999999" },
|
||
new LeftBorder { Val = BorderValues.Single, Size = 4U, Color = "999999" },
|
||
new RightBorder { Val = BorderValues.Single, Size = 4U, Color = "999999" },
|
||
new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4U, Color = "999999" },
|
||
new InsideVerticalBorder { Val = BorderValues.Single, Size = 4U, Color = "999999" }
|
||
));
|
||
innerTable.AppendChild(innerProps);
|
||
|
||
var innerGrid = new TableGrid(
|
||
new GridColumn { Width = "2500" },
|
||
new GridColumn { Width = "2500" });
|
||
innerTable.AppendChild(innerGrid);
|
||
|
||
var innerRow = new TableRow(
|
||
new TableCell(
|
||
new TableCellProperties(new TableCellWidth { Width = "2500", Type = TableWidthUnitValues.Dxa }),
|
||
new Paragraph(new Run(new Text("Inner A")))),
|
||
new TableCell(
|
||
new TableCellProperties(new TableCellWidth { Width = "2500", Type = TableWidthUnitValues.Dxa }),
|
||
new Paragraph(new Run(new Text("Inner B"))))
|
||
);
|
||
innerTable.AppendChild(innerRow);
|
||
|
||
// Add inner table to container cell
|
||
containerCell.AppendChild(innerTable);
|
||
// MUST have a paragraph after nested table
|
||
containerCell.AppendChild(new Paragraph());
|
||
```
|
||
|
||
### 2.12 Table Positioning (Floating Table)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE POSITIONING — FLOATING TABLE
|
||
// =============================================================================
|
||
// TablePositionProperties makes a table "float" at a specific position
|
||
// on the page, allowing text to wrap around it.
|
||
// All position values are in DXA.
|
||
|
||
var floatProps = new TablePositionProperties
|
||
{
|
||
VerticalAnchor = VerticalAnchorValues.Page,
|
||
HorizontalAnchor = HorizontalAnchorValues.Page,
|
||
TablePositionX = 2880, // 2 inches from left page edge
|
||
TablePositionY = 4320, // 3 inches from top page edge
|
||
LeftFromText = 180, // 0.125 inch gap from text on left
|
||
RightFromText = 180,
|
||
TopFromText = 0,
|
||
BottomFromText = 0
|
||
};
|
||
|
||
tblProps.AppendChild(floatProps);
|
||
// Produces XML:
|
||
// <w:tblpPr w:vertAnchor="page" w:horzAnchor="page"
|
||
// w:tblpX="2880" w:tblpY="4320"
|
||
// w:leftFromText="180" w:rightFromText="180"
|
||
// w:topFromText="0" w:bottomFromText="0" />
|
||
```
|
||
|
||
### 2.13 TableLook — Conditional Formatting Flags
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE LOOK — CONDITIONAL FORMATTING FLAGS
|
||
// =============================================================================
|
||
// TableLook controls which conditional formatting bands are applied from
|
||
// the table style. These flags tell Word which "special" formatting to use.
|
||
//
|
||
// Val is a hex bitmask. Common values:
|
||
// 0x04A0 = FirstRow + LastRow + NoHBand (typical for styled tables)
|
||
// 0x0000 = no conditional formatting
|
||
//
|
||
// Individual boolean flags can also be set:
|
||
|
||
var tableLook = new TableLook
|
||
{
|
||
Val = "04A0",
|
||
FirstRow = true, // Apply first-row (header) formatting
|
||
LastRow = true, // Apply last-row (totals) formatting
|
||
FirstColumn = false,
|
||
LastColumn = false,
|
||
NoHorizontalBand = true, // Don't apply horizontal banding
|
||
NoVerticalBand = true // Don't apply vertical banding
|
||
};
|
||
|
||
tblProps.AppendChild(tableLook);
|
||
// Produces XML:
|
||
// <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="1"
|
||
// w:firstColumn="0" w:lastColumn="0"
|
||
// w:noHBand="1" w:noVBand="1" />
|
||
```
|
||
|
||
### 2.14 Complete Styled Table — Header + Data + Totals + Zebra Striping
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// COMPLETE STYLED TABLE
|
||
// =============================================================================
|
||
// Builds a professional table with:
|
||
// - Header row (dark background, white text, repeats on page break)
|
||
// - Data rows with alternating zebra-stripe shading
|
||
// - Totals row (bold, top border)
|
||
// - Full 100% width on Letter page
|
||
|
||
static Table CreateStyledTable(string[][] data, bool hasHeaderRow = true, bool hasTotalsRow = true)
|
||
{
|
||
int cols = data[0].Length;
|
||
// For Letter page with 1" margins: 12240 - 2 * 1440 = 9360
|
||
int totalWidth = 9360;
|
||
int colWidth = totalWidth / cols;
|
||
|
||
var table = new Table();
|
||
|
||
// --- Table Properties ---
|
||
var tblPr = new TableProperties(
|
||
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
|
||
new TableLayout { Type = TableLayoutValues.Fixed },
|
||
new TableBorders(
|
||
new TopBorder { Val = BorderValues.Single, Size = 8U, Color = "000000" },
|
||
new BottomBorder { Val = BorderValues.Single, Size = 8U, Color = "000000" },
|
||
new LeftBorder { Val = BorderValues.Single, Size = 4U, Color = "BFBFBF" },
|
||
new RightBorder { Val = BorderValues.Single, Size = 4U, Color = "BFBFBF" },
|
||
new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4U, Color = "BFBFBF" },
|
||
new InsideVerticalBorder { Val = BorderValues.Single, Size = 4U, Color = "BFBFBF" }
|
||
),
|
||
new TableLook
|
||
{
|
||
Val = "04A0", FirstRow = true, LastRow = true,
|
||
FirstColumn = false, LastColumn = false,
|
||
NoHorizontalBand = true, NoVerticalBand = true
|
||
}
|
||
);
|
||
table.AppendChild(tblPr);
|
||
|
||
// --- Table Grid ---
|
||
var grid = new TableGrid();
|
||
for (int c = 0; c < cols; c++)
|
||
{
|
||
int w = (c == cols - 1) ? totalWidth - colWidth * (cols - 1) : colWidth;
|
||
grid.AppendChild(new GridColumn { Width = w.ToString() });
|
||
}
|
||
table.AppendChild(grid);
|
||
|
||
// --- Rows ---
|
||
for (int r = 0; r < data.Length; r++)
|
||
{
|
||
bool isHeader = hasHeaderRow && r == 0;
|
||
bool isTotals = hasTotalsRow && r == data.Length - 1;
|
||
bool isOddDataRow = !isHeader && !isTotals && (r % 2 == 1);
|
||
|
||
var row = new TableRow();
|
||
|
||
// Row properties
|
||
var trPr = new TableRowProperties();
|
||
trPr.AppendChild(new TableRowHeight
|
||
{
|
||
Val = isHeader ? 480U : 360U,
|
||
HeightRule = HeightRuleValues.AtLeast
|
||
});
|
||
if (isHeader)
|
||
{
|
||
trPr.AppendChild(new TableHeader()); // Repeat header on each page
|
||
}
|
||
row.AppendChild(trPr);
|
||
|
||
// Cells
|
||
for (int c = 0; c < cols; c++)
|
||
{
|
||
int w = (c == cols - 1) ? totalWidth - colWidth * (cols - 1) : colWidth;
|
||
var cell = new TableCell();
|
||
var tcPr = new TableCellProperties();
|
||
tcPr.AppendChild(new TableCellWidth
|
||
{
|
||
Width = w.ToString(),
|
||
Type = TableWidthUnitValues.Dxa
|
||
});
|
||
tcPr.AppendChild(new TableCellVerticalAlignment
|
||
{
|
||
Val = TableVerticalAlignmentValues.Center
|
||
});
|
||
|
||
// Header: dark blue background
|
||
if (isHeader)
|
||
{
|
||
tcPr.AppendChild(new Shading
|
||
{
|
||
Val = ShadingPatternValues.Clear,
|
||
Color = "auto",
|
||
Fill = "2F5496" // Dark blue
|
||
});
|
||
}
|
||
// Zebra stripe: light gray on odd data rows
|
||
else if (isOddDataRow)
|
||
{
|
||
tcPr.AppendChild(new Shading
|
||
{
|
||
Val = ShadingPatternValues.Clear,
|
||
Color = "auto",
|
||
Fill = "F2F2F2" // Light gray
|
||
});
|
||
}
|
||
// Totals row: top border emphasis
|
||
if (isTotals)
|
||
{
|
||
tcPr.AppendChild(new TableCellBorders(
|
||
new TopBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 12U, // 1.5pt
|
||
Color = "000000"
|
||
}
|
||
));
|
||
}
|
||
cell.AppendChild(tcPr);
|
||
|
||
// Paragraph with text
|
||
var runProps = new RunProperties();
|
||
if (isHeader)
|
||
{
|
||
runProps.AppendChild(new Bold());
|
||
runProps.AppendChild(new Color { Val = "FFFFFF" }); // White text
|
||
runProps.AppendChild(new FontSize { Val = "22" }); // 11pt
|
||
}
|
||
else if (isTotals)
|
||
{
|
||
runProps.AppendChild(new Bold());
|
||
}
|
||
|
||
var para = new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification
|
||
{
|
||
Val = (c > 0) ? JustificationValues.Right : JustificationValues.Left
|
||
}
|
||
),
|
||
new Run(runProps, new Text(data[r][c]))
|
||
);
|
||
cell.AppendChild(para);
|
||
row.AppendChild(cell);
|
||
}
|
||
table.AppendChild(row);
|
||
}
|
||
return table;
|
||
}
|
||
|
||
// --- Usage ---
|
||
string[][] salesData =
|
||
[
|
||
["Product", "Q1", "Q2", "Q3", "Q4" ],
|
||
["Widget A", "1,200", "1,350", "1,100", "1,500"],
|
||
["Widget B", "800", "920", "870", "1,010"],
|
||
["Widget C", "2,100", "2,300", "2,150", "2,400"],
|
||
["Total", "4,100", "4,570", "4,120", "4,910"]
|
||
];
|
||
|
||
var styledTable = CreateStyledTable(salesData, hasHeaderRow: true, hasTotalsRow: true);
|
||
body.AppendChild(styledTable);
|
||
body.AppendChild(new Paragraph()); // Empty paragraph after table
|
||
```
|
||
|
||
### 2.15 Three-Line Table (学术三线表)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// THREE-LINE TABLE (学术三线表)
|
||
// =============================================================================
|
||
// Academic/scientific table style common in Chinese academic publishing:
|
||
// - Top border: THICK (1.5pt)
|
||
// - Border below header: THIN (0.75pt)
|
||
// - Bottom border: THICK (1.5pt)
|
||
// - NO vertical borders, NO other horizontal borders
|
||
//
|
||
// This is achieved by:
|
||
// 1. Setting table borders to None (removes all defaults)
|
||
// 2. Using per-cell borders on the header row (bottom) and first/last rows (top/bottom)
|
||
|
||
static Table CreateThreeLineTable(string[] headers, string[][] rows)
|
||
{
|
||
int cols = headers.Length;
|
||
int totalWidth = 9360;
|
||
int colWidth = totalWidth / cols;
|
||
|
||
var table = new Table();
|
||
|
||
// Table properties: NO borders by default
|
||
var tblPr = new TableProperties(
|
||
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
|
||
new TableJustification { Val = TableRowAlignmentValues.Center },
|
||
new TableLayout { Type = TableLayoutValues.Fixed },
|
||
new TableBorders(
|
||
new TopBorder { Val = BorderValues.None, Size = 0U },
|
||
new BottomBorder { Val = BorderValues.None, Size = 0U },
|
||
new LeftBorder { Val = BorderValues.None, Size = 0U },
|
||
new RightBorder { Val = BorderValues.None, Size = 0U },
|
||
new InsideHorizontalBorder { Val = BorderValues.None, Size = 0U },
|
||
new InsideVerticalBorder { Val = BorderValues.None, Size = 0U }
|
||
)
|
||
);
|
||
table.AppendChild(tblPr);
|
||
|
||
// Table grid
|
||
var grid = new TableGrid();
|
||
for (int c = 0; c < cols; c++)
|
||
{
|
||
int w = (c == cols - 1) ? totalWidth - colWidth * (cols - 1) : colWidth;
|
||
grid.AppendChild(new GridColumn { Width = w.ToString() });
|
||
}
|
||
table.AppendChild(grid);
|
||
|
||
// --- Header row ---
|
||
var headerRow = new TableRow();
|
||
headerRow.AppendChild(new TableRowProperties(new TableHeader()));
|
||
|
||
for (int c = 0; c < cols; c++)
|
||
{
|
||
int w = (c == cols - 1) ? totalWidth - colWidth * (cols - 1) : colWidth;
|
||
var cell = new TableCell();
|
||
var tcPr = new TableCellProperties(
|
||
new TableCellWidth { Width = w.ToString(), Type = TableWidthUnitValues.Dxa },
|
||
new TableCellBorders(
|
||
// Top: THICK line (1.5pt = 12 eighth-points)
|
||
new TopBorder { Val = BorderValues.Single, Size = 12U, Color = "000000", Space = 0U },
|
||
// Bottom: THIN line (0.75pt = 6 eighth-points)
|
||
new BottomBorder { Val = BorderValues.Single, Size = 6U, Color = "000000", Space = 0U }
|
||
),
|
||
new TableCellVerticalAlignment { Val = TableVerticalAlignmentValues.Center }
|
||
);
|
||
cell.AppendChild(tcPr);
|
||
cell.AppendChild(new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Center }),
|
||
new Run(
|
||
new RunProperties(new Bold()),
|
||
new Text(headers[c]))));
|
||
cell.AppendChild(cell); // Fix: should be row.AppendChild
|
||
headerRow.AppendChild(cell);
|
||
}
|
||
table.AppendChild(headerRow);
|
||
|
||
// --- Data rows ---
|
||
for (int r = 0; r < rows.Length; r++)
|
||
{
|
||
var dataRow = new TableRow();
|
||
bool isLastRow = (r == rows.Length - 1);
|
||
|
||
for (int c = 0; c < cols; c++)
|
||
{
|
||
int w = (c == cols - 1) ? totalWidth - colWidth * (cols - 1) : colWidth;
|
||
var cell = new TableCell();
|
||
var tcPr = new TableCellProperties(
|
||
new TableCellWidth { Width = w.ToString(), Type = TableWidthUnitValues.Dxa }
|
||
);
|
||
|
||
// Last data row: add THICK bottom border
|
||
if (isLastRow)
|
||
{
|
||
tcPr.AppendChild(new TableCellBorders(
|
||
new BottomBorder
|
||
{
|
||
Val = BorderValues.Single,
|
||
Size = 12U, // 1.5pt thick
|
||
Color = "000000",
|
||
Space = 0U
|
||
}
|
||
));
|
||
}
|
||
|
||
tcPr.AppendChild(new TableCellVerticalAlignment
|
||
{
|
||
Val = TableVerticalAlignmentValues.Center
|
||
});
|
||
cell.AppendChild(tcPr);
|
||
cell.AppendChild(new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Center }),
|
||
new Run(new Text(rows[r][c]))));
|
||
dataRow.AppendChild(cell);
|
||
}
|
||
table.AppendChild(dataRow);
|
||
}
|
||
|
||
return table;
|
||
}
|
||
|
||
// --- Usage ---
|
||
string[] columnHeaders = ["Variable", "Mean", "SD", "p-value"];
|
||
string[][] dataRows =
|
||
[
|
||
["Age", "45.2", "12.3", "0.032"],
|
||
["BMI", "26.8", "4.1", "0.001"],
|
||
["SBP", "132", "18.5", "< 0.001"],
|
||
["HR", "72.1", "11.2", "0.145"]
|
||
];
|
||
|
||
var threeLineTable = CreateThreeLineTable(columnHeaders, dataRows);
|
||
body.AppendChild(threeLineTable);
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Headers, Footers & Page Numbers
|
||
|
||
Headers and footers are stored in separate XML parts (HeaderPart, FooterPart) linked to SectionProperties via HeaderReference and FooterReference.
|
||
|
||
### 3.1 Creating HeaderPart and FooterPart
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// CREATING HEADER AND FOOTER PARTS
|
||
// =============================================================================
|
||
// Headers and footers are separate parts within the package, each containing
|
||
// their own XML document tree rooted at <w:hdr> or <w:ftr>.
|
||
// They are linked to sections via HeaderReference/FooterReference.
|
||
//
|
||
// Steps:
|
||
// 1. Add a HeaderPart/FooterPart to the MainDocumentPart
|
||
// 2. Set the part's root element (Header/Footer)
|
||
// 3. Add content (paragraphs, tables, images) to the root element
|
||
// 4. Create a HeaderReference/FooterReference in SectionProperties
|
||
|
||
var mainPart = doc.MainDocumentPart!;
|
||
|
||
// --- Create a header part ---
|
||
var headerPart = mainPart.AddNewPart<HeaderPart>();
|
||
string headerPartId = mainPart.GetIdOfPart(headerPart);
|
||
|
||
// Build header content
|
||
headerPart.Header = new Header(
|
||
new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Center }),
|
||
new Run(
|
||
new RunProperties(
|
||
new FontSize { Val = "18" }, // 9pt — typical header size
|
||
new Color { Val = "808080" }), // Gray
|
||
new Text("Company Confidential"))
|
||
)
|
||
);
|
||
headerPart.Header.Save();
|
||
|
||
// --- Create a footer part ---
|
||
var footerPart = mainPart.AddNewPart<FooterPart>();
|
||
string footerPartId = mainPart.GetIdOfPart(footerPart);
|
||
|
||
footerPart.Footer = new Footer(
|
||
new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Center }),
|
||
new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text("Footer text here"))
|
||
)
|
||
);
|
||
footerPart.Footer.Save();
|
||
```
|
||
|
||
### 3.2 HeaderReference and FooterReference — Types
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// HEADER/FOOTER REFERENCE — LINKING TO SECTION
|
||
// =============================================================================
|
||
// HeaderReference/FooterReference types:
|
||
// Default — used on all pages (unless First/Even overrides)
|
||
// First — used on the first page of the section (requires TitlePage)
|
||
// Even — used on even-numbered pages (requires EvenAndOddHeaders setting)
|
||
//
|
||
// A section can have up to 3 headers and 3 footers (Default + First + Even).
|
||
|
||
var sectPr = body.Elements<SectionProperties>().FirstOrDefault()
|
||
?? body.AppendChild(new SectionProperties());
|
||
|
||
// Default header (all pages)
|
||
sectPr.AppendChild(new HeaderReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = headerPartId
|
||
});
|
||
|
||
// Default footer (all pages)
|
||
sectPr.AppendChild(new FooterReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = footerPartId
|
||
});
|
||
|
||
// Produces XML:
|
||
// <w:sectPr>
|
||
// <w:headerReference w:type="default" r:id="rId4" />
|
||
// <w:footerReference w:type="default" r:id="rId5" />
|
||
// </w:sectPr>
|
||
```
|
||
|
||
### 3.3 Different First Page (TitlePage)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// DIFFERENT FIRST PAGE — TITLEPAGE
|
||
// =============================================================================
|
||
// To have a different header/footer on the first page, you must:
|
||
// 1. Add a TitlePage element to SectionProperties
|
||
// 2. Create separate HeaderPart/FooterPart for the first page
|
||
// 3. Add HeaderReference/FooterReference with Type = First
|
||
|
||
// Enable different first page
|
||
sectPr.AppendChild(new TitlePage());
|
||
// Produces XML: <w:titlePg />
|
||
|
||
// Create first-page header (e.g., empty / no header on cover page)
|
||
var firstHeaderPart = mainPart.AddNewPart<HeaderPart>();
|
||
string firstHeaderId = mainPart.GetIdOfPart(firstHeaderPart);
|
||
firstHeaderPart.Header = new Header(new Paragraph()); // Empty header
|
||
firstHeaderPart.Header.Save();
|
||
|
||
// Create first-page footer
|
||
var firstFooterPart = mainPart.AddNewPart<FooterPart>();
|
||
string firstFooterId = mainPart.GetIdOfPart(firstFooterPart);
|
||
firstFooterPart.Footer = new Footer(new Paragraph()); // Empty footer
|
||
firstFooterPart.Footer.Save();
|
||
|
||
// Link to section
|
||
sectPr.AppendChild(new HeaderReference
|
||
{
|
||
Type = HeaderFooterValues.First,
|
||
Id = firstHeaderId
|
||
});
|
||
sectPr.AppendChild(new FooterReference
|
||
{
|
||
Type = HeaderFooterValues.First,
|
||
Id = firstFooterId
|
||
});
|
||
```
|
||
|
||
### 3.4 Even and Odd Page Headers
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// EVEN AND ODD PAGE HEADERS/FOOTERS
|
||
// =============================================================================
|
||
// For different headers on even vs. odd pages (e.g., book-style layout):
|
||
// 1. Set EvenAndOddHeaders in DocumentSettingsPart
|
||
// 2. Create separate parts for Even pages
|
||
// 3. "Default" type becomes the ODD page header/footer
|
||
|
||
// Enable even/odd in Settings
|
||
var settingsPart = mainPart.DocumentSettingsPart
|
||
?? mainPart.AddNewPart<DocumentSettingsPart>();
|
||
if (settingsPart.Settings == null)
|
||
settingsPart.Settings = new Settings();
|
||
|
||
settingsPart.Settings.AppendChild(new EvenAndOddHeaders());
|
||
settingsPart.Settings.Save();
|
||
// Produces XML in settings.xml: <w:evenAndOddHeaders />
|
||
|
||
// Create even-page header
|
||
var evenHeaderPart = mainPart.AddNewPart<HeaderPart>();
|
||
string evenHeaderId = mainPart.GetIdOfPart(evenHeaderPart);
|
||
evenHeaderPart.Header = new Header(
|
||
new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Left }),
|
||
new Run(new Text("Chapter Title")) // Left-aligned on even pages
|
||
)
|
||
);
|
||
evenHeaderPart.Header.Save();
|
||
|
||
// Link: "Default" = odd pages, "Even" = even pages
|
||
sectPr.AppendChild(new HeaderReference
|
||
{
|
||
Type = HeaderFooterValues.Even,
|
||
Id = evenHeaderId
|
||
});
|
||
// The existing Default header is now used only on odd pages.
|
||
```
|
||
|
||
### 3.5 SimpleField — PAGE, NUMPAGES, SECTIONPAGES
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// SIMPLE FIELDS — PAGE NUMBERS AND TOTALS
|
||
// =============================================================================
|
||
// SimpleField inserts a field code that Word evaluates at render time.
|
||
// Common field codes:
|
||
// PAGE — current page number
|
||
// NUMPAGES — total pages in document
|
||
// SECTIONPAGES — total pages in current section
|
||
// DATE — current date
|
||
// TIME — current time
|
||
//
|
||
// The Instruction property contains the field code string.
|
||
// A child Run with text is used for the "cached" display value (optional but
|
||
// recommended for non-Word renderers).
|
||
|
||
// --- Simple page number field ---
|
||
var pageField = new SimpleField { Instruction = " PAGE " };
|
||
pageField.AppendChild(new Run(new Text("1"))); // Cached display value
|
||
// Produces XML: <w:fldSimple w:instr=" PAGE "><w:r><w:t>1</w:t></w:r></w:fldSimple>
|
||
|
||
// --- Total page count ---
|
||
var totalField = new SimpleField { Instruction = " NUMPAGES " };
|
||
totalField.AppendChild(new Run(new Text("1")));
|
||
|
||
// --- Section page count ---
|
||
var sectionPagesField = new SimpleField { Instruction = " SECTIONPAGES " };
|
||
sectionPagesField.AppendChild(new Run(new Text("1")));
|
||
```
|
||
|
||
### 3.6 "Page X of Y" Footer
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// "PAGE X OF Y" FOOTER
|
||
// =============================================================================
|
||
// Combines text runs with SimpleField elements in a single paragraph.
|
||
|
||
var pageXofYFooter = mainPart.AddNewPart<FooterPart>();
|
||
string pageXofYId = mainPart.GetIdOfPart(pageXofYFooter);
|
||
|
||
var footerPara = new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Center })
|
||
);
|
||
|
||
// "Page "
|
||
footerPara.AppendChild(new Run(
|
||
new RunProperties(new FontSize { Val = "18" }), // 9pt
|
||
new Text("Page ") { Space = SpaceProcessingModeValues.Preserve }
|
||
));
|
||
|
||
// PAGE field
|
||
var pgField = new SimpleField { Instruction = " PAGE " };
|
||
pgField.AppendChild(new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text("1")));
|
||
footerPara.AppendChild(pgField);
|
||
|
||
// " of "
|
||
footerPara.AppendChild(new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text(" of ") { Space = SpaceProcessingModeValues.Preserve }
|
||
));
|
||
|
||
// NUMPAGES field
|
||
var npField = new SimpleField { Instruction = " NUMPAGES " };
|
||
npField.AppendChild(new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text("1")));
|
||
footerPara.AppendChild(npField);
|
||
|
||
pageXofYFooter.Footer = new Footer(footerPara);
|
||
pageXofYFooter.Footer.Save();
|
||
|
||
// Link to section
|
||
sectPr.AppendChild(new FooterReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = pageXofYId
|
||
});
|
||
```
|
||
|
||
### 3.7 Header with Logo Image
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// HEADER WITH LOGO IMAGE
|
||
// =============================================================================
|
||
// To add an image to a header:
|
||
// 1. Add an ImagePart to the HeaderPart (not MainDocumentPart)
|
||
// 2. Build the Drawing/Inline/Graphic elements in the header paragraph
|
||
// 3. Image sizing uses EMUs (English Metric Units): 1 inch = 914400 EMU
|
||
|
||
var logoHeaderPart = mainPart.AddNewPart<HeaderPart>();
|
||
string logoHeaderId = mainPart.GetIdOfPart(logoHeaderPart);
|
||
|
||
// Add image to header part
|
||
var imagePart = logoHeaderPart.AddImagePart(ImagePartType.Png);
|
||
using (var stream = File.OpenRead("logo.png"))
|
||
{
|
||
imagePart.FeedData(stream);
|
||
}
|
||
string imageRelId = logoHeaderPart.GetIdOfPart(imagePart);
|
||
|
||
// Image dimensions in EMU (e.g., 1.5 inch wide × 0.5 inch tall)
|
||
long widthEmu = 1371600; // 1.5 * 914400
|
||
long heightEmu = 457200; // 0.5 * 914400
|
||
|
||
// Build the inline drawing element
|
||
var drawing = new Drawing(
|
||
new DW.Inline(
|
||
new DW.Extent { Cx = widthEmu, Cy = heightEmu },
|
||
new DW.EffectExtent { LeftEdge = 0L, TopEdge = 0L, RightEdge = 0L, BottomEdge = 0L },
|
||
new DW.DocProperties { Id = 1U, Name = "Logo" },
|
||
new DW.NonVisualGraphicFrameDrawingProperties(
|
||
new A.GraphicFrameLocks { NoChangeAspect = true }),
|
||
new A.Graphic(
|
||
new A.GraphicData(
|
||
new PIC.Picture(
|
||
new PIC.NonVisualPictureProperties(
|
||
new PIC.NonVisualDrawingProperties { Id = 1U, Name = "logo.png" },
|
||
new PIC.NonVisualPictureDrawingProperties()),
|
||
new PIC.BlipFill(
|
||
new A.Blip { Embed = imageRelId },
|
||
new A.Stretch(new A.FillRectangle())),
|
||
new PIC.ShapeProperties(
|
||
new A.Transform2D(
|
||
new A.Offset { X = 0L, Y = 0L },
|
||
new A.Extents { Cx = widthEmu, Cy = heightEmu }),
|
||
new A.PresetGeometry(new A.AdjustValueList())
|
||
{ Preset = A.ShapeTypeValues.Rectangle })
|
||
)
|
||
) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }
|
||
)
|
||
)
|
||
{
|
||
DistanceFromTop = 0U,
|
||
DistanceFromBottom = 0U,
|
||
DistanceFromLeft = 0U,
|
||
DistanceFromRight = 0U
|
||
}
|
||
);
|
||
|
||
logoHeaderPart.Header = new Header(
|
||
new Paragraph(new Run(drawing))
|
||
);
|
||
logoHeaderPart.Header.Save();
|
||
```
|
||
|
||
### 3.8 Table-Layout Header (Logo Left, Text Center, Page Right)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// TABLE-LAYOUT HEADER
|
||
// =============================================================================
|
||
// A common professional header pattern: 3-column invisible table
|
||
// Left cell: Company logo
|
||
// Center cell: Document title
|
||
// Right cell: Page number
|
||
// The table has no borders, giving a clean three-zone layout.
|
||
|
||
var tblHeaderPart = mainPart.AddNewPart<HeaderPart>();
|
||
string tblHeaderId = mainPart.GetIdOfPart(tblHeaderPart);
|
||
|
||
// Content width for Letter with 1" margins = 9360 DXA
|
||
var headerTable = new Table(
|
||
new TableProperties(
|
||
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
|
||
new TableLayout { Type = TableLayoutValues.Fixed },
|
||
new TableBorders(
|
||
new TopBorder { Val = BorderValues.None },
|
||
new BottomBorder { Val = BorderValues.Single, Size = 4U, Color = "000000" },
|
||
new LeftBorder { Val = BorderValues.None },
|
||
new RightBorder { Val = BorderValues.None },
|
||
new InsideHorizontalBorder { Val = BorderValues.None },
|
||
new InsideVerticalBorder { Val = BorderValues.None }
|
||
)
|
||
),
|
||
new TableGrid(
|
||
new GridColumn { Width = "3120" }, // ~1/3
|
||
new GridColumn { Width = "3120" }, // ~1/3
|
||
new GridColumn { Width = "3120" } // ~1/3
|
||
)
|
||
);
|
||
|
||
// Left cell: logo placeholder (or image Drawing)
|
||
var leftCell = new TableCell(
|
||
new TableCellProperties(
|
||
new TableCellWidth { Width = "3120", Type = TableWidthUnitValues.Dxa },
|
||
new TableCellVerticalAlignment { Val = TableVerticalAlignmentValues.Center }),
|
||
new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Left }),
|
||
new Run(
|
||
new RunProperties(new Bold(), new FontSize { Val = "18" }),
|
||
new Text("ACME Corp")))
|
||
);
|
||
|
||
// Center cell: document title
|
||
var centerCell = new TableCell(
|
||
new TableCellProperties(
|
||
new TableCellWidth { Width = "3120", Type = TableWidthUnitValues.Dxa },
|
||
new TableCellVerticalAlignment { Val = TableVerticalAlignmentValues.Center }),
|
||
new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Center }),
|
||
new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text("Technical Specification v2.0")))
|
||
);
|
||
|
||
// Right cell: page number
|
||
var pageFieldRight = new SimpleField { Instruction = " PAGE " };
|
||
pageFieldRight.AppendChild(new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text("1")));
|
||
|
||
var rightCell = new TableCell(
|
||
new TableCellProperties(
|
||
new TableCellWidth { Width = "3120", Type = TableWidthUnitValues.Dxa },
|
||
new TableCellVerticalAlignment { Val = TableVerticalAlignmentValues.Center }),
|
||
new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Right }),
|
||
new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text("Page ") { Space = SpaceProcessingModeValues.Preserve }),
|
||
pageFieldRight)
|
||
);
|
||
|
||
headerTable.AppendChild(new TableRow(leftCell, centerCell, rightCell));
|
||
|
||
tblHeaderPart.Header = new Header(headerTable, new Paragraph());
|
||
tblHeaderPart.Header.Save();
|
||
```
|
||
|
||
### 3.9 Chinese Government Document Page Numbers (公文页码)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// CHINESE GOVERNMENT DOCUMENT PAGE NUMBERS (公文页码)
|
||
// =============================================================================
|
||
// Standard: bottom center, format "-X-" (em-dash surrounding page number)
|
||
// Font: 宋体 (SimSun) 四号 (14pt = "28" half-points)
|
||
// Per GB/T 9704-2012
|
||
|
||
var govFooterPart = mainPart.AddNewPart<FooterPart>();
|
||
string govFooterId = mainPart.GetIdOfPart(govFooterPart);
|
||
|
||
var govFooterPara = new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Center })
|
||
);
|
||
|
||
// Run properties for 宋体四号
|
||
RunProperties GovPageRunProps() => new RunProperties(
|
||
new RunFonts
|
||
{
|
||
Ascii = "SimSun",
|
||
HighAnsi = "SimSun",
|
||
EastAsia = "SimSun"
|
||
},
|
||
new FontSize { Val = "28" }, // 14pt = 四号
|
||
new FontSizeComplexScript { Val = "28" }
|
||
);
|
||
|
||
// "—" (em-dash before page number)
|
||
govFooterPara.AppendChild(new Run(
|
||
GovPageRunProps(),
|
||
new Text("\u2014") { Space = SpaceProcessingModeValues.Preserve }
|
||
));
|
||
|
||
// PAGE field
|
||
var govPageField = new SimpleField { Instruction = " PAGE " };
|
||
govPageField.AppendChild(new Run(GovPageRunProps(), new Text("1")));
|
||
govFooterPara.AppendChild(govPageField);
|
||
|
||
// "—" (em-dash after page number)
|
||
govFooterPara.AppendChild(new Run(
|
||
GovPageRunProps(),
|
||
new Text("\u2014") { Space = SpaceProcessingModeValues.Preserve }
|
||
));
|
||
|
||
govFooterPart.Footer = new Footer(govFooterPara);
|
||
govFooterPart.Footer.Save();
|
||
|
||
// Link to section
|
||
sectPr.AppendChild(new FooterReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = govFooterId
|
||
});
|
||
```
|
||
|
||
### 3.10 Multi-Section with Different Headers
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// MULTI-SECTION WITH DIFFERENT HEADERS
|
||
// =============================================================================
|
||
// Each section can have its own set of header/footer parts.
|
||
// To change headers mid-document, create a section break and attach
|
||
// different HeaderReference/FooterReference to each SectionProperties.
|
||
// See Section 4 for full section break mechanics.
|
||
|
||
// Section 1 header
|
||
var sec1Header = mainPart.AddNewPart<HeaderPart>();
|
||
string sec1HeaderId = mainPart.GetIdOfPart(sec1Header);
|
||
sec1Header.Header = new Header(
|
||
new Paragraph(new Run(new Text("Chapter 1: Introduction"))));
|
||
sec1Header.Header.Save();
|
||
|
||
// Section 2 header
|
||
var sec2Header = mainPart.AddNewPart<HeaderPart>();
|
||
string sec2HeaderId = mainPart.GetIdOfPart(sec2Header);
|
||
sec2Header.Header = new Header(
|
||
new Paragraph(new Run(new Text("Chapter 2: Methods"))));
|
||
sec2Header.Header.Save();
|
||
|
||
// Section 1 content + section break
|
||
body.AppendChild(new Paragraph(new Run(new Text("Section 1 content..."))));
|
||
|
||
// Mid-document section properties (in last paragraph of section 1)
|
||
var sec1Break = new Paragraph(
|
||
new ParagraphProperties(
|
||
new SectionProperties(
|
||
new PageSize { Width = 12240U, Height = 15840U },
|
||
new PageMargin
|
||
{
|
||
Top = 1440, Bottom = 1440,
|
||
Left = 1440U, Right = 1440U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
},
|
||
new SectionType { Val = SectionMarkValues.NextPage },
|
||
new HeaderReference { Type = HeaderFooterValues.Default, Id = sec1HeaderId }
|
||
)
|
||
)
|
||
);
|
||
body.AppendChild(sec1Break);
|
||
|
||
// Section 2 content
|
||
body.AppendChild(new Paragraph(new Run(new Text("Section 2 content..."))));
|
||
|
||
// Final section properties (last child of Body)
|
||
var sec2Props = new SectionProperties(
|
||
new PageSize { Width = 12240U, Height = 15840U },
|
||
new PageMargin
|
||
{
|
||
Top = 1440, Bottom = 1440,
|
||
Left = 1440U, Right = 1440U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
},
|
||
new HeaderReference { Type = HeaderFooterValues.Default, Id = sec2HeaderId }
|
||
);
|
||
body.AppendChild(sec2Props);
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Section Breaks & Multi-Section
|
||
|
||
### 4.1 Section Properties Placement Rules
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// SECTION PROPERTIES PLACEMENT
|
||
// =============================================================================
|
||
// There are exactly TWO places SectionProperties can appear:
|
||
//
|
||
// 1. FINAL SECTION: As the last child element of <w:body>.
|
||
// This controls the last (or only) section of the document.
|
||
// body.AppendChild(new SectionProperties(...));
|
||
//
|
||
// 2. MID-DOCUMENT SECTION BREAK: Inside ParagraphProperties of the
|
||
// LAST paragraph of a section. This paragraph acts as the section divider.
|
||
// The paragraph's text content belongs to the PREVIOUS section.
|
||
//
|
||
// IMPORTANT: Mid-document SectionProperties goes inside pPr, NOT as a child
|
||
// of Body. This is the #1 mistake when creating multi-section documents.
|
||
|
||
// --- Final section (last child of Body) ---
|
||
var finalSectPr = new SectionProperties(
|
||
new PageSize { Width = 12240U, Height = 15840U },
|
||
new PageMargin
|
||
{
|
||
Top = 1440, Bottom = 1440,
|
||
Left = 1440U, Right = 1440U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
}
|
||
);
|
||
body.AppendChild(finalSectPr); // MUST be last child
|
||
|
||
// --- Mid-document section break ---
|
||
// This paragraph ends section 1 and starts section 2
|
||
var sectionBreakPara = new Paragraph(
|
||
new ParagraphProperties(
|
||
new SectionProperties(
|
||
new PageSize { Width = 12240U, Height = 15840U },
|
||
new PageMargin
|
||
{
|
||
Top = 1440, Bottom = 1440,
|
||
Left = 1440U, Right = 1440U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
},
|
||
new SectionType { Val = SectionMarkValues.NextPage }
|
||
)
|
||
)
|
||
);
|
||
// Produces XML:
|
||
// <w:p>
|
||
// <w:pPr>
|
||
// <w:sectPr>
|
||
// <w:pgSz w:w="12240" w:h="15840" />
|
||
// <w:pgMar ... />
|
||
// <w:type w:val="nextPage" />
|
||
// </w:sectPr>
|
||
// </w:pPr>
|
||
// </w:p>
|
||
```
|
||
|
||
### 4.2 SectionType Values
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// SECTION TYPE VALUES
|
||
// =============================================================================
|
||
// SectionType controls how the section break behaves:
|
||
//
|
||
// NextPage — new section starts on next page (most common)
|
||
// Continuous — new section starts on same page (used for column changes)
|
||
// EvenPage — new section starts on next even page (inserts blank if needed)
|
||
// OddPage — new section starts on next odd page (inserts blank if needed)
|
||
//
|
||
// If SectionType is omitted, the default is NextPage.
|
||
|
||
// Next page break (explicit)
|
||
var nextPageBreak = new SectionType { Val = SectionMarkValues.NextPage };
|
||
|
||
// Continuous — same page (e.g., switch from 1-column to 2-column)
|
||
var continuousBreak = new SectionType { Val = SectionMarkValues.Continuous };
|
||
|
||
// Even page — for chapters that must start on left page (in book layout)
|
||
var evenPageBreak = new SectionType { Val = SectionMarkValues.EvenPage };
|
||
|
||
// Odd page — for chapters that must start on right page
|
||
var oddPageBreak = new SectionType { Val = SectionMarkValues.OddPage };
|
||
```
|
||
|
||
### 4.3 Per-Section Page Setup
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// PER-SECTION PAGE SETUP
|
||
// =============================================================================
|
||
// Each section independently controls its own:
|
||
// - PageSize (portrait vs landscape, paper size)
|
||
// - PageMargin
|
||
// - Columns
|
||
// - Headers/Footers
|
||
// - PageNumberType (restart numbering)
|
||
// - LineNumbering
|
||
// - DocGrid
|
||
|
||
// Example: Section with landscape A4 and narrow margins
|
||
var landscapeSection = new SectionProperties(
|
||
new PageSize
|
||
{
|
||
Width = 16838U, // A4 long edge (landscape: swap W/H)
|
||
Height = 11906U, // A4 short edge
|
||
Orient = PageOrientationValues.Landscape
|
||
},
|
||
new PageMargin
|
||
{
|
||
Top = 720, Bottom = 720,
|
||
Left = 720U, Right = 720U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
},
|
||
new SectionType { Val = SectionMarkValues.NextPage }
|
||
);
|
||
```
|
||
|
||
### 4.4 PageNumberType — Restart Numbering
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// PAGE NUMBER TYPE — RESTART AND FORMAT
|
||
// =============================================================================
|
||
// PageNumberType controls page numbering within a section.
|
||
// Start — starting page number (restarts counting)
|
||
// Format — numbering format (Decimal, UpperRoman, LowerRoman,
|
||
// UpperLetter, LowerLetter)
|
||
//
|
||
// Common pattern: front matter uses i, ii, iii; body restarts at 1.
|
||
|
||
// Restart at page 1 (e.g., after front matter)
|
||
var restartNumbering = new PageNumberType
|
||
{
|
||
Start = 1,
|
||
Format = NumberFormatValues.Decimal
|
||
};
|
||
// Produces XML: <w:pgNumType w:start="1" w:fmt="decimal" />
|
||
|
||
// Roman numeral numbering for front matter
|
||
var romanNumbering = new PageNumberType
|
||
{
|
||
Start = 1,
|
||
Format = NumberFormatValues.LowerRoman
|
||
};
|
||
// Produces XML: <w:pgNumType w:start="1" w:fmt="lowerRoman" />
|
||
|
||
// Add to section properties
|
||
landscapeSection.AppendChild(restartNumbering);
|
||
```
|
||
|
||
### 4.5 Complete Example: Portrait, Landscape, Portrait
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// COMPLETE MULTI-SECTION EXAMPLE
|
||
// =============================================================================
|
||
// Document with three sections:
|
||
// Section 1: Letter portrait (cover + intro)
|
||
// Section 2: Letter landscape (wide table)
|
||
// Section 3: Letter portrait (conclusion), page numbers restart at 1
|
||
//
|
||
// Each section has its own header.
|
||
|
||
using var doc = WordprocessingDocument.Create(
|
||
"MultiSection.docx", WordprocessingDocumentType.Document);
|
||
|
||
var mainPart = doc.MainDocumentPart!;
|
||
var body = mainPart.Document.Body!;
|
||
|
||
// --- Create headers for each section ---
|
||
HeaderPart CreateSimpleHeader(string text)
|
||
{
|
||
var hp = mainPart.AddNewPart<HeaderPart>();
|
||
hp.Header = new Header(
|
||
new Paragraph(
|
||
new ParagraphProperties(
|
||
new ParagraphBorders(
|
||
new BottomBorder
|
||
{
|
||
Val = BorderValues.Single, Size = 4U,
|
||
Color = "999999", Space = 1U
|
||
}),
|
||
new Justification { Val = JustificationValues.Right }),
|
||
new Run(
|
||
new RunProperties(
|
||
new FontSize { Val = "18" },
|
||
new Color { Val = "999999" }),
|
||
new Text(text)))
|
||
);
|
||
hp.Header.Save();
|
||
return hp;
|
||
}
|
||
|
||
var header1 = CreateSimpleHeader("Introduction");
|
||
var header2 = CreateSimpleHeader("Data Tables");
|
||
var header3 = CreateSimpleHeader("Conclusion");
|
||
|
||
// --- Create "Page X of Y" footer (shared) ---
|
||
var sharedFooter = mainPart.AddNewPart<FooterPart>();
|
||
var fPara = new Paragraph(
|
||
new ParagraphProperties(new Justification { Val = JustificationValues.Center }));
|
||
fPara.AppendChild(new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text("Page ") { Space = SpaceProcessingModeValues.Preserve }));
|
||
var pf = new SimpleField { Instruction = " PAGE " };
|
||
pf.AppendChild(new Run(new RunProperties(new FontSize { Val = "18" }), new Text("1")));
|
||
fPara.AppendChild(pf);
|
||
fPara.AppendChild(new Run(
|
||
new RunProperties(new FontSize { Val = "18" }),
|
||
new Text(" of ") { Space = SpaceProcessingModeValues.Preserve }));
|
||
var npf = new SimpleField { Instruction = " NUMPAGES " };
|
||
npf.AppendChild(new Run(new RunProperties(new FontSize { Val = "18" }), new Text("1")));
|
||
fPara.AppendChild(npf);
|
||
sharedFooter.Footer = new Footer(fPara);
|
||
sharedFooter.Footer.Save();
|
||
string sharedFooterId = mainPart.GetIdOfPart(sharedFooter);
|
||
|
||
// =================== SECTION 1: Portrait ===================
|
||
body.AppendChild(new Paragraph(
|
||
new ParagraphProperties(
|
||
new Justification { Val = JustificationValues.Center }),
|
||
new Run(
|
||
new RunProperties(new Bold(), new FontSize { Val = "48" }),
|
||
new Text("Annual Report 2025"))));
|
||
|
||
body.AppendChild(new Paragraph(new Run(new Text("Introduction content goes here..."))));
|
||
|
||
// Section 1 break (inside pPr of last paragraph)
|
||
body.AppendChild(new Paragraph(
|
||
new ParagraphProperties(
|
||
new SectionProperties(
|
||
new PageSize { Width = 12240U, Height = 15840U },
|
||
new PageMargin
|
||
{
|
||
Top = 1440, Bottom = 1440,
|
||
Left = 1440U, Right = 1440U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
},
|
||
new SectionType { Val = SectionMarkValues.NextPage },
|
||
new HeaderReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = mainPart.GetIdOfPart(header1)
|
||
},
|
||
new FooterReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = sharedFooterId
|
||
}
|
||
)
|
||
)
|
||
));
|
||
|
||
// =================== SECTION 2: Landscape ===================
|
||
body.AppendChild(new Paragraph(new Run(
|
||
new RunProperties(new Bold(), new FontSize { Val = "28" }),
|
||
new Text("Wide Data Table"))));
|
||
|
||
body.AppendChild(new Paragraph(new Run(
|
||
new Text("This section uses landscape orientation for a wide table."))));
|
||
|
||
// Section 2 break
|
||
body.AppendChild(new Paragraph(
|
||
new ParagraphProperties(
|
||
new SectionProperties(
|
||
new PageSize
|
||
{
|
||
Width = 15840U, // SWAPPED for landscape
|
||
Height = 12240U, // SWAPPED for landscape
|
||
Orient = PageOrientationValues.Landscape
|
||
},
|
||
new PageMargin
|
||
{
|
||
Top = 1440, Bottom = 1440,
|
||
Left = 1440U, Right = 1440U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
},
|
||
new SectionType { Val = SectionMarkValues.NextPage },
|
||
new HeaderReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = mainPart.GetIdOfPart(header2)
|
||
},
|
||
new FooterReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = sharedFooterId
|
||
}
|
||
)
|
||
)
|
||
));
|
||
|
||
// =================== SECTION 3: Portrait (restart page numbers) ===================
|
||
body.AppendChild(new Paragraph(new Run(
|
||
new RunProperties(new Bold(), new FontSize { Val = "28" }),
|
||
new Text("Conclusion"))));
|
||
|
||
body.AppendChild(new Paragraph(new Run(
|
||
new Text("Final section content with restarted page numbering."))));
|
||
|
||
// Final section properties — last child of Body
|
||
body.AppendChild(new SectionProperties(
|
||
new PageSize { Width = 12240U, Height = 15840U },
|
||
new PageMargin
|
||
{
|
||
Top = 1440, Bottom = 1440,
|
||
Left = 1440U, Right = 1440U,
|
||
Header = 720U, Footer = 720U, Gutter = 0U
|
||
},
|
||
new PageNumberType
|
||
{
|
||
Start = 1, // Restart at page 1
|
||
Format = NumberFormatValues.Decimal
|
||
},
|
||
new HeaderReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = mainPart.GetIdOfPart(header3)
|
||
},
|
||
new FooterReference
|
||
{
|
||
Type = HeaderFooterValues.Default,
|
||
Id = sharedFooterId
|
||
}
|
||
));
|
||
|
||
mainPart.Document.Save();
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Document Properties
|
||
|
||
### 5.1 CoreFilePropertiesPart (Dublin Core Metadata)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// CORE FILE PROPERTIES (DUBLIN CORE)
|
||
// =============================================================================
|
||
// Core properties map to the Dublin Core metadata standard.
|
||
// They appear in File > Properties > Summary in Word.
|
||
// The underlying XML is stored in docProps/core.xml.
|
||
//
|
||
// Available properties: Title, Subject, Creator (Author), Keywords,
|
||
// Description, LastModifiedBy, Revision, Created, Modified, Category,
|
||
// ContentStatus, ContentType, Identifier, Language, Version
|
||
|
||
using var doc = WordprocessingDocument.Create(
|
||
"PropertiesDemo.docx", WordprocessingDocumentType.Document);
|
||
|
||
// Core properties are accessed via the package-level properties
|
||
// Use the OpenXml Packaging API:
|
||
var corePart = doc.CoreFilePropertiesPart
|
||
?? doc.AddCoreFilePropertiesPart();
|
||
|
||
// The core properties use the OpenXmlPart's XML directly.
|
||
// You can set properties via the PackageProperties interface:
|
||
doc.PackageProperties.Title = "Quarterly Financial Report";
|
||
doc.PackageProperties.Subject = "Q4 2025 Financial Summary";
|
||
doc.PackageProperties.Creator = "Jane Smith";
|
||
doc.PackageProperties.Keywords = "finance, quarterly, report, 2025";
|
||
doc.PackageProperties.Description = "Comprehensive financial report for Q4 2025";
|
||
doc.PackageProperties.LastModifiedBy = "John Doe";
|
||
doc.PackageProperties.Revision = "3";
|
||
doc.PackageProperties.Created = DateTime.UtcNow;
|
||
doc.PackageProperties.Modified = DateTime.UtcNow;
|
||
doc.PackageProperties.Category = "Financial Reports";
|
||
doc.PackageProperties.ContentStatus = "Final";
|
||
doc.PackageProperties.Language = "en-US";
|
||
doc.PackageProperties.Version = "2.0";
|
||
|
||
// Produces docProps/core.xml:
|
||
// <cp:coreProperties>
|
||
// <dc:title>Quarterly Financial Report</dc:title>
|
||
// <dc:subject>Q4 2025 Financial Summary</dc:subject>
|
||
// <dc:creator>Jane Smith</dc:creator>
|
||
// <cp:keywords>finance, quarterly, report, 2025</cp:keywords>
|
||
// <dc:description>Comprehensive financial report...</dc:description>
|
||
// <cp:lastModifiedBy>John Doe</cp:lastModifiedBy>
|
||
// <cp:revision>3</cp:revision>
|
||
// <dcterms:created>2025-12-01T00:00:00Z</dcterms:created>
|
||
// <dcterms:modified>2025-12-01T00:00:00Z</dcterms:modified>
|
||
// <cp:category>Financial Reports</cp:category>
|
||
// <cp:contentStatus>Final</cp:contentStatus>
|
||
// </cp:coreProperties>
|
||
```
|
||
|
||
### 5.2 ExtendedFilePropertiesPart (Application Properties)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// EXTENDED FILE PROPERTIES (APPLICATION PROPERTIES)
|
||
// =============================================================================
|
||
// Extended properties are stored in docProps/app.xml.
|
||
// They include application-specific metadata like Company, Manager, etc.
|
||
|
||
using DocumentFormat.OpenXml.ExtendedProperties;
|
||
|
||
var extPart = doc.ExtendedFilePropertiesPart
|
||
?? doc.AddExtendedFilePropertiesPart();
|
||
|
||
extPart.Properties = new DocumentFormat.OpenXml.ExtendedProperties.Properties();
|
||
|
||
// Company name
|
||
extPart.Properties.AppendChild(
|
||
new DocumentFormat.OpenXml.ExtendedProperties.Company("ACME Corporation"));
|
||
|
||
// Manager
|
||
extPart.Properties.AppendChild(
|
||
new DocumentFormat.OpenXml.ExtendedProperties.Manager("Alice Johnson"));
|
||
|
||
// Application name
|
||
extPart.Properties.AppendChild(
|
||
new DocumentFormat.OpenXml.ExtendedProperties.Application("Custom Document Generator"));
|
||
|
||
// Application version
|
||
extPart.Properties.AppendChild(
|
||
new DocumentFormat.OpenXml.ExtendedProperties.ApplicationVersion("1.0.0"));
|
||
|
||
// Total editing time in minutes
|
||
extPart.Properties.AppendChild(
|
||
new DocumentFormat.OpenXml.ExtendedProperties.TotalTime("0"));
|
||
|
||
// Pages, Words, Characters (these are hints; Word recalculates them)
|
||
extPart.Properties.AppendChild(
|
||
new DocumentFormat.OpenXml.ExtendedProperties.Pages("1"));
|
||
extPart.Properties.AppendChild(
|
||
new DocumentFormat.OpenXml.ExtendedProperties.Words("0"));
|
||
extPart.Properties.AppendChild(
|
||
new DocumentFormat.OpenXml.ExtendedProperties.Characters("0"));
|
||
|
||
extPart.Properties.Save();
|
||
|
||
// Produces docProps/app.xml:
|
||
// <Properties>
|
||
// <Company>ACME Corporation</Company>
|
||
// <Manager>Alice Johnson</Manager>
|
||
// <Application>Custom Document Generator</Application>
|
||
// <AppVersion>1.0.0</AppVersion>
|
||
// <TotalTime>0</TotalTime>
|
||
// <Pages>1</Pages>
|
||
// </Properties>
|
||
```
|
||
|
||
### 5.3 CustomFilePropertiesPart (Custom Key-Value Properties)
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// CUSTOM FILE PROPERTIES (KEY-VALUE PAIRS)
|
||
// =============================================================================
|
||
// Custom properties allow arbitrary key-value metadata.
|
||
// Stored in docProps/custom.xml. Useful for document management systems,
|
||
// workflow tracking, template variables, etc.
|
||
//
|
||
// Supported value types: Text (string), Number (int), Date (DateTime),
|
||
// Boolean (bool), Filetime
|
||
|
||
using DocumentFormat.OpenXml.CustomProperties;
|
||
using DocumentFormat.OpenXml.VariantTypes;
|
||
|
||
var customPart = doc.CustomFilePropertiesPart
|
||
?? doc.AddCustomFilePropertiesPart();
|
||
|
||
customPart.Properties = new DocumentFormat.OpenXml.CustomProperties.Properties();
|
||
|
||
// Property IDs must start at 2 and increment
|
||
int propertyId = 2;
|
||
|
||
// --- String property ---
|
||
customPart.Properties.AppendChild(new CustomDocumentProperty
|
||
{
|
||
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
|
||
PropertyId = propertyId++,
|
||
Name = "Department",
|
||
VTLPWSTR = new VTLPWSTR("Engineering")
|
||
});
|
||
|
||
// --- Integer property ---
|
||
customPart.Properties.AppendChild(new CustomDocumentProperty
|
||
{
|
||
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
|
||
PropertyId = propertyId++,
|
||
Name = "DocumentVersion",
|
||
VTInt32 = new VTInt32("5")
|
||
});
|
||
|
||
// --- Boolean property ---
|
||
customPart.Properties.AppendChild(new CustomDocumentProperty
|
||
{
|
||
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
|
||
PropertyId = propertyId++,
|
||
Name = "Approved",
|
||
VTBool = new VTBool("true")
|
||
});
|
||
|
||
// --- DateTime property ---
|
||
customPart.Properties.AppendChild(new CustomDocumentProperty
|
||
{
|
||
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
|
||
PropertyId = propertyId++,
|
||
Name = "ApprovalDate",
|
||
VTFileTime = new VTFileTime(DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"))
|
||
});
|
||
|
||
// --- Double/Float property ---
|
||
customPart.Properties.AppendChild(new CustomDocumentProperty
|
||
{
|
||
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
|
||
PropertyId = propertyId++,
|
||
Name = "ConfidenceScore",
|
||
VTDouble = new VTDouble("0.95")
|
||
});
|
||
|
||
customPart.Properties.Save();
|
||
|
||
// Produces docProps/custom.xml:
|
||
// <Properties>
|
||
// <property fmtid="{D5CDD505-...}" pid="2" name="Department">
|
||
// <vt:lpwstr>Engineering</vt:lpwstr>
|
||
// </property>
|
||
// <property fmtid="{D5CDD505-...}" pid="3" name="DocumentVersion">
|
||
// <vt:i4>5</vt:i4>
|
||
// </property>
|
||
// <property fmtid="{D5CDD505-...}" pid="4" name="Approved">
|
||
// <vt:bool>true</vt:bool>
|
||
// </property>
|
||
// ...
|
||
// </Properties>
|
||
```
|
||
|
||
### 5.4 Reading and Modifying Existing Properties
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// READING AND MODIFYING EXISTING PROPERTIES
|
||
// =============================================================================
|
||
|
||
using var existingDoc = WordprocessingDocument.Open("existing.docx", isEditable: true);
|
||
|
||
// --- Read core properties ---
|
||
string? title = existingDoc.PackageProperties.Title;
|
||
string? author = existingDoc.PackageProperties.Creator;
|
||
DateTime? modified = existingDoc.PackageProperties.Modified;
|
||
|
||
// --- Modify core properties ---
|
||
existingDoc.PackageProperties.Title = "Updated Title";
|
||
existingDoc.PackageProperties.Modified = DateTime.UtcNow;
|
||
|
||
// --- Read custom properties ---
|
||
var customProps = existingDoc.CustomFilePropertiesPart?.Properties;
|
||
if (customProps != null)
|
||
{
|
||
foreach (var prop in customProps.Elements<CustomDocumentProperty>())
|
||
{
|
||
string name = prop.Name!;
|
||
// Value is in one of the VT* child elements
|
||
string? textValue = prop.VTLPWSTR?.Text;
|
||
string? intValue = prop.VTInt32?.Text;
|
||
string? boolValue = prop.VTBool?.Text;
|
||
// Use whichever is non-null
|
||
}
|
||
}
|
||
|
||
// --- Update a specific custom property ---
|
||
var deptProp = customProps?.Elements<CustomDocumentProperty>()
|
||
.FirstOrDefault(p => p.Name == "Department");
|
||
if (deptProp != null)
|
||
{
|
||
deptProp.VTLPWSTR = new VTLPWSTR("Marketing");
|
||
customProps!.Save();
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Printing & Compatibility Settings
|
||
|
||
### 6.1 DocumentSettingsPart — Zoom, TabStop, ProofState
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// DOCUMENT SETTINGS — ZOOM, TAB STOPS, PROOF STATE
|
||
// =============================================================================
|
||
// DocumentSettingsPart (settings.xml) contains document-wide settings.
|
||
// These control behavior in Word's UI and rendering.
|
||
|
||
var mainPart2 = doc.MainDocumentPart!;
|
||
var settingsPart2 = mainPart2.DocumentSettingsPart
|
||
?? mainPart2.AddNewPart<DocumentSettingsPart>();
|
||
settingsPart2.Settings ??= new Settings();
|
||
var settings = settingsPart2.Settings;
|
||
|
||
// --- Zoom level ---
|
||
// Val = percentage (100 = 100%). Percent is a string.
|
||
settings.AppendChild(new Zoom
|
||
{
|
||
Percent = "120", // 120% zoom
|
||
Val = PresetZoomValues.BestFit // Or: None, FullPage, BestFit, TextFit
|
||
});
|
||
// Produces XML: <w:zoom w:percent="120" w:val="bestFit" />
|
||
|
||
// --- Default Tab Stop ---
|
||
// Distance in DXA for default tab stops. Standard is 720 (0.5 inch).
|
||
settings.AppendChild(new DefaultTabStop
|
||
{
|
||
Val = 720 // 0.5 inch
|
||
});
|
||
// Produces XML: <w:defaultTabStop w:val="720" />
|
||
|
||
// For Chinese documents, common default tab stop is 420 (about 2 characters wide)
|
||
// settings.AppendChild(new DefaultTabStop { Val = 420 });
|
||
|
||
// --- Proof State ---
|
||
// Tells Word the spell/grammar check status. Setting both to "clean"
|
||
// prevents Word from showing the "Proofing" notification on open.
|
||
settings.AppendChild(new ProofState
|
||
{
|
||
Spelling = ProofingStateValues.Clean,
|
||
Grammar = ProofingStateValues.Clean
|
||
});
|
||
// Produces XML: <w:proofState w:spelling="clean" w:grammar="clean" />
|
||
|
||
// --- Character Spacing Control ---
|
||
// CompressWhitespace: whether to compress whitespace at line edges (CJK)
|
||
settings.AppendChild(new CharacterSpacingControl
|
||
{
|
||
Val = CharacterSpacingValues.DoNotCompress
|
||
});
|
||
|
||
// --- Remove personal information on save ---
|
||
settings.AppendChild(new RemovePersonalInformation());
|
||
// Produces XML: <w:removePersonalInformation />
|
||
|
||
settings.Save();
|
||
```
|
||
|
||
### 6.2 Compatibility Settings
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// COMPATIBILITY SETTINGS
|
||
// =============================================================================
|
||
// Compatibility settings control how Word renders the document,
|
||
// providing backward compatibility with older Word versions.
|
||
// CompatibilityMode is the most important setting.
|
||
|
||
var compat = new Compatibility();
|
||
|
||
// --- Compatibility Mode ---
|
||
// 15 = Word 2013+ mode (current standard)
|
||
// 14 = Word 2010 mode
|
||
// 12 = Word 2007 mode
|
||
// Omitted or 0 = oldest compatibility
|
||
compat.AppendChild(new CompatibilitySetting
|
||
{
|
||
Name = CompatSettingNameValues.CompatibilityMode,
|
||
Uri = "http://schemas.microsoft.com/office/word",
|
||
Val = "15" // Word 2013+ mode
|
||
});
|
||
|
||
// --- Override page break rules ---
|
||
// Useful compatibility flags for consistent rendering:
|
||
|
||
// UseWord2002TableStyleRules: use newer table style rules
|
||
compat.AppendChild(new CompatibilitySetting
|
||
{
|
||
Name = CompatSettingNameValues.OverrideTableStyleFontSizeAndJustification,
|
||
Uri = "http://schemas.microsoft.com/office/word",
|
||
Val = "1"
|
||
});
|
||
|
||
// EnableOpenTypeFeatures: enable advanced typography
|
||
compat.AppendChild(new CompatibilitySetting
|
||
{
|
||
Name = CompatSettingNameValues.EnableOpenTypeFeatures,
|
||
Uri = "http://schemas.microsoft.com/office/word",
|
||
Val = "1"
|
||
});
|
||
|
||
// DifferentiateMultirowTableHeaders
|
||
compat.AppendChild(new CompatibilitySetting
|
||
{
|
||
Name = CompatSettingNameValues.DifferentiateMultirowTableHeaders,
|
||
Uri = "http://schemas.microsoft.com/office/word",
|
||
Val = "1"
|
||
});
|
||
|
||
// UseWord2013TrackBottomHyphenation
|
||
compat.AppendChild(new CompatibilitySetting
|
||
{
|
||
Name = CompatSettingNameValues.UseWord2013TrackBottomHyphenation,
|
||
Uri = "http://schemas.microsoft.com/office/word",
|
||
Val = "0"
|
||
});
|
||
|
||
settings.AppendChild(compat);
|
||
settings.Save();
|
||
|
||
// Produces XML:
|
||
// <w:compat>
|
||
// <w:compatSetting w:name="compatibilityMode"
|
||
// w:uri="http://schemas.microsoft.com/office/word" w:val="15" />
|
||
// <w:compatSetting w:name="overrideTableStyleFontSizeAndJustification"
|
||
// w:uri="http://schemas.microsoft.com/office/word" w:val="1" />
|
||
// ...
|
||
// </w:compat>
|
||
```
|
||
|
||
### 6.3 MirrorMargins, GutterAtTop, BookFoldPrinting
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// MIRROR MARGINS, GUTTER, BOOK FOLD
|
||
// =============================================================================
|
||
// These settings are for documents intended for double-sided printing or
|
||
// book binding.
|
||
|
||
// --- Mirror Margins ---
|
||
// When enabled, Left/Right margins become Inside/Outside.
|
||
// On even pages, margins are swapped so the binding edge stays consistent.
|
||
settings.AppendChild(new MirrorMargins());
|
||
// Produces XML: <w:mirrorMargins />
|
||
// After this, PageMargin.Left = inside margin, PageMargin.Right = outside margin.
|
||
// Even pages automatically flip them.
|
||
|
||
// --- Gutter at Top ---
|
||
// By default, gutter is added to the left (or inside with MirrorMargins).
|
||
// GutterAtTop moves the gutter to the top margin instead.
|
||
// Used for calendar-style or top-bound documents.
|
||
settings.AppendChild(new GutterAtTop());
|
||
// Produces XML: <w:gutterAtTop />
|
||
|
||
// --- Book Fold Printing ---
|
||
// Enables booklet printing (2 pages per sheet, folded in half).
|
||
// Word reorders pages for saddle-stitch binding.
|
||
settings.AppendChild(new BookFoldPrinting());
|
||
// Produces XML: <w:bookFoldPrinting />
|
||
|
||
// Optional: sheets per booklet (for thick documents split into signatures)
|
||
// Default 0 = all pages in one booklet
|
||
settings.AppendChild(new BookFoldPrintingSheets { Val = 16 });
|
||
// Produces XML: <w:bookFoldPrintingSheets w:val="16" />
|
||
// 16 sheets = 64 pages per booklet signature
|
||
|
||
// --- Book Fold Reversed ---
|
||
// For right-to-left booklets (Hebrew, Arabic, Japanese right-bound)
|
||
// settings.AppendChild(new BookFoldRevPrinting());
|
||
|
||
settings.Save();
|
||
```
|
||
|
||
### 6.4 Additional Document Settings
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// ADDITIONAL DOCUMENT SETTINGS
|
||
// =============================================================================
|
||
|
||
// --- Update Fields on Open ---
|
||
// Forces Word to recalculate all fields (TOC, page numbers, etc.) on open.
|
||
settings.AppendChild(new UpdateFieldsOnOpen { Val = true });
|
||
// Produces XML: <w:updateFields w:val="true" />
|
||
// WARNING: This causes a dialog popup in some Word versions.
|
||
|
||
// --- Do Not Track Moves ---
|
||
// Prevents move tracking in track changes (shows as delete + insert instead).
|
||
// settings.AppendChild(new DoNotTrackMoves());
|
||
|
||
// --- Do Not Track Formatting ---
|
||
// Prevents formatting changes from being tracked.
|
||
// settings.AppendChild(new DoNotTrackFormatting());
|
||
|
||
// --- Default Zoom for Print Preview ---
|
||
// PrintTwoOnOne: print 2 pages per sheet
|
||
// settings.AppendChild(new PrintTwoOnOne());
|
||
|
||
// --- Document protection (read-only, forms-only, etc.) ---
|
||
// DocumentProtection can enforce read-only, allow only comments,
|
||
// restrict to form fields, etc.
|
||
// settings.AppendChild(new DocumentProtection
|
||
// {
|
||
// Edit = DocumentProtectionValues.ReadOnly,
|
||
// Enforcement = true
|
||
// });
|
||
|
||
// --- Even and Odd Headers (setting-level flag) ---
|
||
// Must be set here for the EvenPage header/footer references to work.
|
||
// settings.AppendChild(new EvenAndOddHeaders());
|
||
|
||
settings.Save();
|
||
```
|
||
|
||
### 6.5 Complete Settings Setup
|
||
|
||
```csharp
|
||
// =============================================================================
|
||
// COMPLETE SETTINGS SETUP — PRODUCTION-READY
|
||
// =============================================================================
|
||
// A comprehensive settings configuration suitable for most documents.
|
||
|
||
static void ConfigureDocumentSettings(WordprocessingDocument doc)
|
||
{
|
||
var mainPart = doc.MainDocumentPart!;
|
||
var settingsPart = mainPart.DocumentSettingsPart
|
||
?? mainPart.AddNewPart<DocumentSettingsPart>();
|
||
settingsPart.Settings = new Settings();
|
||
var s = settingsPart.Settings;
|
||
|
||
// Zoom to 100%
|
||
s.AppendChild(new Zoom
|
||
{
|
||
Percent = "100",
|
||
Val = PresetZoomValues.None
|
||
});
|
||
|
||
// Default tab stop: 0.5 inch
|
||
s.AppendChild(new DefaultTabStop { Val = 720 });
|
||
|
||
// Mark spell/grammar as clean
|
||
s.AppendChild(new ProofState
|
||
{
|
||
Spelling = ProofingStateValues.Clean,
|
||
Grammar = ProofingStateValues.Clean
|
||
});
|
||
|
||
// Character spacing control
|
||
s.AppendChild(new CharacterSpacingControl
|
||
{
|
||
Val = CharacterSpacingValues.DoNotCompress
|
||
});
|
||
|
||
// Compatibility: Word 2013+ mode with useful features
|
||
var compat = new Compatibility();
|
||
foreach (var (name, val) in new[]
|
||
{
|
||
(CompatSettingNameValues.CompatibilityMode, "15"),
|
||
(CompatSettingNameValues.OverrideTableStyleFontSizeAndJustification, "1"),
|
||
(CompatSettingNameValues.EnableOpenTypeFeatures, "1"),
|
||
(CompatSettingNameValues.DifferentiateMultirowTableHeaders, "1"),
|
||
(CompatSettingNameValues.UseWord2013TrackBottomHyphenation, "0"),
|
||
})
|
||
{
|
||
compat.AppendChild(new CompatibilitySetting
|
||
{
|
||
Name = name,
|
||
Uri = "http://schemas.microsoft.com/office/word",
|
||
Val = val
|
||
});
|
||
}
|
||
s.AppendChild(compat);
|
||
|
||
s.Save();
|
||
}
|
||
|
||
// Usage:
|
||
ConfigureDocumentSettings(doc);
|
||
```
|
||
|
||
---
|
||
|
||
## Quick Reference: Unit Conversions
|
||
|
||
| Unit | Full Name | Relation |
|
||
|------|-----------|----------|
|
||
| DXA | Twentieths of a point | 1 inch = 1440 DXA; 1 cm = 567 DXA; 1 pt = 20 DXA |
|
||
| Half-point | Half a typographic point | Font size: 24 = 12pt. 1 pt = 2 half-points |
|
||
| Eighth-point | Eighth of a point | Border size: 8 = 1pt. 1 pt = 8 eighth-points |
|
||
| EMU | English Metric Unit | 1 inch = 914400 EMU; 1 cm = 360000 EMU; 1 pt = 12700 EMU |
|
||
| Pct (table width) | Fiftieths of a percent | 5000 = 100%. Multiply percentage by 50 |
|
||
|
||
## Quick Reference: Common PageSize Values
|
||
|
||
| Paper | Width (DXA) | Height (DXA) | Inches | mm |
|
||
|-------|-------------|--------------|--------|-----|
|
||
| Letter | 12240 | 15840 | 8.5 x 11 | 216 x 279 |
|
||
| A4 | 11906 | 16838 | -- | 210 x 297 |
|
||
| Legal | 12240 | 20160 | 8.5 x 14 | 216 x 356 |
|
||
| A3 | 16838 | 23811 | -- | 297 x 420 |
|
||
| B5 | 10318 | 14570 | -- | 182 x 257 |
|
||
|
||
## Quick Reference: Common Margin Presets (DXA)
|
||
|
||
| Preset | Top | Bottom | Left | Right |
|
||
|--------|-----|--------|------|-------|
|
||
| Normal | 1440 | 1440 | 1440 | 1440 |
|
||
| Narrow | 720 | 720 | 720 | 720 |
|
||
| Moderate | 1440 | 1440 | 1080 | 1080 |
|
||
| Wide | 1440 | 1440 | 2880 | 2880 |
|
||
| Chinese 公文 | 2098 | 1984 | 1588 | 1474 |
|