using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.CustomProperties;
using DocumentFormat.OpenXml.ExtendedProperties;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.VariantTypes;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniMaxAIDocx.Core.OpenXml;
using MiniMaxAIDocx.Core.Typography;
using WpPageSize = DocumentFormat.OpenXml.Wordprocessing.PageSize;
namespace MiniMaxAIDocx.Core.Samples;
///
/// Compilable reference examples for DOCX document creation and setup.
/// Every method is self-contained and demonstrates a specific aspect of
/// document creation using the OpenXML SDK 3.x strongly-typed API.
///
public static class DocumentCreationSamples
{
// ────────────────────────────────────────────────────────────────────
// 1. MINIMAL DOCUMENT
// ────────────────────────────────────────────────────────────────────
///
/// Creates the absolute minimum valid DOCX file: a single empty paragraph
/// inside a body, with a final section properties element.
/// This is the smallest file Word/LibreOffice will open without error.
///
///
/// Produces this XML in word/document.xml:
///
/// <w:document>
/// <w:body>
/// <w:p/>
/// <w:sectPr>
/// <w:pgSz w:w="12240" w:h="15840"/>
/// <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
/// w:header="720" w:footer="720" w:gutter="0"/>
/// </w:sectPr>
/// </w:body>
/// </w:document>
///
///
public static void CreateMinimalDocument(string path)
{
// IMPORTANT: OpenXML SDK 3.x uses IDisposable — always wrap in using.
// Never call .Close() — it was removed in SDK 3.x.
using var doc = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document);
// Every DOCX needs exactly one MainDocumentPart
var mainPart = doc.AddMainDocumentPart();
// The Document element is the root of word/document.xml
mainPart.Document = new Document(
new Body(
// At least one paragraph is required for a valid document
new Paragraph(),
// SectionProperties must be the LAST child of Body
// WARNING: If sectPr is not last, Word may silently move it or corrupt the file
new SectionProperties(
// PageSize: Letter = 8.5" x 11" = 12240 x 15840 DXA (1 inch = 1440 DXA)
new WpPageSize
{
Width = (UInt32Value)12240U,
Height = (UInt32Value)15840U
},
// PageMargin: 1 inch all sides = 1440 DXA each
// Header/footer distance: 0.5 inch = 720 DXA
new PageMargin
{
Top = 1440,
Right = (UInt32Value)1440U,
Bottom = 1440,
Left = (UInt32Value)1440U,
Header = (UInt32Value)720U,
Footer = (UInt32Value)720U,
Gutter = (UInt32Value)0U
}
)
)
);
// Save is called automatically by Dispose, but explicit save ensures
// all parts are flushed before the stream closes
mainPart.Document.Save();
}
// ────────────────────────────────────────────────────────────────────
// 2. FULL DOCUMENT (all parts)
// ────────────────────────────────────────────────────────────────────
///
/// Creates a production-ready DOCX with all standard parts:
/// styles, settings, numbering definitions, font table, and theme.
/// This mirrors the structure Word generates for a "New Blank Document".
///
public static void CreateFullDocument(string path)
{
using var doc = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
// ── StyleDefinitionsPart ──
// Contains all named styles (Normal, Heading1, etc.)
var stylesPart = mainPart.AddNewPart();
stylesPart.Styles = new Styles();
// Populate styles using the StyleSystemSamples helper
StyleSystemSamples.SetupDocDefaults(stylesPart);
StyleSystemSamples.CreateBasicStyles(stylesPart);
stylesPart.Styles.Save();
// ── DocumentSettingsPart ──
// Contains zoom, compatibility, proofing state, etc.
var settingsPart = mainPart.AddNewPart();
settingsPart.Settings = new Settings();
AddDocumentSettings(mainPart);
settingsPart.Settings.Save();
// ── NumberingDefinitionsPart ──
// Required if any paragraph uses numbered/bulleted lists
var numberingPart = mainPart.AddNewPart();
numberingPart.Numbering = new Numbering();
// Add a basic bullet list abstract numbering definition
var abstractNum = new AbstractNum(
new Level(
new NumberingFormat { Val = NumberFormatValues.Bullet },
new LevelText { Val = "\u2022" }, // bullet character
new LevelJustification { Val = LevelJustificationValues.Left },
new ParagraphProperties(
new Indentation
{
Left = "720", // 0.5 inch = 720 DXA
Hanging = "360" // 0.25 inch hanging indent
}
)
)
{ LevelIndex = 0 }
)
{ AbstractNumberId = 1 };
// IMPORTANT: AbstractNum elements must come BEFORE NumberingInstance elements
// in the Numbering part, or Word will report corruption
numberingPart.Numbering.Append(abstractNum);
numberingPart.Numbering.Append(
new NumberingInstance(
new AbstractNumId { Val = 1 }
)
{ NumberID = 1 }
);
numberingPart.Numbering.Save();
// ── FontTablePart ──
// Declares fonts used in the document; Word auto-populates on save,
// but pre-creating it avoids a repair prompt
var fontTablePart = mainPart.AddNewPart();
fontTablePart.Fonts = new Fonts(
new Font(
new Panose1Number { Val = "020B0604020202020204" },
new FontCharSet { Val = "00" },
new FontFamily { Val = FontFamilyValues.Swiss }
)
{ Name = "Calibri" },
new Font(
new Panose1Number { Val = "020B0604020202020204" },
new FontCharSet { Val = "00" },
new FontFamily { Val = FontFamilyValues.Swiss }
)
{ Name = "Calibri Light" }
);
fontTablePart.Fonts.Save();
// ── ThemePart ──
// Defines the document's theme colors and fonts.
// IMPORTANT: We use a minimal theme; for full Office themes, copy from a .docx template
var themePart = mainPart.AddNewPart();
// Write minimal theme XML directly since the strongly-typed API for themes
// lives in DocumentFormat.OpenXml.Drawing and is very verbose
using (var writer = new System.IO.StreamWriter(themePart.GetStream()))
{
writer.Write("""
""");
}
// ── Document body ──
mainPart.Document = new Document(
new Body(
new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("Sample Document"))
),
new Paragraph(
new Run(new Text("This document includes all standard parts."))
),
// Final section properties
new SectionProperties(
new WpPageSize
{
Width = (UInt32Value)(uint)PageSizes.A4.WidthDxa,
Height = (UInt32Value)(uint)PageSizes.A4.HeightDxa
},
new PageMargin
{
Top = PageSizes.StandardMargins.TopDxa,
Right = (UInt32Value)(uint)PageSizes.StandardMargins.RightDxa,
Bottom = PageSizes.StandardMargins.BottomDxa,
Left = (UInt32Value)(uint)PageSizes.StandardMargins.LeftDxa,
Header = (UInt32Value)720U,
Footer = (UInt32Value)720U,
Gutter = (UInt32Value)0U
}
)
)
);
// Set document-level metadata
SetDocumentProperties(doc);
mainPart.Document.Save();
}
// ────────────────────────────────────────────────────────────────────
// 3. CREATE FROM STREAM (for web/API)
// ────────────────────────────────────────────────────────────────────
///
/// Creates a DOCX entirely in memory, returning a MemoryStream.
/// Ideal for ASP.NET / Web API scenarios where you return FileStreamResult.
///
///
/// Usage in ASP.NET:
///
/// var stream = DocumentCreationSamples.CreateFromStream();
/// return File(stream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "report.docx");
///
///
public static MemoryStream CreateFromStream()
{
// IMPORTANT: The MemoryStream must remain open after WordprocessingDocument is disposed.
// Do NOT wrap the stream in a using statement here — the caller owns its lifetime.
var stream = new MemoryStream();
// WARNING: You MUST pass 'true' for the 'leaveOpen' parameter (via the overload that
// accepts a stream) so that disposing the WordprocessingDocument does NOT close the stream.
// The Create overload with Stream does this correctly in SDK 3.x.
using (var doc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document))
{
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document(
new Body(
new Paragraph(
new Run(new Text("Generated in memory"))
),
new SectionProperties(
new WpPageSize
{
Width = (UInt32Value)(uint)PageSizes.Letter.WidthDxa,
Height = (UInt32Value)(uint)PageSizes.Letter.HeightDxa
},
new PageMargin
{
Top = 1440,
Right = (UInt32Value)1440U,
Bottom = 1440,
Left = (UInt32Value)1440U,
Header = (UInt32Value)720U,
Footer = (UInt32Value)720U,
Gutter = (UInt32Value)0U
}
)
)
);
mainPart.Document.Save();
}
// Disposing the WordprocessingDocument flushes all data to the stream.
// IMPORTANT: Reset position so the caller can read from the beginning.
stream.Position = 0;
return stream;
}
// ────────────────────────────────────────────────────────────────────
// 4. OPEN AND EDIT EXISTING DOCUMENT
// ────────────────────────────────────────────────────────────────────
///
/// Opens an existing DOCX, modifies its content, and saves.
/// Demonstrates the read-modify-write pattern.
///
public static void OpenAndEdit(string path)
{
// Open for editing (isEditable = true)
// WARNING: If the file is read-only on disk, this will throw IOException.
// For read-only access, pass false and use a copy-to-stream pattern instead.
using var doc = WordprocessingDocument.Open(path, isEditable: true);
var body = doc.MainDocumentPart?.Document.Body;
if (body is null)
return;
// Add a new paragraph at the end, BEFORE the final SectionProperties
// WARNING: Always insert before sectPr — it must remain the last child of Body
var sectPr = body.Elements().FirstOrDefault();
var newParagraph = new Paragraph(
new ParagraphProperties(
// Apply Normal style explicitly (usually inherited, but being explicit is safer)
new ParagraphStyleId { Val = "Normal" },
new Justification { Val = JustificationValues.Left }
),
new Run(
new RunProperties(
new Bold(),
new Color { Val = "FF0000" } // red, RGB hex without #
),
// IMPORTANT: When text has leading/trailing whitespace, you must set
// SpaceProcessingModeValues.Preserve or Word will strip it
new Text("This paragraph was added programmatically.") { Space = SpaceProcessingModeValues.Preserve }
)
);
if (sectPr is not null)
{
// Insert before the final section properties
body.InsertBefore(newParagraph, sectPr);
}
else
{
// No sectPr found (unusual but possible); just append
body.Append(newParagraph);
}
// Modify an existing paragraph — change the text of the first paragraph
var firstPara = body.Elements().FirstOrDefault();
if (firstPara is not null)
{
var firstRun = firstPara.Elements().FirstOrDefault();
if (firstRun is not null)
{
var textElement = firstRun.Elements().FirstOrDefault();
if (textElement is not null)
{
textElement.Text = "Modified: " + textElement.Text;
}
}
}
// Save is called automatically on Dispose, but calling explicitly
// ensures errors surface at a known point
doc.MainDocumentPart!.Document.Save();
}
// ────────────────────────────────────────────────────────────────────
// 5. DOCUMENT DEFAULTS (DocDefaults)
// ────────────────────────────────────────────────────────────────────
///
/// Sets RunPropertiesDefault and ParagraphPropertiesDefault in the styles part.
/// These defaults apply to ALL paragraphs and runs unless overridden by a style or direct formatting.
///
///
/// Produces in styles.xml:
///
/// <w:docDefaults>
/// <w:rPrDefault>
/// <w:rPr>
/// <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="SimSun" w:cs="Arial"/>
/// <w:sz w:val="22"/>
/// <w:szCs w:val="22"/>
/// <w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA"/>
/// </w:rPr>
/// </w:rPrDefault>
/// <w:pPrDefault>
/// <w:pPr>
/// <w:spacing w:after="160" w:line="259" w:lineRule="auto"/>
/// </w:pPr>
/// </w:pPrDefault>
/// </w:docDefaults>
///
///
public static void SetDocDefaults(MainDocumentPart mainPart)
{
// Ensure StyleDefinitionsPart exists
var stylesPart = mainPart.StyleDefinitionsPart
?? mainPart.AddNewPart();
stylesPart.Styles ??= new Styles();
var docDefaults = new DocDefaults();
// ── Run Properties Default ──
// These become the "base" formatting for all text in the document
var runPropsDefault = new RunPropertiesDefault(
new RunPropertiesBaseStyle(
// RunFonts has 4 slots for different Unicode ranges:
// Ascii — Latin characters (U+0000–U+007F)
// HighAnsi — extended Latin (U+0080–U+FFFF, non-EastAsian)
// EastAsia — CJK characters
// ComplexScript — RTL scripts (Arabic, Hebrew, etc.)
new RunFonts
{
Ascii = "Calibri",
HighAnsi = "Calibri",
EastAsia = "SimSun", // 宋体 — standard CJK body font
ComplexScript = "Arial"
},
// Font size is in HALF-POINTS: 22 half-pt = 11pt
new FontSize { Val = "22" },
// Complex script size (for Arabic/Hebrew text)
new FontSizeComplexScript { Val = "22" },
// Language tags control spell-check and hyphenation
new Languages
{
Val = "en-US",
EastAsia = "zh-CN",
Bidi = "ar-SA"
}
)
);
// ── Paragraph Properties Default ──
// Spacing that applies to all paragraphs unless overridden
var paraPropsDefault = new ParagraphPropertiesDefault(
new ParagraphPropertiesBaseStyle(
new SpacingBetweenLines
{
// After = space after paragraph in DXA twentieths-of-a-point
// 160 DXA = 8pt after each paragraph (Word 2016+ default)
After = "160",
// Line = line spacing:
// For "auto" rule: value is in 240ths of a line
// 259 = 1.0791... ≈ 1.08 line spacing (Word's "single" with body text font)
// For "exact"/"atLeast": value is in DXA twentieths-of-a-point
Line = "259",
LineRule = LineSpacingRuleValues.Auto
}
)
);
docDefaults.Append(runPropsDefault);
docDefaults.Append(paraPropsDefault);
// IMPORTANT: DocDefaults must be the FIRST child of w:styles.
// If there are existing children, prepend it.
var existingDocDefaults = stylesPart.Styles.DocDefaults;
if (existingDocDefaults is not null)
{
existingDocDefaults.Remove();
}
stylesPart.Styles.PrependChild(docDefaults);
stylesPart.Styles.Save();
}
// ────────────────────────────────────────────────────────────────────
// 6. DOCUMENT SETTINGS
// ────────────────────────────────────────────────────────────────────
///
/// Adds document-level settings: zoom, default tab stop, proofing,
/// compatibility options, field update behavior, and character spacing control.
///
///
/// Produces in word/settings.xml:
///
/// <w:settings>
/// <w:zoom w:percent="100"/>
/// <w:defaultTabStop w:val="720"/>
/// <w:characterSpacingControl w:val="doNotCompress"/>
/// <w:proofState w:spelling="clean" w:grammar="clean"/>
/// <w:updateFields w:val="true"/>
/// <w:compat>
/// <w:compatSetting w:name="compatibilityMode" w:uri="..." w:val="15"/>
/// </w:compat>
/// </w:settings>
///
///
public static void AddDocumentSettings(MainDocumentPart mainPart)
{
var settingsPart = mainPart.DocumentSettingsPart
?? mainPart.AddNewPart();
settingsPart.Settings ??= new Settings();
var settings = settingsPart.Settings;
// ── Zoom level ──
// 100 = 100%. Word remembers this for the next open.
settings.Append(new Zoom { Percent = "100" });
// ── Default tab stop ──
// 720 DXA = 0.5 inch. This is the interval for default tab stops
// across the entire document. Common values:
// 720 = 0.5 inch (US default)
// 420 = ~0.74cm (common in Chinese docs)
settings.Append(new DefaultTabStop { Val = 720 });
// ── Character spacing control ──
// Controls how CJK character spacing is handled
// DoNotCompress — no compression of punctuation
// CompressPunctuation — compress CJK punctuation at line start/end
// CompressPunctuationAndJapaneseKanaWhitespace — full CJK compression
settings.Append(new CharacterSpacingControl
{
Val = CharacterSpacingValues.DoNotCompress
});
// ── Proofing state ──
// Tells Word that spell/grammar check is clean; avoids the squiggly-line
// check running immediately on open
settings.Append(new ProofState
{
Spelling = ProofingStateValues.Clean,
Grammar = ProofingStateValues.Clean
});
// ── Update fields on open ──
// WARNING: Setting this to true causes Word to prompt "This document contains
// fields that may refer to other files. Do you want to update the fields?"
// Useful for TOC/TOF fields that need refreshing
settings.Append(new UpdateFieldsOnOpen { Val = true });
// ── Compatibility settings ──
// compatibilityMode = 15 means "Word 2013+ mode" — the highest stable mode.
// This controls layout behavior: line breaking, table widths, spacing, etc.
// WARNING: Using a lower value (e.g., 11 for Word 2003) changes layout
// significantly and is almost never what you want.
var compat = new Compatibility();
compat.Append(new CompatibilitySetting
{
Name = CompatSettingNameValues.CompatibilityMode,
Uri = "http://schemas.microsoft.com/office/word",
Val = "15"
});
// Additional CJK compatibility settings
compat.Append(new CompatibilitySetting
{
Name = CompatSettingNameValues.OverrideTableStyleFontSizeAndJustification,
Uri = "http://schemas.microsoft.com/office/word",
Val = "1"
});
compat.Append(new CompatibilitySetting
{
Name = CompatSettingNameValues.EnableOpenTypeFeatures,
Uri = "http://schemas.microsoft.com/office/word",
Val = "1"
});
settings.Append(compat);
settings.Save();
}
// ────────────────────────────────────────────────────────────────────
// 7. DOCUMENT PROPERTIES (metadata)
// ────────────────────────────────────────────────────────────────────
///
/// Sets core properties (Dublin Core: title, author, dates),
/// extended properties (company, application name),
/// and custom properties (arbitrary key-value pairs).
///
///
/// Core properties go into docProps/core.xml (Dublin Core + CP namespaces).
/// Extended properties go into docProps/app.xml.
/// Custom properties go into docProps/custom.xml.
///
public static void SetDocumentProperties(WordprocessingDocument doc)
{
// ── Core Properties ──
// These map to Dublin Core metadata elements in docProps/core.xml
doc.PackageProperties.Title = "Quarterly Report";
doc.PackageProperties.Subject = "Financial Summary";
doc.PackageProperties.Creator = "MiniMax AI"; // Author
doc.PackageProperties.Keywords = "report, finance, Q4";
doc.PackageProperties.Description = "Auto-generated financial report";
doc.PackageProperties.Category = "Reports";
doc.PackageProperties.ContentStatus = "Draft";
// Dates — PackageProperties uses DateTimeOffset?
doc.PackageProperties.Created = DateTimeOffset.UtcNow.DateTime;
doc.PackageProperties.Modified = DateTimeOffset.UtcNow.DateTime;
doc.PackageProperties.LastModifiedBy = "DocBuilder Agent";
// ── Extended Properties ──
// These go into docProps/app.xml
var extendedProps = doc.AddExtendedFilePropertiesPart();
extendedProps.Properties = new DocumentFormat.OpenXml.ExtendedProperties.Properties
{
Company = new Company("MiniMax Inc."),
Application = new Application("MiniMaxAIDocx"),
ApplicationVersion = new ApplicationVersion("1.0.0")
};
extendedProps.Properties.Save();
// ── Custom Properties ──
// Arbitrary key-value pairs; visible in File > Properties > Custom in Word
var customProps = doc.AddCustomFilePropertiesPart();
customProps.Properties = new DocumentFormat.OpenXml.CustomProperties.Properties();
// Each custom property needs a unique PID starting at 2
// (PID 0 and 1 are reserved by the system)
int pid = 2;
// String property
customProps.Properties.Append(new CustomDocumentProperty
{
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
PropertyId = pid++,
Name = "Department",
VTLPWSTR = new VTLPWSTR("Engineering")
});
// Integer property
customProps.Properties.Append(new CustomDocumentProperty
{
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
PropertyId = pid++,
Name = "ReviewCount",
VTInt32 = new VTInt32("3")
});
// Boolean property
customProps.Properties.Append(new CustomDocumentProperty
{
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
PropertyId = pid++,
Name = "IsApproved",
VTBool = new VTBool("true")
});
// Date property
customProps.Properties.Append(new CustomDocumentProperty
{
FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
PropertyId = pid++,
Name = "ReviewDate",
VTFileTime = new VTFileTime(DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"))
});
customProps.Properties.Save();
}
// ────────────────────────────────────────────────────────────────────
// 8. PAGE SETUP (sizes, margins, orientation)
// ────────────────────────────────────────────────────────────────────
///
/// Creates a document demonstrating various page setups:
/// A4/Letter sizes, standard/narrow/wide/公文 margins,
/// and both portrait and landscape orientations (as separate sections).
///
public static void CreateDocumentWithPageSetup(string path)
{
using var doc = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
var body = new Body();
// ════════════════════════════════════════════════════════════
// SECTION 1: A4 Portrait with Standard Margins (1 inch all)
// ════════════════════════════════════════════════════════════
body.Append(
new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("Section 1: A4 Portrait, Standard Margins"))
)
);
body.Append(
new Paragraph(new Run(new Text("Standard 1-inch margins all around.")))
);
// IMPORTANT: This is a "continuous" section break that ends section 1.
// The sectPr inside a pPr defines the properties FOR THE PRECEDING section.
// The FINAL section's properties are in the body-level sectPr.
body.Append(
new Paragraph(
new ParagraphProperties(
new SectionProperties(
new WpPageSize
{
// A4: 210mm x 297mm = 11906 x 16838 DXA
Width = (UInt32Value)(uint)PageSizes.A4.WidthDxa,
Height = (UInt32Value)(uint)PageSizes.A4.HeightDxa
// IMPORTANT: No Orient attribute = portrait (default)
},
new PageMargin
{
Top = PageSizes.StandardMargins.TopDxa, // 1440 = 1 inch
Right = (UInt32Value)(uint)PageSizes.StandardMargins.RightDxa,
Bottom = PageSizes.StandardMargins.BottomDxa,
Left = (UInt32Value)(uint)PageSizes.StandardMargins.LeftDxa,
Header = (UInt32Value)720U,
Footer = (UInt32Value)720U,
Gutter = (UInt32Value)0U
}
)
)
)
);
// ════════════════════════════════════════════════════════════
// SECTION 2: Letter Portrait with Narrow Margins
// ════════════════════════════════════════════════════════════
body.Append(
new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("Section 2: Letter Portrait, Narrow Margins"))
)
);
body.Append(
new Paragraph(new Run(new Text("Narrow 0.5-inch margins for maximum content area.")))
);
body.Append(
new Paragraph(
new ParagraphProperties(
new SectionProperties(
new WpPageSize
{
// US Letter: 8.5" x 11" = 12240 x 15840 DXA
Width = (UInt32Value)(uint)PageSizes.Letter.WidthDxa,
Height = (UInt32Value)(uint)PageSizes.Letter.HeightDxa
},
new PageMargin
{
Top = PageSizes.NarrowMargins.TopDxa, // 720 = 0.5 inch
Right = (UInt32Value)(uint)PageSizes.NarrowMargins.RightDxa,
Bottom = PageSizes.NarrowMargins.BottomDxa,
Left = (UInt32Value)(uint)PageSizes.NarrowMargins.LeftDxa,
Header = (UInt32Value)720U,
Footer = (UInt32Value)720U,
Gutter = (UInt32Value)0U
}
)
)
)
);
// ════════════════════════════════════════════════════════════
// SECTION 3: A4 Landscape with Wide Margins
// ════════════════════════════════════════════════════════════
body.Append(
new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("Section 3: A4 Landscape, Wide Margins"))
)
);
body.Append(
new Paragraph(new Run(new Text("Landscape orientation — width and height are SWAPPED.")))
);
body.Append(
new Paragraph(
new ParagraphProperties(
new SectionProperties(
new WpPageSize
{
// IMPORTANT: For landscape, SWAP width and height values
// AND set Orient = Landscape
Width = (UInt32Value)(uint)PageSizes.A4.HeightDxa, // 16838 (was height)
Height = (UInt32Value)(uint)PageSizes.A4.WidthDxa, // 11906 (was width)
Orient = PageOrientationValues.Landscape
},
new PageMargin
{
// Wide margins: 1" top/bottom, 1.5" left/right
Top = PageSizes.WideMargins.TopDxa, // 1440
Right = (UInt32Value)(uint)PageSizes.WideMargins.RightDxa, // 2160
Bottom = PageSizes.WideMargins.BottomDxa, // 1440
Left = (UInt32Value)(uint)PageSizes.WideMargins.LeftDxa, // 2160
Header = (UInt32Value)720U,
Footer = (UInt32Value)720U,
Gutter = (UInt32Value)0U
}
)
)
)
);
// ════════════════════════════════════════════════════════════
// SECTION 4 (FINAL): A4 Portrait with Chinese 公文 Margins
// ════════════════════════════════════════════════════════════
// Chinese government document standard (GB/T 9704-2012):
// Page: A4 (210mm x 297mm)
// Top: 37mm ≈ 2098 DXA Bottom: 35mm ≈ 1984 DXA
// Left: 28mm ≈ 1587 DXA Right: 26mm ≈ 1474 DXA
body.Append(
new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("Section 4: A4 Portrait, 公文 Margins (GB/T 9704)"))
)
);
body.Append(
new Paragraph(new Run(new Text("Chinese government document standard margins.")))
);
// The FINAL section's SectionProperties goes as a direct child of Body (not in pPr)
body.Append(
new SectionProperties(
new WpPageSize
{
Width = (UInt32Value)(uint)PageSizes.A4.WidthDxa,
Height = (UInt32Value)(uint)PageSizes.A4.HeightDxa
},
new PageMargin
{
// 公文 margins per GB/T 9704-2012
Top = UnitConverter.CmToDxa(3.7), // 37mm top
Right = (UInt32Value)(uint)UnitConverter.CmToDxa(2.6), // 26mm right
Bottom = UnitConverter.CmToDxa(3.5), // 35mm bottom
Left = (UInt32Value)(uint)UnitConverter.CmToDxa(2.8), // 28mm left
Header = (UInt32Value)(uint)UnitConverter.CmToDxa(1.5), // 15mm header
Footer = (UInt32Value)(uint)UnitConverter.CmToDxa(1.75),// 17.5mm footer
Gutter = (UInt32Value)0U
},
// Document grid for Chinese text layout:
// 28 lines per page, 28 characters per line (公文 standard)
new DocGrid
{
Type = DocGridValues.LinesAndChars,
LinePitch = 579, // vertical pitch in DXA: ~28 lines on A4
CharacterSpace = 210 // character spacing adjustment
}
)
);
mainPart.Document = new Document(body);
mainPart.Document.Save();
}
// ────────────────────────────────────────────────────────────────────
// 9. MULTI-SECTION DOCUMENT
// ────────────────────────────────────────────────────────────────────
///
/// Creates a document with three sections demonstrating:
/// - Portrait intro with "first page" header
/// - Landscape table section with different header
/// - Portrait conclusion with page number restart
/// Each section has its own headers and page numbering.
///
public static void CreateMultiSectionDocument(string path)
{
using var doc = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
// ── Create headers for each section ──
// Each section can reference its own header/footer parts.
// --- Section 1 headers: "first page" + "default" ---
var header1Default = CreateHeaderPart(mainPart, "Introduction — Page ");
var header1FirstPage = CreateHeaderPart(mainPart, "CONFIDENTIAL DRAFT");
// --- Section 2 header: landscape section ---
var header2Default = CreateHeaderPart(mainPart, "Data Tables — Landscape View");
// --- Section 3 header: conclusion ---
var header3Default = CreateHeaderPart(mainPart, "Conclusion — Page ");
var body = new Body();
// ════════════════════════════════════════════════════════════
// SECTION 1: Portrait Introduction
// ════════════════════════════════════════════════════════════
body.Append(
new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("Introduction"))
)
);
body.Append(
new Paragraph(new Run(new Text(
"This section uses portrait orientation with a special first-page header.")))
);
body.Append(
new Paragraph(new Run(new Text(
"The first page shows 'CONFIDENTIAL DRAFT', subsequent pages show the section name.")))
);
// Section 1 properties (embedded in paragraph = section break)
var sect1Props = new SectionProperties(
// Header references link to header parts via relationship IDs
// HeaderFooterType: Default = all pages, First = first page only, Even = even pages
new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(header1Default)
},
new HeaderReference
{
Type = HeaderFooterValues.First,
Id = mainPart.GetIdOfPart(header1FirstPage)
},
// IMPORTANT: SectionType controls how the section break renders:
// NextPage — starts on a new page (default if omitted)
// Continuous — no page break, flows on same page
// EvenPage / OddPage — starts on next even/odd page
new SectionType { Val = SectionMarkValues.NextPage },
new WpPageSize
{
Width = (UInt32Value)(uint)PageSizes.A4.WidthDxa,
Height = (UInt32Value)(uint)PageSizes.A4.HeightDxa
},
new PageMargin
{
Top = 1440, Right = (UInt32Value)1440U, Bottom = 1440,
Left = (UInt32Value)1440U, Header = (UInt32Value)720U,
Footer = (UInt32Value)720U, Gutter = (UInt32Value)0U
},
// PageNumberType: start numbering from 1
new PageNumberType { Start = 1 },
// TitlePage: enables the "Different First Page" header/footer
// Without this, the First header reference is ignored!
new TitlePage()
);
body.Append(new Paragraph(new ParagraphProperties(sect1Props)));
// ════════════════════════════════════════════════════════════
// SECTION 2: Landscape Data Tables
// ════════════════════════════════════════════════════════════
body.Append(
new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("Data Tables"))
)
);
// Add a simple table to justify landscape orientation
var table = new Table(
new TableProperties(
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
new TableBorders(
new TopBorder { Val = BorderValues.Single, Size = 4 },
new BottomBorder { Val = BorderValues.Single, Size = 4 },
new LeftBorder { Val = BorderValues.Single, Size = 4 },
new RightBorder { Val = BorderValues.Single, Size = 4 },
new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4 },
new InsideVerticalBorder { Val = BorderValues.Single, Size = 4 }
)
),
new TableGrid(
new GridColumn { Width = "3000" },
new GridColumn { Width = "3000" },
new GridColumn { Width = "3000" }
),
// Header row
new TableRow(
new TableCell(new Paragraph(new Run(new Text("Column A")))),
new TableCell(new Paragraph(new Run(new Text("Column B")))),
new TableCell(new Paragraph(new Run(new Text("Column C"))))
),
// Data row
new TableRow(
new TableCell(new Paragraph(new Run(new Text("Data 1")))),
new TableCell(new Paragraph(new Run(new Text("Data 2")))),
new TableCell(new Paragraph(new Run(new Text("Data 3"))))
)
);
body.Append(table);
// Section 2 properties (landscape)
var sect2Props = new SectionProperties(
new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(header2Default)
},
new SectionType { Val = SectionMarkValues.NextPage },
new WpPageSize
{
// IMPORTANT: Landscape = swap width/height AND set Orient
Width = (UInt32Value)(uint)PageSizes.A4.HeightDxa,
Height = (UInt32Value)(uint)PageSizes.A4.WidthDxa,
Orient = PageOrientationValues.Landscape
},
new PageMargin
{
Top = 1440, Right = (UInt32Value)1440U, Bottom = 1440,
Left = (UInt32Value)1440U, Header = (UInt32Value)720U,
Footer = (UInt32Value)720U, Gutter = (UInt32Value)0U
},
// Continue page numbering from previous section (no Start attribute)
new PageNumberType()
);
body.Append(new Paragraph(new ParagraphProperties(sect2Props)));
// ════════════════════════════════════════════════════════════
// SECTION 3 (FINAL): Portrait Conclusion with Restart Numbering
// ════════════════════════════════════════════════════════════
body.Append(
new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Heading1" }),
new Run(new Text("Conclusion"))
)
);
body.Append(
new Paragraph(new Run(new Text(
"This section restarts page numbering from 1.")))
);
// Final section: SectionProperties as direct child of Body
body.Append(
new SectionProperties(
new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(header3Default)
},
new WpPageSize
{
Width = (UInt32Value)(uint)PageSizes.A4.WidthDxa,
Height = (UInt32Value)(uint)PageSizes.A4.HeightDxa
},
new PageMargin
{
Top = 1440, Right = (UInt32Value)1440U, Bottom = 1440,
Left = (UInt32Value)1440U, Header = (UInt32Value)720U,
Footer = (UInt32Value)720U, Gutter = (UInt32Value)0U
},
// Restart page numbering from 1 for this section
new PageNumberType { Start = 1 }
)
);
mainPart.Document = new Document(body);
mainPart.Document.Save();
}
// ────────────────────────────────────────────────────────────────────
// HELPER: Create a header part with text and optional page number field
// ────────────────────────────────────────────────────────────────────
///
/// Creates a HeaderPart with the given text. If the text ends with a space,
/// a PAGE field is appended to show the page number.
///
private static HeaderPart CreateHeaderPart(MainDocumentPart mainPart, string text)
{
var headerPart = mainPart.AddNewPart();
var header = new Header();
var para = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }
)
);
// Add the text run
para.Append(new Run(
new RunProperties(
new FontSize { Val = "18" } // 9pt for header text
),
new Text(text) { Space = SpaceProcessingModeValues.Preserve }
));
// If text ends with space, add a PAGE field (auto page number)
if (text.EndsWith(' '))
{
// PAGE field uses three runs: begin, instruction, end
// This is the "complex field" pattern used by Word
para.Append(new Run(
new RunProperties(new FontSize { Val = "18" }),
new FieldChar { FieldCharType = FieldCharValues.Begin }
));
para.Append(new Run(
new RunProperties(new FontSize { Val = "18" }),
new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }
));
para.Append(new Run(
new RunProperties(new FontSize { Val = "18" }),
new FieldChar { FieldCharType = FieldCharValues.End }
));
}
header.Append(para);
headerPart.Header = header;
headerPart.Header.Save();
return headerPart;
}
}