# OpenXML SDK 3.x Complete Reference Encyclopedia **Target:** DocumentFormat.OpenXml 3.x / .NET 8+ / C# 12 **Last Updated:** 2026-03-22 This document serves as an exhaustive reference for building DOCX files with the OpenXML SDK. Every code block is ready to copy-paste. --- ## Namespace Aliases Used Throughout ```csharp using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; ``` --- ## Table of Contents 1. [Document Creation Skeleton](#1-document-creation-skeleton) 2. [Style System Deep Dive](#2-style-system-deep-dive) 3. [Character Formatting (RunProperties)](#3-character-formatting-runproperties--exhaustive) 4. [Paragraph Formatting (ParagraphProperties)](#4-paragraph-formatting-paragraphproperties--exhaustive) --- ## 1. Document Creation Skeleton ### 1.1 Complete Flow: Create to Save ```csharp // ============================================================================= // DOCUMENT CREATION SKELETON // ============================================================================= // This is the minimal complete flow for creating a valid DOCX from scratch. // Follow these steps in order: Create -> AddParts -> AddContent -> Save. // // Key insight: WordprocessingDocument.Create() adds MainDocumentPart automatically, // but all other parts (Styles, Settings, Numbering, Theme) must be added manually. // --- STEP 1: CREATE THE PACKAGE --- // The file path can be absolute or relative. WordprocessingDocumentType.Document // is the standard choice for .docx files (vs. Template, MacroEnabled, etc.) string outputPath = "C:\\Docs\\MyDocument.docx"; using var doc = WordprocessingDocument.Create( outputPath, // File path WordprocessingDocumentType.Document, // Document type enum new DocumentOptions // Optional: AutoSave, etc. { AutoSave = false // true = flush changes automatically }); // --- STEP 2: GET OR CREATE THE MAIN DOCUMENT PART --- // When you call Create(), MainDocumentPart is automatically created and linked. // You access it via .MainDocumentPart (not .AddMainDocumentPart, which would add // a SECOND main part — illegal). For a fresh document, just use .MainDocumentPart. var mainPart = doc.MainDocumentPart!; var body = mainPart.Document.Body!; // Body is created automatically with the part // --- STEP 3: ADD ADDITIONAL PARTS --- // These are OPTIONAL but recommended for a complete document: // - StyleDefinitionsPart: required for styles // - NumberingDefinitionsPart: required for bullets/numbers // - DocumentSettingsPart: zoom, proof state, tab stops, compatibility // - ThemePart: color/theme information // Parts are created fresh and linked via relationships. // Example: Add styles part (covered in Section 2) var stylesPart = mainPart.AddNewPart(); stylesPart.Styles = new Styles(); stylesPart.Styles.Save(); // Example: Add settings part (covered in 1.4) var settingsPart = mainPart.AddNewPart(); settingsPart.Settings = new Settings(); settingsPart.Settings.Save(); // --- STEP 4: ADD CONTENT TO BODY --- // Body accepts: Paragraph (w:p), Table (w:tbl), Structured Document Tag (w:sdt) // Content is added in document order (no need for explicit index). // IMPORTANT: SectionProperties (w:sectPr) MUST be the last child of body. body.Append(new Paragraph( new Run(new Text("Hello, World!")))); // --- STEP 5: SET SECTION PROPERTIES (PAGE LAYOUT) --- // sectPr defines page size, margins, headers/footers, columns, etc. // It must be the last child of body. If missing, Word uses defaults (Letter/A4, 1" margins). var sectPr = new SectionProperties(); // Page Size: Width/Height in DXA (1 inch = 1440 DXA) // Letter: 12240 x 15840 DXA (8.5" x 11") // A4: 11906 x 16838 DXA (210mm x 297mm) sectPr.Append(new PageSize { Width = 12240u, // 8.5 inches Height = 15840u // 11 inches }); // Page Margins: all four margins in DXA // Note: Top+Bottom margins + HeaderDistance = distance from page edge to text sectPr.Append(new PageMargin { Top = 1440, // 1 inch Bottom = 1440, // 1 inch Left = 1440u, // 1 inch (uint required) Right = 1440u, // 1 inch Header = 720u, // 0.5 inch from page edge to header Footer = 720u // 0.5 inch from page edge to footer }); // Attach sectPr to body (must be last) body.Append(sectPr); // --- STEP 6: SAVE --- // Because we use `using`, Dispose() is called automatically when the block exits. // Dispose() saves the file. If you forget `using`, call doc.Save() explicitly. ``` ### 1.2 Opening an Existing Document ```csharp // ============================================================================= // OPENING EXISTING DOCUMENTS // ============================================================================= // Open() has multiple overloads: // 1. Open(string path, bool isEditable, AutoSave) // 2. Open(Stream, bool isEditable, AutoSave) // 3. Open(string path, bool isEditable, OpenSettings) // // isEditable=true means open for read/write. false = read-only. // isEditable=false is faster (shared locks avoided) but throws if file is read-only. // --- OPEN FOR EDITING (READ/WRITE) --- string inputPath = "C:\\Docs\\Existing.docx"; using var editDoc = WordprocessingDocument.Open( inputPath, isEditable: true, // Required for modification new OpenSettings { AutoSave = true // Automatically save on Dispose }); var body = editDoc.MainDocumentPart!.Document.Body!; // ... make changes ... // No explicit Save() needed if AutoSave = true // --- OPEN AS READ-ONLY (FASTER) --- using var readOnlyDoc = WordprocessingDocument.Open( inputPath, isEditable: false, // Read-only mode new OpenSettings { // MarkupDeclarationProcess options }); // --- OPEN FROM STREAM --- byte[] fileBytes = File.ReadAllBytes(inputPath); using var streamDoc = WordprocessingDocument.Open( new MemoryStream(fileBytes), isEditable: true, new OpenSettings { AutoSave = false }); // After editing, you MUST copy the stream back to file if AutoSave=false: // streamDoc.MainDocumentPart.Document.Save(); // File.WriteAllBytes(outputPath, streamStream.ToArray()); // --- OPEN FROM HTTP RESPONSE (WEB SCENARIO) --- using var httpClient = new HttpClient(); var response = await httpClient.GetAsync("https://example.com/document.docx"); using var webStream = await response.Content.ReadAsStreamAsync(); using var webDoc = WordprocessingDocument.Open(webStream, isEditable: true); ``` ### 1.3 Stream-Based Creation (MemoryStream for Web) ```csharp // ============================================================================= // STREAM-BASED DOCUMENT CREATION // ============================================================================= // Use MemoryStream when you want to: // 1. Generate a document in memory before sending to a client // 2. Avoid touching the filesystem (ASP.NET Core scenarios) // 3. Return a document from an API endpoint // // CRITICAL: The stream MUST be seekable when you call .Open(). // After WordprocessingDocument.Create(), the stream position is at the beginning. // If you write to the stream BEFORE creating the document, seek to 0 first. // --- CREATE IN MEMORY --- MemoryStream memStream = new MemoryStream(); // Create directly on a stream (no file path involved) using (var doc = WordprocessingDocument.Create( memStream, WordprocessingDocumentType.Document, new DocumentOptions { AutoSave = false })) { var mainPart = doc.MainDocumentPart!; mainPart.Document = new Document(new Body()); mainPart.Document.Body!.Append(new Paragraph( new Run(new Text("Generated in memory")))); mainPart.Document.Save(); // Save to the underlying stream } // At this point, memStream contains the complete DOCX // --- SEND TO HTTP RESPONSE (ASP.NET Core) --- // In an API controller: [HttpGet("download")] public async Task DownloadDocument() { var memStream = new MemoryStream(); using (var doc = WordprocessingDocument.Create( memStream, WordprocessingDocumentType.Document)) { var mainPart = doc.MainDocumentPart!; mainPart.Document = new Document(new Body()); mainPart.Document.Body!.Append(new Paragraph( new Run(new Text("Download me!")))); mainPart.Document.Save(); } memStream.Position = 0; // IMPORTANT: Reset position for reading return File(memStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "GeneratedDocument.docx"); } // --- CREATE FROM TEMPLATE IN MEMORY --- // Useful for mail-merge style operations MemoryStream templateStream = new MemoryStream(); File.WriteAllBytes("template.docx", templateStream.ToArray()); // Save a template first using var templateSource = new MemoryStream(File.ReadAllBytes("template.docx")); using var mergedDoc = (WordprocessingDocument)templateSource.Clone(); // Clone() creates an editable copy. Don't forget to set position: mergedDoc.MainDocumentPart!.Document.Body!.Append(new Paragraph( new Run(new Text("Added content")))); ``` ### 1.4 Adding All Standard Parts ```csharp // ============================================================================= // ADDING ALL STANDARD DOCUMENT PARTS // ============================================================================= // A complete document should have: // 1. MainDocumentPart (auto-created) // 2. StyleDefinitionsPart // 3. NumberingDefinitionsPart // 4. DocumentSettingsPart // 5. ThemePart (optional) // 6. Custom parts (headers, footers, comments, etc.) // --- COMPLETE SETUP METHOD --- public static void CreateCompleteDocument(string path) { using var doc = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document); var mainPart = doc.MainDocumentPart!; // Initialize document mainPart.Document = new Document(new Body()); var body = mainPart.Document.Body!; // Add all parts AddStylesPart(mainPart); AddNumberingPart(mainPart); AddSettingsPart(mainPart); AddThemePart(mainPart); AddHeadersAndFooters(mainPart); // Add sample content AddSampleContent(body); // Section properties MUST be last body.Append(CreateSectionProperties()); mainPart.Document.Save(); } // --- STYLES PART --- // See Section 2 for detailed style creation private static void AddStylesPart(MainDocumentPart mainPart) { var stylesPart = mainPart.AddNewPart(); var styles = new Styles(); // DocDefaults: document-wide defaults for run and paragraph properties // These apply when no explicit style or direct formatting overrides them styles.Append(new DocDefaults( new RunPropertiesDefault( new RunPropertiesBaseStyle( new RunFonts { Ascii = "Calibri", HighAnsi = "Calibri" }, new FontSize { Val = "22" }, // 22 half-points = 11pt new FontSizeComplexScript { Val = "22" } ) ), new ParagraphPropertiesDefault( new ParagraphPropertiesBaseStyle( new SpacingBetweenLines { After = "200", Line = "276", LineRule = LineSpacingRuleValues.Auto } ) ) )); // Default Normal style styles.Append(new Style( new StyleName { Val = "Normal" }, new PrimaryStyle() ) { Type = StyleValues.Paragraph, StyleId = "Normal", Default = true }); stylesPart.Styles = styles; stylesPart.Styles.Save(); } // --- NUMBERING PART --- // Required for bulleted and numbered lists private static void AddNumberingPart(MainDocumentPart mainPart) { var numberingPart = mainPart.AddNewPart(); var numbering = new Numbering(); // AbstractNum defines the list format (bullet, number, multilevel) // Creates a bullet list definition with 3 levels var abstractNum = new AbstractNum { AbstractNumberId = 1 }; // Level 0: Bullet (dot) abstractNum.Append(new Level( new StartNumberingValue { Val = 1 }, new NumberingFormat { Val = NumberFormatValues.Bullet }, new LevelText { Val = "•" }, new LevelJustification { Val = LevelJustificationValues.Left }, new PreviousParagraphProperties( new Indentation { Left = "720", Hanging = "360" }) // 720 DXA indent, 360 DXA hanging ) { LevelIndex = 0 }); // Level 1: Dash abstractNum.Append(new Level( new StartNumberingValue { Val = 1 }, new NumberingFormat { Val = NumberFormatValues.Bullet }, new LevelText { Val = "–" }, new LevelJustification { Val = LevelJustificationValues.Left }, new PreviousParagraphProperties( new Indentation { Left = "1440", Hanging = "360" }) ) { LevelIndex = 1 }); // Level 2: Circle abstractNum.Append(new Level( new StartNumberingValue { Val = 1 }, new NumberingFormat { Val = NumberFormatValues.Bullet }, new LevelText { Val = "◦" }, new LevelJustification { Val = LevelJustificationValues.Left }, new PreviousParagraphProperties( new Indentation { Left = "2160", Hanging = "360" }) ) { LevelIndex = 2 }); numbering.Append(abstractNum); // NumberingInstance links to AbstractNum and assigns a numId numbering.Append(new NumberingInstance( new AbstractNumId { Val = 1 } ) { NumberID = 1 }); numberingPart.Numbering = numbering; numberingPart.Numbering.Save(); } // --- SETTINGS PART --- // Contains document-level settings: zoom, proof state, default tab stop, etc. private static void AddSettingsPart(MainDocumentPart mainPart) { var settingsPart = mainPart.AddNewPart(); var settings = new Settings(); // Zoom: document zoom percentage (default 100%) // Val is a percentage value (e.g., "100" = 100%) settings.Append(new Zoom { Val = "100", Percent = true, SnapToGrid = true }); // ProofState: spelling/grammar check state // Val combines bits: 1=grammar, 2=spelling, 3=both settings.Append(new ProofState { Val = ProofingStateValues.Clean }); // Default tab stop interval in DXA // Word inserts tab stops every 720 DXA (0.5 inch) by default settings.Append(new DefaultTabStop { Val = 720 }); // Character spacing control: automatically adjust character spacing // to maintain consistent line spacing (similar to InDesign) settings.Append(new CharacterSpacingControl { Val = CharacterSpacingValues.CompressPunctuation }); // Compatibility settings: controls how Word handles certain formatting // to ensure compatibility with different Word versions settings.Append(new Compatibility( new UseFELayout(), // Use formatted East Asian layout new UseAsianDigraphicLineBreakRules(), // CJK line breaking rules new AllowSpaceOfSameStyleInTable(), // Table cell spacing new DoNotUseIndentAsPercentageForTabStops(), // Legacy tab behavior new ProportionalOtherIndents(), // Proportional indents new LayoutTableRawTextInTable() // Raw text in layout tables )); // Revision tracking view settings settings.Append(new RevisionView { DocPart = false, Formatting = true, Ink = true, Markup = true }); settingsPart.Settings = settings; settingsPart.Settings.Save(); } // --- THEME PART --- // Defines color scheme, font scheme, and format scheme for the document theme private static void AddThemePart(MainDocumentPart mainPart) { var themePart = mainPart.AddNewPart(); var theme = new Theme( new ThemeElements( // Color scheme: 10 predefined theme colors new ColorScheme( new Dark1Color(new Color { Val = "000000" }), new Light1Color(new Color { Val = "FFFFFF" }), new Dark2Color(new Color { Val = "1F497D" }), new Light2Color(new Color { Val = "EEECE1" }), new Accent1Color(new Color { Val = "4F81BD" }), new Accent2Color(new Color { Val = "C0504D" }), new Accent3Color(new Color { Val = "9BBB59" }), new Accent4Color(new Color { Val = "8064A2" }), new Accent5Color(new Color { Val = "4BACC6" }), new Accent6Color(new Color { Val = "F79646" }), new Hyperlink(new Color { Val = "0000FF" }), new FollowedHyperlinkColor(new Color { Val = "800080" }) ), // Font scheme: major (headings) and minor (body) fonts new FontScheme( new MajorFont { Val = "Calibri Light" }, new MinorFont { Val = "Calibri" } ), // Format scheme: default fill and effect styles new FormatScheme( new FillStyleList( new FillStyle { Fill = new PatternFill { PatternType = PatternValues.Solid } } ), new LineStyleList( new LineStyle { Val = LineValues.Single } ) ) ), new ThemeName { Val = "Office Theme" }, new ThemeNames( new LanguageBasedString { Val = "en-US", LanguageId = "x-none" } ) ); themePart.Theme = theme; themePart.Theme.Save(); } // --- HEADERS AND FOOTERS --- private static void AddHeadersAndFooters(MainDocumentPart mainPart) { // Header var headerPart = mainPart.AddNewPart(); headerPart.Header = new Header( new Paragraph( new ParagraphProperties( new Justification { Val = JustificationValues.Right }), new Run( new RunProperties( new RunFonts { Ascii = "Calibri Light", HighAnsi = "Calibri Light" }, new Italic(), new FontSize { Val = "20" } // 10pt ), new Text("Document Header")) )); var headerId = mainPart.GetIdOfPart(headerPart); // Footer var footerPart = mainPart.AddNewPart(); footerPart.Footer = new Footer( new Paragraph( new ParagraphProperties( new Justification { Val = JustificationValues.Center }), new Run(new Text("Page ") { Space = SpaceProcessingModeValues.Preserve }), new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }), new Run(new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }), new Run(new FieldChar { FieldCharType = FieldCharValues.End }), new Run(new Text(" of ") { Space = SpaceProcessingModeValues.Preserve }), new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }), new Run(new FieldCode(" NUMPAGES ") { Space = SpaceProcessingModeValues.Preserve }), new Run(new FieldChar { FieldCharType = FieldCharValues.End }) )); var footerId = mainPart.GetIdOfPart(footerPart); // Reference IDs in section properties // (added in CreateSectionProperties below) } // --- SECTION PROPERTIES (COMPLETE) --- private static SectionProperties CreateSectionProperties() { var sectPr = new SectionProperties(); // Header/Footer references (must come before page size/margins) var mainPart = doc.MainDocumentPart; // Note: in real code, pass as parameter sectPr.Append(new HeaderReference { Type = HeaderFooterValues.Default, Id = mainPart!.GetIdOfPart(mainPart.HeaderParts.First()) }); sectPr.Append(new FooterReference { Type = HeaderFooterValues.Default, Id = mainPart.GetIdOfPart(mainPart.FooterParts.First()) }); // Page size sectPr.Append(new PageSize { Width = 12240u, Height = 15840u }); // Page margins sectPr.Append(new PageMargin { Top = 1440, Bottom = 1440, Left = 1440u, Right = 1440u, Header = 720u, Footer = 720u }); // Page numbering format sectPr.Append(new PageNumberType { Start = 1, Format = NumberFormatValues.Decimal }); // Column settings (default: 1 column) sectPr.Append(new Columns { ColumnCount = 1, EqualWidth = true }); // Paper source (printer tray) // sectPr.Append(new PaperSource { Tray = 1, Paper = 7 }); return sectPr; } ``` ### 1.5 Unit Systems Reference ```csharp // ============================================================================= // UNIT SYSTEMS IN OPENXML // ============================================================================= // Understanding units is critical. Wrong unit = wrong formatting. // // DXA (Twentieths of a DXA) - "Standard Document Unit" // 1 DXA = 1/20th of a point // 1 inch = 1440 DXA // 1 cm = 567 DXA (approx) // Used for: margins, indents, spacing, tab stops, column widths // // Half-Points (sz) - Font Size // Value is in half-points (1/2 point increments) // 24 = 12pt, 28 = 14pt, 36 = 18pt, 48 = 24pt // Used for: FontSize.Val, FontSizeComplexScript.Val // // Points (pt) - Direct Measurements // Standard typographic point (72 per inch) // Used for: some line spacing values, border widths // // EMU (English Metric Units) - Drawing Objects // 1 inch = 914400 EMU // Used for: drawing object sizes, shapes, images // // STARS (Special Twips Advanced Right-Left) - CJK Indentation // Used for: FirstLineChars, HangingChars (special FirstLine/Hanging for CJK) // Converts character counts to DXA based on font metrics // // LINE SPACING SPECIAL VALUES: // Line = "240" with LineRule = Auto = single spacing (default) // Line = "480" with LineRule = Auto = double spacing // Line = "360" with LineRule = Auto = 1.5 spacing // Line = "240" with LineRule = Exact = exactly 12pt // Line = "288" with LineRule = AtLeast = at least 14.4pt (grows with content) // --- CONVERSION HELPER METHODS --- public static class OpenXmlUnits { // DXA conversions public static int InchesToDxa(double inches) => (int)(inches * 1440); public static int CmToDxa(double cm) => (int)(cm * 567.0); public static int PtToDxa(double pt) => (int)(pt * 20); public static double DxaToInches(int dxa) => dxa / 1440.0; public static double DxaToCm(int dxa) => dxa / 567.0; public static double DxaToPt(int dxa) => dxa / 20.0; // EMU conversions (for drawings) public static long InchesToEmu(double inches) => (long)(inches * 914400); public static long CmToEmu(double cm) => (long)(cm * 360000); public static double EmuToInches(long emu) => emu / 914400.0; // Half-point conversions (font sizes) public static int PtToHalfPt(double pt) => (int)(pt * 2); public static int FontSizeToSz(double ptSize) => (int)(ptSize * 2); public static double SzToPt(int sz) => sz / 2.0; // Line spacing public static int SingleSpacing => 240; public static int DoubleSpacing => 480; public static int OneAndHalfSpacing => 360; public static int LineSpacingPt(double pt) => (int)(pt * 20); // Convert to DXA } // Example usage: var marginInInches = OpenXmlUnits.DxaToInches(1440); // 1.0 var fontSizeInSz = OpenXmlUnits.FontSizeToSz(12.0); // 24 var indentInDxa = OpenXmlUnits.InchesToDxa(0.5); // 720 ``` --- ## 2. Style System Deep Dive ### 2.1 Style Types and Structure ```csharp // ============================================================================= // STYLE TYPES OVERVIEW // ============================================================================= // OpenXML defines 4 style types (StyleValues enum): // 1. Paragraph (w:p) - controls paragraph-level formatting // 2. Character (w:r) - controls inline/run-level formatting // 3. Table (w:tbl) - controls table-level formatting // 4. Numbering (w:num) - NOT a style type, but a separate numbering system // // Key insight: A style can be BOTH paragraph and character style (linked style). // The "linkedStyle" element links a paragraph style to a character style. // --- MINIMAL PARAGRAPH STYLE --- // A paragraph style controls: pPr (paragraph properties) and optionally rPr Style minimalParaStyle = new Style( new StyleName { Val = "MyParagraphStyle" }, new PrimaryStyle() // Primary styles appear in Style gallery ) { Type = StyleValues.Paragraph, StyleId = "MyParagraphStyle" }; // --- MINIMAL CHARACTER STYLE --- // A character style controls: rPr only (no pPr) Style minimalCharStyle = new Style( new StyleName { Val = "MyCharacterStyle" }, new PrimaryStyle() ) { Type = StyleValues.Character, StyleId = "MyCharacterStyle" }; // Character style with run properties (fonts, size, bold, etc.) Style charStyleWithFormatting = new Style( new StyleName { Val = "Emphasis" }, new PrimaryStyle(), new StyleRunProperties( new Italic(), new Color { Val = "C00000" } // Dark red ) ) { Type = StyleValues.Character, StyleId = "Emphasis" }; // --- LINKED STYLE (Paragraph + Character) --- // A linked style combines both: it can be applied to a paragraph OR a run. // This is how Word's "Heading 1" works — applies to paragraphs, but you can // also select text within a heading and apply the same style as character formatting. Style linkedStyle = new Style( new StyleName { Val = "LinkedStyle" }, new PrimaryStyle(), new LinkedStyle { Val = "LinkedStyleChar" }, // Links to character style new StyleParagraphProperties( new SpacingBetweenLines { After = "120" } ), new StyleRunProperties( new Bold(), new FontSize { Val = "24" } ) ) { Type = StyleValues.Paragraph, StyleId = "LinkedStyle" }; // Corresponding character style (normally same name + "Char" suffix by convention) Style linkedStyleChar = new Style( new StyleName { Val = "LinkedStyle Char" }, // Word convention: adds " Char" new PrimaryStyle(), new StyleRunProperties( new Bold(), new FontSize { Val = "24" } ) ) { Type = StyleValues.Character, StyleId = "LinkedStyleChar" }; // --- TABLE STYLE --- Style tableStyle = new Style( new StyleName { Val = "MyTableStyle" }, new PrimaryStyle(), new StyleTableProperties( new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct }, // 50% width new TableBorders( new TopBorder { Val = BorderValues.Single, Size = 4, Color = "000000" }, new BottomBorder { Val = BorderValues.Single, Size = 4, Color = "000000" }, new LeftBorder { Val = BorderValues.Single, Size = 4, Color = "000000" }, new RightBorder { Val = BorderValues.Single, Size = 4, Color = "000000" }, new InsideHorizontalBorder { Val = BorderValues.Single, Size = 2, Color = "CCCCCC" }, new InsideVerticalBorder { Val = BorderValues.Single, Size = 2, Color = "CCCCCC" } ), new TableCellMarginDefault( new TopMargin { Width = "0", Type = TableWidthUnitValues.DXA }, new StartMargin { Width = "108", Type = TableWidthUnitValues.DXA }, new BottomMargin { Width = "0", Type = TableWidthUnitValues.DXA }, new EndMargin { Width = "108", Type = TableWidthUnitValues.DXA } ) ) ) { Type = StyleValues.Table, StyleId = "MyTableStyle" }; ``` ### 2.2 DocDefaults and Document-Wide Defaults ```csharp // ============================================================================= // DOCDEFAULTS: DOCUMENT-WIDE DEFAULTS // ============================================================================= // DocDefaults lives inside Styles and provides fallback values when: // 1. No explicit style is applied // 2. No direct formatting is applied // It contains RunPropertiesDefault and/or ParagraphPropertiesDefault. // // CRITICAL: DocDefaults applies to the entire document. Any explicit style // or direct formatting will override it. // --- COMPLETE DOCDEFAULTS SETUP --- var docDefaults = new DocDefaults( // Run properties defaults: default font, size, language for all runs new RunPropertiesDefault( new RunPropertiesBaseStyle( // RunFonts: which font to use for each script // Word will fall back through these: ASCII -> HighAnsi -> EastAsia -> ComplexScript // Always specify at minimum Ascii and HighAnsi new RunFonts { Ascii = "Calibri", // Western/Latin font (primary) HighAnsi = "Calibri", // Latin characters (often same as Ascii) EastAsia = "SimSun", // East Asian font (CJK) ComplexScript = "Arial", // Complex scripts (Arabic, Hebrew, Thai) ASCIITheme = ThemeFontValues.Minor, HighAnsiTheme = ThemeFontValues.Minor, EastAsiaTheme = ThemeFontValues.Minor, ComplexScriptTheme = ThemeFontValues.Minor }, // FontSize: in HALF-POINTS (24 = 12pt, 22 = 11pt, 20 = 10pt) new FontSize { Val = "22" }, // 11pt for body new FontSizeComplexScript { Val = "22" }, // Languages: required for proper hyphenation and spell checking new Languages { Val = "en-US" }, // Default language new Languages { EastAsia = "zh-CN", Val = "en-US" } // Can set multiple ) ), // Paragraph properties defaults: default spacing, etc. new ParagraphPropertiesDefault( new ParagraphPropertiesBaseStyle( // SpacingBetweenLines: default paragraph spacing // After = "200" = 200 DXA = 10pt after each paragraph new SpacingBetweenLines { After = "200", Line = "276", LineRule = LineSpacingRuleValues.Auto // Auto = 1.15x line height } ) ) ); // --- LAYOUT LUNCTIONS (LATENT STYLES) --- // Latent styles are hidden styles that exist in Word but aren't in styles.xml. // They provide fast-access defaults for formatting (e.g., Normal, Heading 1-6, etc.) // when the user hasn't explicitly customized them. // // DocDefaults can define LatentStyleCountOverride to adjust count, // but true latent styles are controlled by Normal.dotm (Word's global template). Styles CreateStylesWithDocDefaults() { var styles = new Styles(); // DocDefaults with run and paragraph properties defaults styles.Append(new DocDefaults( new RunPropertiesDefault( new RunPropertiesBaseStyle( new RunFonts { Ascii = "Calibri", HighAnsi = "Calibri" }, new FontSize { Val = "22" }, new Languages { Val = "en-US" } ) ), new ParagraphPropertiesDefault( new ParagraphPropertiesBaseStyle( new SpacingBetweenLines { After = "160", Line = "276", LineRule = LineSpacingRuleValues.Auto } ) ) )); // LatentStyles: override defaults for built-in latent styles // These control Word's "fast-styles" like Heading 1-6 before they're customized styles.Append(new LatentStyles( new Count { Val = 159 }, // Total latent style count new FirstLineChars { Val = 352 }, // Default first line char count new HorizontalOverflow { Val = HorizontalOverflowValues.Overflow }, new VerticalOverflow { Val = VerticalOverflowValues.Overflow }, new KoreanSpaceAdjust { Val = true }, // Each LatentStyleException overrides ONE attribute of ONE latent style // StyleID = the built-in style name (e.g., "Normal", "heading 1") // Attribute: what to change (bold, italic, font, color, etc.) // The defaults for built-in headings: font=Calibri, size=24, bold new LatentStyleException( new Primary烙, new StyleName { Val = "Normal" }, new UIPriority { Val = 1 }, new PrimaryZone(), new QuickStyle() ), new LatentStyleException( new Primary烙, new StyleName { Val = "heading 1" }, new UIPriority { Val = 9 }, new PrimaryZone(), new QuickStyle(), new Bold(), new BoldComplexScript(), new FontSize { Val = "48" }, // 24pt = 48 half-pts new FontSizeComplexScript { Val = "48" } ) )); return styles; } ``` ### 2.3 Complete Heading Styles Hierarchy ```csharp // ============================================================================= // HEADING STYLES WITH PROPER INHERITANCE CHAIN // ============================================================================= // Word's built-in heading system uses style inheritance: // Normal (base) -> Heading1 -> Heading2 -> Heading3 -> Heading4 -> Heading5 -> Heading6 // // Why this matters: // - Each heading INHERITS from its parent (basedOn) // - Define common properties in Normal, override in each heading // - Change body font once in Normal, all headings inherit it // - Heading-specific properties override as needed // --- HEADING STYLE FACTORY --- public static Style CreateHeadingStyle(int level, FontConfig fonts) { // Validate level (1-9 are valid, 1-6 are standard) if (level < 1 || level > 9) throw new ArgumentOutOfRangeException(nameof(level)); double[] headingSizes = [26.0, 20.0, 16.0, 14.0, 12.0, 11.0, 11.0, 11.0, 11.0]; string[] outlineLevels = ["0", "1", "2", "3", "4", "5", "6", "7", "8"}; var style = new Style( new StyleName { Val = $"heading {level}" }, // Display name new BasedOn { Val = level == 1 ? "Normal" : $"Heading{level - 1}" }, // Parent style new NextParagraphStyle { Val = "Normal" }, // After heading -> Normal new PrimaryStyle(), // Show in Styles gallery new UIPriority { Val = 9 - level }, // Priority in gallery (H1 = 8, H2 = 7, etc.) new QuickStyle(), // Appears in Quick Styles gallery // Paragraph properties: spacing, keep options, outline level new StyleParagraphProperties( new KeepNext(), // Keep heading with next paragraph new KeepLines(), // Keep all lines of heading together new SpacingBetweenLines // Spacing before/after { Before = level == 1 ? "480" : "240", // H1 = 240pt before, others = 120pt After = "120" }, new OutlineLevel { Val = level - 1 } // 0-indexed for H1=0, H2=1, etc. ), // Run properties: font, size, bold new StyleRunProperties( new RunFonts { Ascii = fonts.HeadingFont, HighAnsi = fonts.HeadingFont, EastAsia = "SimHei" // Bold heading font for CJK }, new FontSize { Val = UnitConverter.FontSizeToSz(headingSizes[level - 1]) }, new FontSizeComplexScript { Val = UnitConverter.FontSizeToSz(headingSizes[level - 1]) }, new Bold(), new BoldComplexScript() ) ) { Type = StyleValues.Paragraph, StyleId = $"Heading{level}" }; return style; } // --- ADD ALL HEADING STYLES TO STYLES COLLECTION --- public static void AddHeadingStyles(Styles styles, FontConfig fonts) { for (int i = 1; i <= 6; i++) { styles.Append(CreateHeadingStyle(i, fonts)); } // Also add Heading 7-9 (valid in Word, less commonly used) for (int i = 7; i <= 9; i++) { styles.Append(CreateHeadingStyle(i, fonts)); } } // --- HEADING STYLES INHERITANCE VISUALIZATION --- // When you apply "Heading2" (basedOn="Heading1"): // // Normal style: // - Font: Calibri 11pt // - Spacing: 0 before, 200 after // - No bold // // Heading1 (basedOn="Normal"): // - Inherits: Calibri 11pt // - Overrides: Calibri Light 26pt, Bold, Spacing 480 before/120 after // - Adds: KeepNext, KeepLines, OutlineLevel=0 // // Heading2 (basedOn="Heading1"): // - Inherits: Calibri Light 26pt, Bold, KeepNext, KeepLines // - Overrides: 20pt // - Inherits: OutlineLevel=1 // // Effective result: Heading2 = Calibri Light 20pt Bold, KeepNext+KeepLines, 480/120 spacing, OL=1 ``` ### 2.4 Style Inheritance Chain Resolution ```csharp // ============================================================================= // STYLE INHERITANCE RESOLUTION // ============================================================================= // OpenXML styles resolve properties through the basedOn chain at RENDER TIME. // The document.xml stores only the styleId, not the resolved properties. // Word (or this library) walks the chain at load/display time. // // Example: Applying "Heading2" to a paragraph // // 1. Start with Heading2 style definition // 2. Walk basedOn chain: Heading2 -> Heading1 -> Normal -> (null) // 3. Collect properties in reverse order (most generic first): // a. Normal: Ascii=Calibri, sz=22, no bold // b. Heading1: Ascii=Calibri Light, sz=48, bold (override Calibri, sz, bold) // c. Heading2: sz=40 (override sz only) // 4. Final resolved style: Ascii=Calibri Light, sz=40, bold (bold from H1) // // IMPORTANT: Style override is COMPLETE for each element type: // - If Normal has rPr with Fonts, and Heading1 has pPr only, // Heading1 still inherits Normal's rPr fully. // - StyleRunProperties (rPr) and StyleParagraphProperties (pPr) are separate. // --- RESOLVING STYLE PROPERTIES MANUALLY --- // For debugging or custom rendering, you may need to resolve style chains public static class StyleResolver { public record ResolvedStyle( StyleName? Name, RunProperties? RunProps, ParagraphProperties? ParaProps, string? BasedOn, string Type); public static ResolvedStyle Resolve(Styles styles, string styleId) { var styleMap = styles.Elements