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; } }