using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniMaxAIDocx.Core.Samples;
///
/// Exhaustive reference for every ParagraphProperties (w:pPr) child element in OpenXML.
/// Each method demonstrates one formatting category with full XML doc comments,
/// unit explanations, and gotchas. All code compiles against DocumentFormat.OpenXml 3.5.1.
///
public static class ParagraphFormattingSamples
{
// ──────────────────────────────────────────────────────────────────
// 1. Justification / Alignment (w:jc)
// ──────────────────────────────────────────────────────────────────
///
/// Sets paragraph horizontal alignment (justification).
///
/// All JustificationValues:
///
/// - Left — Left-aligned (default for LTR documents). Ragged right edge.
/// - Center — Centered text.
/// - Right — Right-aligned. Ragged left edge.
/// - Both — Justified: text stretches to fill the full line width.
/// The last line is left-aligned. Word adjusts inter-word spacing.
/// - Distribute — Like justify, but also stretches the last line.
/// Word adjusts both inter-word AND inter-character spacing. Used in CJK typography.
/// - ThaiDistribute — Special distribute mode for Thai script,
/// which has unique spacing rules around vowel marks and tone marks.
///
///
///
/// Gotcha: In RTL paragraphs (w:bidi), "Left" actually means the START edge
/// (right side in RTL) and "Right" means the END edge. Use Start/End values if
/// you need direction-independent alignment (not all renderers support them).
///
///
public static void ApplyJustification(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Left-aligned (default for Western text)
pPr.Justification = new Justification { Val = JustificationValues.Left };
// Center-aligned
// pPr.Justification = new Justification { Val = JustificationValues.Center };
// Right-aligned
// pPr.Justification = new Justification { Val = JustificationValues.Right };
// Justified (both edges flush, last line left-aligned)
// pPr.Justification = new Justification { Val = JustificationValues.Both };
// Distribute (all lines justified including last, with inter-character spacing)
// pPr.Justification = new Justification { Val = JustificationValues.Distribute };
// Thai distribute (specialized Thai script distribution)
// pPr.Justification = new Justification { Val = JustificationValues.ThaiDistribute };
}
// ──────────────────────────────────────────────────────────────────
// 2. Indentation (w:ind)
// ──────────────────────────────────────────────────────────────────
///
/// Sets paragraph indentation: left, right, first-line, and hanging.
///
/// Units: All values are in DXA (twentieths of a point).
/// 1 inch = 1440 DXA, 1 cm ≈ 567 DXA, 1 pt = 20 DXA.
///
///
/// Properties:
///
/// - Left — Left indent for the entire paragraph (shifts all lines right).
/// - Right — Right indent (shifts the right boundary left).
/// - FirstLine — Additional indent for the FIRST line only (added to Left).
/// 720 DXA = 0.5 inch first-line indent.
/// - Hanging — The first line hangs LEFT of the paragraph body.
/// Used for numbered/bulleted lists. Mutually exclusive with FirstLine.
///
///
///
/// CJK character units:
///
/// - FirstLineChars — First-line indent in hundredths of a character width.
/// 200 = 2 character widths. Takes precedence over FirstLine when set.
/// - LeftChars — Left indent in hundredths of a character width.
/// - RightChars — Right indent in hundredths of a character width.
/// - HangingChars — Hanging indent in hundredths of a character width.
///
///
///
/// Gotcha: FirstLine and Hanging are mutually exclusive. If both are set,
/// behavior is undefined. Setting one should clear the other.
///
///
/// Gotcha: When using character-based units (FirstLineChars, etc.),
/// the corresponding DXA value (FirstLine, etc.) should also be set as a fallback
/// for renderers that do not support character-based indentation.
///
///
public static void ApplyIndentation(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Standard indentation in DXA
pPr.Indentation = new Indentation
{
Left = "720", // 0.5 inch left indent (720 DXA)
Right = "360", // 0.25 inch right indent (360 DXA)
FirstLine = "720" // 0.5 inch first-line indent (720 DXA)
};
// Hanging indent (commonly used with bullets/numbering)
// pPr.Indentation = new Indentation
// {
// Left = "720", // Overall paragraph indent
// Hanging = "360" // First line hangs back 0.25 inch
// // Effective first line position: 720 - 360 = 360 DXA from margin
// };
// CJK character-based indent
// pPr.Indentation = new Indentation
// {
// FirstLineChars = 200, // 2 character widths (200 hundredths)
// FirstLine = "480" // DXA fallback (approx 2 chars at ~10.5pt SimSun)
// };
}
// ──────────────────────────────────────────────────────────────────
// 3. Line Spacing (w:spacing — line, lineRule)
// ──────────────────────────────────────────────────────────────────
///
/// Sets the spacing between lines within a paragraph.
///
/// LineRule values and their Line units:
///
/// - Auto — Line is in 240ths of a line (proportional).
/// 240 = single spacing (1.0), 276 = 1.15 (Word default), 360 = 1.5, 480 = 2.0.
/// Formula: value = desiredMultiplier * 240.
/// - Exact — Line is in DXA (twentieths of a point).
/// The line height is fixed at exactly this value. Text may be clipped if too tall.
/// Example: 240 DXA = 12pt exact line height.
/// - AtLeast — Line is in DXA. The line height is at least this
/// value, but can grow larger to accommodate tall content (images, large fonts).
/// Example: 240 DXA = at least 12pt.
///
///
///
/// Gotcha: The unit of "Line" changes depending on LineRule!
/// Auto = 240ths of a line, Exact/AtLeast = DXA (twips). This is a very common
/// source of bugs. If you set Line="360" with LineRule=Auto, you get 1.5x spacing.
/// If you set Line="360" with LineRule=Exact, you get 18pt fixed height.
///
///
/// Gotcha: If LineRule is omitted, it defaults to Auto.
///
///
public static void ApplyLineSpacing(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Single spacing (1.0x) — Auto mode, 240/240 = 1.0
// pPr.SpacingBetweenLines = new SpacingBetweenLines
// {
// Line = "240",
// LineRule = LineSpacingRuleValues.Auto
// };
// 1.15x spacing (Word's default) — Auto mode, 276/240 = 1.15
pPr.SpacingBetweenLines = new SpacingBetweenLines
{
Line = "276",
LineRule = LineSpacingRuleValues.Auto
};
// 1.5x spacing — Auto mode, 360/240 = 1.5
// pPr.SpacingBetweenLines = new SpacingBetweenLines
// {
// Line = "360",
// LineRule = LineSpacingRuleValues.Auto
// };
// Double spacing (2.0x) — Auto mode, 480/240 = 2.0
// pPr.SpacingBetweenLines = new SpacingBetweenLines
// {
// Line = "480",
// LineRule = LineSpacingRuleValues.Auto
// };
// Exact 14pt line height — no growing for tall content
// pPr.SpacingBetweenLines = new SpacingBetweenLines
// {
// Line = "280", // 14pt × 20 DXA/pt = 280 DXA
// LineRule = LineSpacingRuleValues.Exact
// };
// At-least 12pt — minimum height, can grow
// pPr.SpacingBetweenLines = new SpacingBetweenLines
// {
// Line = "240", // 12pt × 20 = 240 DXA
// LineRule = LineSpacingRuleValues.AtLeast
// };
}
// ──────────────────────────────────────────────────────────────────
// 4. Paragraph Spacing — Before/After (w:spacing — before, after)
// ──────────────────────────────────────────────────────────────────
///
/// Sets the space before and after a paragraph.
///
/// Unit: Before and After are in DXA (twentieths of a point).
/// 1pt = 20 DXA. Common values: 0 DXA = 0pt, 120 DXA = 6pt, 200 DXA = 10pt,
/// 240 DXA = 12pt.
///
///
/// CJK line units:
///
/// - BeforeLines — Space before in hundredths of a line.
/// 100 = 1 line of space. Takes precedence over Before when set.
/// - AfterLines — Space after in hundredths of a line.
///
///
///
/// Gotcha: Paragraph spacing collapses: when two paragraphs are adjacent,
/// the space between them is the LARGER of paragraph1.After and paragraph2.Before,
/// NOT the sum. This is standard Word behavior.
///
///
/// Gotcha: can suppress spacing between
/// paragraphs of the same style, overriding Before/After.
///
///
/// BeforeAutoSpacing / AfterAutoSpacing: When set to true, Word auto-calculates
/// the spacing (typically 14pt for HTML-imported paragraphs). Overrides Before/After.
///
///
public static void ApplyParagraphSpacing(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// 6pt before, 10pt after (typical body text spacing)
pPr.SpacingBetweenLines = new SpacingBetweenLines
{
Before = "120", // 6pt × 20 = 120 DXA
After = "200" // 10pt × 20 = 200 DXA
};
// Combined with line spacing
// pPr.SpacingBetweenLines = new SpacingBetweenLines
// {
// Before = "240", // 12pt before
// After = "120", // 6pt after
// Line = "276", // 1.15x line spacing
// LineRule = LineSpacingRuleValues.Auto
// };
// CJK line-based spacing
// pPr.SpacingBetweenLines = new SpacingBetweenLines
// {
// BeforeLines = 50, // 0.5 line before
// AfterLines = 100, // 1 line after
// Before = "120", // DXA fallback
// After = "240" // DXA fallback
// };
// Auto spacing (used in HTML imports)
// pPr.SpacingBetweenLines = new SpacingBetweenLines
// {
// BeforeAutoSpacing = true, // Word decides (typically 14pt)
// AfterAutoSpacing = true
// };
}
// ──────────────────────────────────────────────────────────────────
// 5. Pagination Control (w:keepNext, w:keepLines, w:widowControl,
// w:pageBreakBefore)
// ──────────────────────────────────────────────────────────────────
///
/// Controls how a paragraph interacts with page breaks.
///
/// Properties:
///
/// - KeepNext — Keeps this paragraph on the same page as the NEXT paragraph.
/// Essential for headings (so a heading is never orphaned at the bottom of a page
/// while its body text starts on the next page).
/// - KeepLines — Prevents a page break within this paragraph.
/// All lines of the paragraph stay on the same page. If it doesn't fit, the entire
/// paragraph moves to the next page.
/// - WidowControl — Prevents widows (a single last line of a paragraph at
/// the top of a page) and orphans (a single first line at the bottom of a page).
/// Default is ON. Set Val=false to allow widows/orphans.
/// - PageBreakBefore — Forces a page break immediately before this paragraph.
/// Used for chapter headings and section starts.
///
///
///
/// Gotcha: These properties can cause unexpected pagination behavior.
/// A chain of KeepNext paragraphs can push an entire group to the next page.
/// KeepLines on a very long paragraph can cause a full blank page.
///
///
public static void ApplyKeepTogether(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Keep with next paragraph (typical for headings)
pPr.KeepNext = new KeepNext();
// Keep all lines of this paragraph together
pPr.KeepLines = new KeepLines();
// Widow/orphan control (on by default, explicitly setting here)
pPr.WidowControl = new WidowControl();
// Force page break before this paragraph
// pPr.PageBreakBefore = new PageBreakBefore();
// Disable widow/orphan control (allow widows/orphans)
// pPr.WidowControl = new WidowControl { Val = false };
}
// ──────────────────────────────────────────────────────────────────
// 6. Outline Level (w:outlineLvl)
// ──────────────────────────────────────────────────────────────────
///
/// Sets the outline level for Table of Contents (TOC) integration
/// and Navigation Pane display.
///
/// Values: 0–8 (where 0 = top-level heading, 8 = deepest level).
/// Level 9 (BodyTextLevel) means "body text" (not included in TOC).
///
///
/// Relationship to heading styles: Word's built-in Heading 1 through Heading 9
/// styles have outlineLvl 0–8 respectively. You can assign an outline level to ANY
/// paragraph style, making it appear in the TOC without using a Heading style.
///
///
/// Gotcha: If you set outlineLvl directly on paragraphs (not in a style),
/// each paragraph needs the property. It is more maintainable to define a style
/// with the outline level and apply the style.
///
///
public static void ApplyOutlineLevel(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Level 0 = equivalent to Heading 1 in TOC
pPr.OutlineLevel = new OutlineLevel { Val = 0 };
// Level 1 = Heading 2 equivalent
// pPr.OutlineLevel = new OutlineLevel { Val = 1 };
// Level 2 = Heading 3 equivalent
// pPr.OutlineLevel = new OutlineLevel { Val = 2 };
// Body text (explicitly not in TOC)
// pPr.OutlineLevel = new OutlineLevel { Val = 9 };
}
// ──────────────────────────────────────────────────────────────────
// 7. Paragraph Borders (w:pBdr)
// ──────────────────────────────────────────────────────────────────
///
/// Applies borders to a paragraph (top, bottom, left, right, between, bar).
///
/// Border properties:
///
/// - Val — Border style. Common values: Single, Double, Dotted, Dashed,
/// DotDash, DotDotDash, Triple, ThickThinSmallGap, ThinThickSmallGap,
/// ThickThinMediumGap, ThinThickMediumGap, ThickThinLargeGap, ThinThickLargeGap,
/// Wave, DoubleWave, DashSmallGap, DashDotStroked, ThreeDEmboss, ThreeDEngrave,
/// Outset, Inset, None, Nil.
/// - Size — Width in eighths of a point. 4 = 0.5pt, 8 = 1pt, 12 = 1.5pt.
/// Range: 2–96.
/// - Space — Distance from text to border in points. Range: 0–31.
/// - Color — Hex RGB color (e.g., "000000") or "auto".
///
///
///
/// Between border: Renders between consecutive paragraphs that BOTH have the
/// "between" border set. This is how Word creates a visually grouped block of bordered
/// paragraphs without doubling up borders between them.
///
///
/// Bar border: A vertical line at the start edge of the paragraph (left for LTR,
/// right for RTL). Not the same as the left border — the bar appears in the margin area.
///
///
public static void ApplyParagraphBorders(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
pPr.ParagraphBorders = new ParagraphBorders(
// Top border
new TopBorder
{
Val = BorderValues.Single,
Size = 4, // 0.5pt
Space = 1, // 1pt from text
Color = "000000"
},
// Bottom border
new BottomBorder
{
Val = BorderValues.Single,
Size = 4,
Space = 1,
Color = "000000"
},
// Left border
new LeftBorder
{
Val = BorderValues.Single,
Size = 4,
Space = 4, // 4pt from text
Color = "000000"
},
// Right border
new RightBorder
{
Val = BorderValues.Single,
Size = 4,
Space = 4,
Color = "000000"
}
);
// Add "between" border for consecutive bordered paragraphs
// pPr.ParagraphBorders.AppendChild(new BetweenBorder
// {
// Val = BorderValues.Single,
// Size = 4,
// Space = 1,
// Color = "000000"
// });
// Add "bar" border (vertical bar in the margin)
// pPr.ParagraphBorders.AppendChild(new BarBorder
// {
// Val = BorderValues.Single,
// Size = 4,
// Space = 0,
// Color = "FF0000" // Red bar
// });
}
// ──────────────────────────────────────────────────────────────────
// 8. Paragraph Shading (w:shd)
// ──────────────────────────────────────────────────────────────────
///
/// Applies a background color or pattern to the entire paragraph.
///
/// Properties:
///
/// - Val — Shading pattern. Use ShadingPatternValues.Clear for a
/// solid background (most common). Other patterns: HorizontalStripe, VerticalStripe,
/// ReverseDiagonalStripe, DiagonalStripe, DiagonalCross, HorizontalCross,
/// ThinHorizontalStripe, ThinVerticalStripe, Percent5 through Percent95, etc.
/// - Fill — Background color as hex RGB (e.g., "FFFF00"). "auto" = no fill.
/// - Color — Foreground/pattern color. Only visible with non-Clear patterns.
/// "auto" = automatic.
///
///
///
/// Theme-based shading: Use ThemeFill, ThemeFillTint, ThemeFillShade for
/// theme-aware background colors. The Fill attribute serves as a fallback.
///
///
public static void ApplyParagraphShading(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Solid light yellow background
pPr.Shading = new Shading
{
Val = ShadingPatternValues.Clear, // Solid fill
Fill = "FFFFCC", // Light yellow
Color = "auto"
};
// Theme-based background
// pPr.Shading = new Shading
// {
// Val = ShadingPatternValues.Clear,
// Fill = "D9E2F3", // Hex fallback
// ThemeFill = ThemeColorValues.Accent1,
// ThemeFillTint = "33" // Light tint
// };
// Patterned shading (rare, but valid)
// pPr.Shading = new Shading
// {
// Val = ShadingPatternValues.Percent10, // 10% dot pattern
// Fill = "FFFFFF", // White background
// Color = "000000" // Black dots
// };
}
// ──────────────────────────────────────────────────────────────────
// 9. Tab Stops (w:tabs)
// ──────────────────────────────────────────────────────────────────
///
/// Defines custom tab stops with alignment and leader characters.
///
/// Tab alignment values:
///
/// - Left — Text starts at the tab position (default).
/// - Center — Text is centered on the tab position.
/// - Right — Text ends at the tab position (text flows leftward).
/// - Decimal — Aligns on the decimal point (for numbers like 1,234.56).
/// - Bar — Draws a vertical bar at the tab position (text is not affected).
/// - Clear — Clears an inherited tab stop at this position.
/// - Number — Tab position for list numbering.
///
///
///
/// Tab leader values: The character that fills the space before the tab stop.
///
/// - None — Blank space (default).
/// - Dot — Dots (. . . . . .) — common in TOC.
/// - Hyphen — Hyphens (- - - - -).
/// - Underscore — Continuous underline (__________).
/// - Heavy — Thick underline.
/// - MiddleDot — Middle dots (· · · · ·).
///
///
///
/// Position unit: Tab stop position is in DXA (twentieths of a point).
/// 1440 DXA = 1 inch, 720 DXA = 0.5 inch.
///
///
/// Gotcha: Tab stops are cumulative with style-defined tabs unless you use
/// a Clear tab to remove an inherited one. Order tab stops by position.
///
///
public static void ApplyTabStops(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
pPr.Tabs = new Tabs(
// Left tab at 1 inch
new TabStop
{
Val = TabStopValues.Left,
Position = 1440 // 1 inch = 1440 DXA
},
// Center tab at 3 inches
new TabStop
{
Val = TabStopValues.Center,
Position = 4320 // 3 inches = 4320 DXA
},
// Right tab at 6 inches with dot leader (TOC style)
new TabStop
{
Val = TabStopValues.Right,
Position = 8640, // 6 inches = 8640 DXA
Leader = TabStopLeaderCharValues.Dot
},
// Decimal tab at 4 inches (for aligning numbers)
new TabStop
{
Val = TabStopValues.Decimal,
Position = 5760 // 4 inches = 5760 DXA
}
);
// Clear an inherited tab stop at 2 inches
// pPr.Tabs.AppendChild(new TabStop
// {
// Val = TabStopValues.Clear,
// Position = 2880
// });
// Bar tab at 0.5 inch (draws a vertical line, does not move text)
// pPr.Tabs.AppendChild(new TabStop
// {
// Val = TabStopValues.Bar,
// Position = 720
// });
}
// ──────────────────────────────────────────────────────────────────
// 10. Numbering / List (w:numPr)
// ──────────────────────────────────────────────────────────────────
///
/// Associates a paragraph with a numbering definition (bulleted or numbered list).
///
/// NumberingId (w:numId): References a numbering definition instance
/// in the numbering.xml part (NumberingDefinitionsPart). This ID links to an
/// AbstractNum that defines the list format.
///
///
/// NumberingLevelReference (w:ilvl): The nesting level (0-based).
/// 0 = top-level item, 1 = first sub-level, etc. Maximum depth: 8 (9 levels total).
///
///
/// Gotcha: The numbering definition must exist in numbering.xml.
/// Creating a paragraph with numPr that references a non-existent numId will cause
/// Word to show an error or ignore the numbering.
///
///
/// Gotcha: To remove numbering from a paragraph that inherits it from a style,
/// set NumberingId to 0: new NumberingId { Val = 0 }
///
///
/// NumberingChange: For tracked changes, wrap numPr changes in a
/// NumberingChange element to record the revision.
///
///
public static void ApplyNumbering(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Associate with numbering definition #1, level 0 (top-level)
pPr.NumberingProperties = new NumberingProperties
{
NumberingLevelReference = new NumberingLevelReference { Val = 0 },
NumberingId = new NumberingId { Val = 1 }
};
// Sub-level item (indented bullet/number)
// pPr.NumberingProperties = new NumberingProperties
// {
// NumberingLevelReference = new NumberingLevelReference { Val = 1 },
// NumberingId = new NumberingId { Val = 1 }
// };
// Remove numbering inherited from style
// pPr.NumberingProperties = new NumberingProperties
// {
// NumberingId = new NumberingId { Val = 0 } // 0 = no numbering
// };
}
// ──────────────────────────────────────────────────────────────────
// 11. Bidirectional (w:bidi, w:textDirection)
// ──────────────────────────────────────────────────────────────────
///
/// Sets the paragraph as right-to-left (for Arabic/Hebrew text).
///
/// BiDi (w:bidi): When set, the paragraph direction is right-to-left.
/// This affects: text flow direction, default alignment (right becomes default),
/// indentation sides (left/right swap meaning), tab stop behavior.
///
///
/// TextDirection (w:textDirection): Controls text flow direction within
/// the paragraph's text area. Values include LrTb (left-to-right, top-to-bottom,
/// default), TbRl (top-to-bottom, right-to-left — vertical CJK), BtLr
/// (bottom-to-top, left-to-right — rotated).
///
///
/// Gotcha: For RTL paragraphs, also set Justification to Right
/// (which visually aligns to the RIGHT = start edge in RTL context).
///
///
public static void ApplyBidirectional(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Set paragraph as right-to-left
pPr.BiDi = new BiDi();
// Also set right-alignment (the "start" edge for RTL)
pPr.Justification = new Justification { Val = JustificationValues.Right };
// Text direction for vertical CJK layout
// pPr.TextDirection = new TextDirection
// {
// Val = TextDirectionValues.TopToBottomRightToLeft // Vertical CJK
// };
// Text direction for rotated layout
// pPr.TextDirection = new TextDirection
// {
// Val = TextDirectionValues.BottomToTopLeftToRight // 90° rotation
// };
}
// ──────────────────────────────────────────────────────────────────
// 12. Contextual Spacing (w:contextualSpacing)
// ──────────────────────────────────────────────────────────────────
///
/// Suppresses Before/After spacing between consecutive paragraphs that
/// share the same paragraph style.
///
/// Use case: List items. When multiple "List Paragraph" items follow each other,
/// contextual spacing removes the gap between them while preserving spacing when
/// the list meets a different style (e.g., body text).
///
///
/// How it works: When two adjacent paragraphs have the same ParagraphStyleId
/// AND both have ContextualSpacing set, the Before spacing of the second paragraph
/// and the After spacing of the first paragraph are suppressed (set to 0).
///
///
/// Gotcha: Both paragraphs must have the same style AND contextual spacing
/// enabled. If only one has it, the spacing is NOT suppressed.
///
///
public static void ApplyContextualSpacing(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
pPr.ContextualSpacing = new ContextualSpacing();
// Disable (override a style that enables it):
// pPr.ContextualSpacing = new ContextualSpacing { Val = false };
}
// ──────────────────────────────────────────────────────────────────
// 13. Mirror Indents (w:mirrorIndents)
// ──────────────────────────────────────────────────────────────────
///
/// Swaps left and right indentation on even/odd pages for book-style layouts.
///
/// Use case: In bound documents (books, reports), you want wider inner margins
/// (the binding side). On odd pages the binding is on the left; on even pages it's
/// on the right. MirrorIndents makes "Left" become "Inside" and "Right" become "Outside".
///
///
/// Gotcha: This property works in conjunction with the section's mirror margins
/// setting (w:mirrorMargins in sectPr). If the section does not have mirror margins
/// enabled, this property has limited effect.
///
///
public static void ApplyMirrorIndents(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
pPr.MirrorIndents = new MirrorIndents();
}
// ──────────────────────────────────────────────────────────────────
// 14. Snap to Grid (w:snapToGrid)
// ──────────────────────────────────────────────────────────────────
///
/// Controls whether paragraph text aligns to the document grid.
///
/// Document grid: Defined in the section properties (w:docGrid), the grid
/// specifies a fixed layout for character and line placement. This is primarily
/// used in CJK documents where characters should align to a uniform grid.
///
///
/// Val = true (default): Text snaps to the grid positions, ensuring uniform
/// character spacing and line heights across the page.
///
///
/// Val = false: Text ignores the document grid. Useful for paragraphs
/// that contain only Western text in a CJK document, where grid alignment
/// would create too much spacing.
///
///
public static void ApplySnapToGrid(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Disable grid snapping for this paragraph
pPr.SnapToGrid = new SnapToGrid { Val = false };
// Re-enable (explicit, same as default)
// pPr.SnapToGrid = new SnapToGrid { Val = true };
}
// ──────────────────────────────────────────────────────────────────
// 15. Suppress Auto-Hyphenation (w:suppressAutoHyphens)
// ──────────────────────────────────────────────────────────────────
///
/// Disables automatic hyphenation for this paragraph.
///
/// Background: When document-level auto-hyphenation is enabled
/// (in document settings), Word breaks long words at line endings with hyphens.
/// This property overrides that for specific paragraphs.
///
///
/// Use case: Disable hyphenation for headings, proper nouns, code blocks,
/// or any text where breaking words would be inappropriate.
///
///
public static void ApplySuppressAutoHyphens(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
pPr.SuppressAutoHyphens = new SuppressAutoHyphens();
// Re-enable auto-hyphenation (override style that suppresses):
// pPr.SuppressAutoHyphens = new SuppressAutoHyphens { Val = false };
}
// ──────────────────────────────────────────────────────────────────
// 16. Paragraph Style (w:pStyle)
// ──────────────────────────────────────────────────────────────────
///
/// Applies a named paragraph style.
///
/// Val: The style ID (not the display name). Built-in style IDs include:
/// "Normal", "Heading1" through "Heading9", "Title", "Subtitle",
/// "ListParagraph", "NoSpacing", "Quote", "IntenseQuote",
/// "TOCHeading", "TOC1" through "TOC9", "Header", "Footer",
/// "FootnoteText", "EndnoteText", "Caption", "Bibliography", etc.
///
///
/// Gotcha: Style IDs are locale-independent (always English) even in
/// non-English installations of Word. The display name is localized, but the
/// ID stays the same. "Heading1" is always "Heading1" regardless of language.
///
///
/// Gotcha: Custom styles use whatever ID was assigned at creation time.
/// The ID may contain spaces or special characters. Always verify the actual
/// style ID in styles.xml rather than guessing from the display name.
///
///
/// Gotcha: If the referenced style does not exist in styles.xml, Word
/// falls back to the "Normal" style silently.
///
///
public static void ApplyParagraphStyle(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Apply Heading 1 style
pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Heading1" };
// Other common built-in style IDs:
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Normal" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Heading2" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Title" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Subtitle" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "ListParagraph" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "NoSpacing" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Quote" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "TOC1" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Header" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Footer" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "FootnoteText" };
// pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Caption" };
}
// ──────────────────────────────────────────────────────────────────
// 17. Frame Properties (w:framePr) — positioned paragraph
// ──────────────────────────────────────────────────────────────────
///
/// Makes a paragraph into a positioned text frame (an anchored box of text).
///
/// Use case: Drop caps, pull quotes, sidebar text, positioned labels.
/// FrameProperties turns a paragraph into a floating frame that can be positioned
/// relative to the page, margin, or text.
///
///
/// Properties:
///
/// - Width (w) — Frame width in DXA. 0 = auto (fit content).
/// - Height (h) — Frame height in DXA. 0 = auto.
/// - HeightRule (hRule) — Auto, AtLeast, or Exact.
/// - HorizontalPosition (x) — Horizontal offset in DXA.
/// - VerticalPosition (y) — Vertical offset in DXA.
/// - HorizontalSpace (hSpace) — Horizontal clearance in DXA.
/// - VerticalSpace (vSpace) — Vertical clearance in DXA.
/// - Anchor — Vertical anchor: Text, Margin, or Page.
/// - AnchorLock — Prevents repositioning in Word UI.
/// - DropCap — DropCapLocationValues: None, Drop, Margin.
/// - Lines — Number of lines for a drop cap (typically 2–4).
/// - Wrap — Text wrapping: Auto, NotBeside, Around, Tight, Through, None.
///
///
///
/// Gotcha: Frame properties are a legacy positioning mechanism. For modern
/// documents, consider using DrawingML text boxes instead. However, framePr is still
/// the standard way to create drop caps in OOXML.
///
///
public static void ApplyFrameProperties(Paragraph para)
{
var pPr = para.GetOrCreateParagraphProperties();
// Drop cap: 3-line dropped initial capital
pPr.FrameProperties = new FrameProperties
{
DropCap = DropCapLocationValues.Drop,
Lines = 3, // Span 3 lines of body text
HorizontalSpace = "72", // 72 DXA = ~3.6pt clearance
Wrap = TextWrappingValues.Around // Body text wraps around
};
// Positioned frame (floating text box)
// pPr.FrameProperties = new FrameProperties
// {
// Width = "2880", // 2 inches wide
// Height = "1440", // 1 inch tall
// HeightRule = HeightRuleValues.AtLeast,
// X = "4320", // 3 inches from anchor
// Y = "1440", // 1 inch from anchor
// HorizontalSpace = "144", // 0.1 inch horizontal clearance
// VerticalSpace = "72", // ~3.6pt vertical clearance
// VerticalAnchor = VerticalAnchorValues.Text,
// Wrap = TextWrappingValues.Around,
// AnchorLock = true
// };
}
// ──────────────────────────────────────────────────────────────────
// 18. Fully Formatted Paragraph (combining multiple properties)
// ──────────────────────────────────────────────────────────────────
///
/// Creates a fully formatted paragraph combining multiple paragraph properties.
/// Demonstrates the correct construction order and element nesting.
///
/// Structure:
/// <w:p><w:pPr>...</w:pPr><w:r>...</w:r></w:p>
///
///
/// Key principle: ParagraphProperties must be the FIRST child of the paragraph.
/// Runs, hyperlinks, and other content follow after.
///
///
public static Paragraph CreateFullyFormattedParagraph()
{
// Build ParagraphProperties first
var pPr = new ParagraphProperties();
// 1. Style (must be first child per schema)
pPr.ParagraphStyleId = new ParagraphStyleId { Val = "Heading1" };
// 2. Pagination
pPr.KeepNext = new KeepNext();
pPr.KeepLines = new KeepLines();
// 3. Page break
// pPr.PageBreakBefore = new PageBreakBefore();
// 4. Widow/orphan control
pPr.WidowControl = new WidowControl();
// 5. Numbering
// pPr.NumberingProperties = new NumberingProperties
// {
// NumberingLevelReference = new NumberingLevelReference { Val = 0 },
// NumberingId = new NumberingId { Val = 1 }
// };
// 6. Borders
pPr.ParagraphBorders = new ParagraphBorders(
new BottomBorder
{
Val = BorderValues.Single,
Size = 8,
Space = 4,
Color = "4472C4"
}
);
// 7. Shading
pPr.Shading = new Shading
{
Val = ShadingPatternValues.Clear,
Fill = "F2F2F2"
};
// 8. Tab stops
pPr.Tabs = new Tabs(
new TabStop
{
Val = TabStopValues.Right,
Position = 9360, // Right margin tab
Leader = TabStopLeaderCharValues.Dot
}
);
// 9. Suppress hyphenation
pPr.SuppressAutoHyphens = new SuppressAutoHyphens();
// 10. Spacing (before/after + line spacing)
pPr.SpacingBetweenLines = new SpacingBetweenLines
{
Before = "240", // 12pt before
After = "120", // 6pt after
Line = "276", // 1.15x line spacing
LineRule = LineSpacingRuleValues.Auto
};
// 11. Indentation
pPr.Indentation = new Indentation
{
Left = "360", // 0.25 inch left indent
FirstLine = "0" // No additional first-line indent
};
// 12. Justification
pPr.Justification = new Justification { Val = JustificationValues.Both };
// 13. Outline level (for TOC)
pPr.OutlineLevel = new OutlineLevel { Val = 0 };
// 14. Paragraph-level run properties (default formatting for runs in this para)
// This sets the DEFAULT run formatting — individual runs can override.
pPr.ParagraphMarkRunProperties = new ParagraphMarkRunProperties(
new RunFonts { Ascii = "Georgia", HighAnsi = "Georgia" },
new Bold(),
new FontSize { Val = "28" }, // 14pt
new Color { Val = "2F5496" }
);
// Build the paragraph
var para = new Paragraph();
para.ParagraphProperties = pPr;
// Add a run with text
var run = new Run(
new RunProperties(
new RunFonts { Ascii = "Georgia", HighAnsi = "Georgia" },
new Bold(),
new FontSize { Val = "28" },
new Color { Val = "2F5496" }
),
new Text("Chapter 1: Introduction") { Space = SpaceProcessingModeValues.Preserve }
);
para.AppendChild(run);
return para;
}
// ──────────────────────────────────────────────────────────────────
// 19. BuildParagraphProperties helper — recommended property order
// ──────────────────────────────────────────────────────────────────
///
/// Helper that constructs ParagraphProperties with elements in the correct schema order.
///
/// OOXML schema order for w:pPr children (ISO 29500-1, section 17.3.1.26):
///
/// - w:pStyle — Paragraph style reference
/// - w:keepNext — Keep with next paragraph
/// - w:keepLines — Keep lines together
/// - w:pageBreakBefore — Page break before
/// - w:framePr — Frame/text-box properties
/// - w:widowControl — Widow/orphan control
/// - w:numPr — Numbering properties
/// - w:suppressLineNumbers — Suppress line numbers
/// - w:pBdr — Paragraph borders
/// - w:shd — Shading
/// - w:tabs — Tab stops
/// - w:suppressAutoHyphens — Suppress auto-hyphenation
/// - w:kinsoku — CJK line-breaking rules
/// - w:wordWrap — Allow word-level wrapping (CJK)
/// - w:overflowPunct — Allow overflow punctuation (CJK)
/// - w:topLinePunct — Top-line punctuation compression (CJK)
/// - w:autoSpaceDE — Auto-space between CJK and Western text
/// - w:autoSpaceDN — Auto-space between CJK text and numbers
/// - w:bidi — Bidirectional (RTL paragraph)
/// - w:adjustRightInd — Auto-adjust right indent for grid
/// - w:snapToGrid — Snap to document grid
/// - w:spacing — Line and paragraph spacing
/// - w:ind — Indentation
/// - w:contextualSpacing — Contextual spacing
/// - w:mirrorIndents — Mirror indents for odd/even pages
/// - w:suppressOverlap — Suppress frame overlap
/// - w:jc — Justification (alignment)
/// - w:textDirection — Text direction
/// - w:textAlignment — Text vertical alignment within line
/// - w:textboxTightWrap — Text box tight wrap
/// - w:outlineLvl — Outline level
/// - w:divId — HTML div ID
/// - w:cnfStyle — Conditional formatting style
/// - w:rPr — Paragraph mark run properties
/// - w:sectPr — Section properties (last pPr in section)
/// - w:pPrChange — Revision tracking for paragraph properties
///
///
///
/// Gotcha: Like RunProperties, when using strongly-typed SDK properties
/// (e.g., pPr.Justification = new Justification { ... }), the SDK handles
/// serialization order automatically. If using AppendChild(), you must
/// maintain the schema order yourself.
///
///
/// Paragraph style ID. Null to skip.
/// Alignment. Null to skip.
/// Space before in points. Null to skip.
/// Space after in points. Null to skip.
/// Line spacing multiplier (e.g., 1.0, 1.15, 1.5, 2.0).
/// Null to skip. Only applies Auto line rule.
/// Left indent in DXA. Null to skip.
/// First line indent in DXA. Null to skip.
/// Use negative value for hanging indent (will be set as Hanging).
/// A well-ordered ParagraphProperties element.
public static ParagraphProperties BuildParagraphProperties(
string? styleId = null,
JustificationValues? justification = null,
double? spacingBeforePt = null,
double? spacingAfterPt = null,
double? lineSpacingMultiplier = null,
int? leftIndentDxa = null,
int? firstLineIndentDxa = null)
{
var pPr = new ParagraphProperties();
// Style reference (schema position 1)
if (styleId is not null)
{
pPr.ParagraphStyleId = new ParagraphStyleId { Val = styleId };
}
// Spacing (schema position 22) — combines para spacing and line spacing
if (spacingBeforePt is not null || spacingAfterPt is not null || lineSpacingMultiplier is not null)
{
var spacing = new SpacingBetweenLines();
if (spacingBeforePt is not null)
{
// Points to DXA: 1pt = 20 DXA
spacing.Before = ((int)(spacingBeforePt.Value * 20)).ToString();
}
if (spacingAfterPt is not null)
{
spacing.After = ((int)(spacingAfterPt.Value * 20)).ToString();
}
if (lineSpacingMultiplier is not null)
{
// Auto mode: multiplier × 240 = value
spacing.Line = ((int)(lineSpacingMultiplier.Value * 240)).ToString();
spacing.LineRule = LineSpacingRuleValues.Auto;
}
pPr.SpacingBetweenLines = spacing;
}
// Indentation (schema position 23)
if (leftIndentDxa is not null || firstLineIndentDxa is not null)
{
var ind = new Indentation();
if (leftIndentDxa is not null)
{
ind.Left = leftIndentDxa.Value.ToString();
}
if (firstLineIndentDxa is not null)
{
if (firstLineIndentDxa.Value >= 0)
{
ind.FirstLine = firstLineIndentDxa.Value.ToString();
}
else
{
// Negative value → hanging indent (positive DXA stored in Hanging)
ind.Hanging = Math.Abs(firstLineIndentDxa.Value).ToString();
}
}
pPr.Indentation = ind;
}
// Justification (schema position 26)
if (justification is not null)
{
pPr.Justification = new Justification { Val = justification };
}
return pPr;
}
// ──────────────────────────────────────────────────────────────────
// Internal helper: get or create ParagraphProperties
// ──────────────────────────────────────────────────────────────────
///
/// Gets the existing ParagraphProperties or creates and attaches a new one.
/// Ensures ParagraphProperties is always the first child of the paragraph.
///
private static ParagraphProperties GetOrCreateParagraphProperties(this Paragraph para)
{
if (para.ParagraphProperties is not null)
return para.ParagraphProperties;
var pPr = new ParagraphProperties();
para.ParagraphProperties = pPr;
return pPr;
}
}