Initial commit: add all skills files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 16:52:49 +08:00
commit 6487becf60
396 changed files with 108871 additions and 0 deletions

View File

@@ -0,0 +1,838 @@
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using A = DocumentFormat.OpenXml.Drawing;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
namespace MiniMaxAIDocx.Core.Samples;
/// <summary>
/// Comprehensive reference for OpenXML headers, footers, and page numbers.
///
/// Architecture:
/// - Headers/footers live in separate HeaderPart/FooterPart containers.
/// - They are linked to sections via HeaderReference/FooterReference in SectionProperties.
/// - Each reference has a Type: Default, First, Even.
/// - The relationship ID (r:id) connects the reference to the part.
///
/// XML structure in SectionProperties:
/// <w:sectPr>
/// <w:headerReference w:type="default" r:id="rId7"/>
/// <w:footerReference w:type="default" r:id="rId8"/>
/// <w:headerReference w:type="first" r:id="rId9"/>
/// <w:titlePg/> <!-- needed to activate first-page header/footer -->
/// </w:sectPr>
///
/// Header/Footer XML (in separate part):
/// <w:hdr> (or <w:ftr>)
/// <w:p>
/// <w:pPr>...</w:pPr>
/// <w:r><w:t>Header text</w:t></w:r>
/// </w:p>
/// </w:hdr>
///
/// Page number fields use complex field codes:
/// PAGE — current page number
/// NUMPAGES — total page count
/// </summary>
public static class HeaderFooterSamples
{
// ──────────────────────────────────────────────────────────────
// 1. AddSimpleHeader — basic text header
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a simple text header to the default header slot.
///
/// Steps:
/// 1. Create a HeaderPart on the MainDocumentPart
/// 2. Set its Header content (must contain at least one Paragraph)
/// 3. Get the relationship ID
/// 4. Add HeaderReference to SectionProperties with type="default"
///
/// XML in header part:
/// <w:hdr>
/// <w:p>
/// <w:pPr><w:jc w:val="right"/></w:pPr>
/// <w:r>
/// <w:rPr><w:color w:val="808080"/><w:sz w:val="18"/></w:rPr>
/// <w:t>My Document Header</w:t>
/// </w:r>
/// </w:p>
/// </w:hdr>
///
/// XML in sectPr:
/// <w:headerReference w:type="default" r:id="rIdXX"/>
/// </summary>
public static void AddSimpleHeader(MainDocumentPart mainPart, SectionProperties sectPr, string text)
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
headerPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right }),
new Run(
new RunProperties(
new Color { Val = "808080" },
new FontSize { Val = "18" }), // 9pt (half-points)
new Text(text) { Space = SpaceProcessingModeValues.Preserve })));
headerPart.Header.Save();
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 2. AddSimpleFooter — basic text footer
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a simple text footer to the default footer slot.
///
/// XML in footer part:
/// <w:ftr>
/// <w:p>
/// <w:pPr><w:jc w:val="center"/></w:pPr>
/// <w:r><w:t>Confidential</w:t></w:r>
/// </w:p>
/// </w:ftr>
///
/// XML in sectPr:
/// <w:footerReference w:type="default" r:id="rIdXX"/>
/// </summary>
public static void AddSimpleFooter(MainDocumentPart mainPart, SectionProperties sectPr, string text)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
footerPart.Footer = new Footer(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(
new Color { Val = "808080" },
new FontSize { Val = "18" }),
new Text(text) { Space = SpaceProcessingModeValues.Preserve })));
footerPart.Footer.Save();
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 3. AddPageNumberFooter — centered page number
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a centered page number footer using the PAGE field code.
///
/// Field code pattern (3 runs):
/// Run 1: FieldChar Begin
/// Run 2: FieldCode " PAGE "
/// Run 3: FieldChar End
///
/// XML:
/// <w:ftr>
/// <w:p>
/// <w:pPr><w:jc w:val="center"/></w:pPr>
/// <w:r><w:fldChar w:fldCharType="begin"/></w:r>
/// <w:r><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
/// <w:r><w:fldChar w:fldCharType="end"/></w:r>
/// </w:p>
/// </w:ftr>
///
/// GOTCHA: FieldCode text MUST have leading/trailing spaces: " PAGE ", not "PAGE".
/// GOTCHA: Use Space = SpaceProcessingModeValues.Preserve on FieldCode to keep spaces.
/// </summary>
public static void AddPageNumberFooter(MainDocumentPart mainPart, SectionProperties sectPr)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
var paragraph = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }));
// PAGE field: Begin → InstrText → End
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
paragraph.Append(new Run(new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
footerPart.Footer = new Footer(paragraph);
footerPart.Footer.Save();
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 4. AddPageXofYFooter — "Page X of Y"
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a footer with "Page X of Y" format using PAGE and NUMPAGES field codes.
///
/// XML:
/// <w:ftr>
/// <w:p>
/// <w:pPr><w:jc w:val="center"/></w:pPr>
/// <w:r><w:t xml:space="preserve">Page </w:t></w:r>
/// <w:r><w:fldChar w:fldCharType="begin"/></w:r>
/// <w:r><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
/// <w:r><w:fldChar w:fldCharType="end"/></w:r>
/// <w:r><w:t xml:space="preserve"> of </w:t></w:r>
/// <w:r><w:fldChar w:fldCharType="begin"/></w:r>
/// <w:r><w:instrText xml:space="preserve"> NUMPAGES </w:instrText></w:r>
/// <w:r><w:fldChar w:fldCharType="end"/></w:r>
/// </w:p>
/// </w:ftr>
/// </summary>
public static void AddPageXofYFooter(MainDocumentPart mainPart, SectionProperties sectPr)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
var paragraph = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }));
// "Page "
paragraph.Append(new Run(new Text("Page ") { Space = SpaceProcessingModeValues.Preserve }));
// PAGE field
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
paragraph.Append(new Run(new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
// " of "
paragraph.Append(new Run(new Text(" of ") { Space = SpaceProcessingModeValues.Preserve }));
// NUMPAGES field
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
paragraph.Append(new Run(new FieldCode(" NUMPAGES ") { Space = SpaceProcessingModeValues.Preserve }));
paragraph.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
footerPart.Footer = new Footer(paragraph);
footerPart.Footer.Save();
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 5. AddDifferentFirstPageHeader — TitlePage element
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a different header for the first page vs. subsequent pages.
///
/// Requires:
/// 1. <w:titlePg/> in SectionProperties to enable first-page header/footer
/// 2. HeaderReference with Type="first" for the first page header
/// 3. HeaderReference with Type="default" for subsequent pages
///
/// XML in sectPr:
/// <w:sectPr>
/// <w:headerReference w:type="first" r:id="rIdFirst"/>
/// <w:headerReference w:type="default" r:id="rIdDefault"/>
/// <w:titlePg/> <!-- CRITICAL: without this, first-page header is ignored -->
/// </w:sectPr>
///
/// GOTCHA: Without <w:titlePg/>, the "first" type header is completely ignored.
/// GOTCHA: If you want a blank first-page header, you still need a HeaderPart
/// with an empty Paragraph — just don't add text to it.
/// </summary>
public static void AddDifferentFirstPageHeader(MainDocumentPart mainPart, SectionProperties sectPr)
{
// First page header: e.g., cover page with large title
var firstHeaderPart = mainPart.AddNewPart<HeaderPart>();
firstHeaderPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(
new Bold(),
new FontSize { Val = "32" }), // 16pt
new Text("COMPANY CONFIDENTIAL"))));
firstHeaderPart.Header.Save();
// Default header for subsequent pages
var defaultHeaderPart = mainPart.AddNewPart<HeaderPart>();
defaultHeaderPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right }),
new Run(
new RunProperties(
new FontSize { Val = "18" }), // 9pt
new Text("Internal Document"))));
defaultHeaderPart.Header.Save();
// Link both headers to section
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.First,
Id = mainPart.GetIdOfPart(firstHeaderPart)
});
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(defaultHeaderPart)
});
// CRITICAL: Enable first page header/footer
sectPr.Append(new TitlePage());
}
// ──────────────────────────────────────────────────────────────
// 6. AddEvenOddHeaders — EvenAndOddHeaders in Settings
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Creates different headers for even and odd pages (e.g., for book-style printing).
///
/// Requires:
/// 1. <w:evenAndOddHeaders/> in document Settings (DocumentSettingsPart)
/// 2. HeaderReference with Type="default" for odd pages
/// 3. HeaderReference with Type="even" for even pages
///
/// XML in settings.xml:
/// <w:settings>
/// <w:evenAndOddHeaders/>
/// </w:settings>
///
/// XML in sectPr:
/// <w:sectPr>
/// <w:headerReference w:type="default" r:id="rIdOdd"/>
/// <w:headerReference w:type="even" r:id="rIdEven"/>
/// </w:sectPr>
///
/// GOTCHA: "default" means ODD pages when evenAndOddHeaders is enabled.
/// GOTCHA: Without the Settings flag, the "even" header is ignored entirely.
/// </summary>
public static void AddEvenOddHeaders(MainDocumentPart mainPart, SectionProperties sectPr)
{
// Enable even/odd header distinction in document settings
var settingsPart = mainPart.DocumentSettingsPart
?? mainPart.AddNewPart<DocumentSettingsPart>();
if (settingsPart.Settings == null)
settingsPart.Settings = new Settings();
// Add EvenAndOddHeaders if not already present
if (settingsPart.Settings.GetFirstChild<EvenAndOddHeaders>() == null)
{
settingsPart.Settings.Append(new EvenAndOddHeaders());
}
settingsPart.Settings.Save();
// Odd page header (Type="default" means odd when even/odd is enabled)
var oddHeaderPart = mainPart.AddNewPart<HeaderPart>();
oddHeaderPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right }),
new Run(new Text("Chapter Title — Odd Page"))));
oddHeaderPart.Header.Save();
// Even page header
var evenHeaderPart = mainPart.AddNewPart<HeaderPart>();
evenHeaderPart.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Left }),
new Run(new Text("Book Title — Even Page"))));
evenHeaderPart.Header.Save();
// Link to section
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default, // = odd pages
Id = mainPart.GetIdOfPart(oddHeaderPart)
});
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Even,
Id = mainPart.GetIdOfPart(evenHeaderPart)
});
}
// ──────────────────────────────────────────────────────────────
// 7. AddHeaderWithLogo — image in header
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a header containing an image (logo).
///
/// Steps:
/// 1. Create HeaderPart
/// 2. Add ImagePart to the HeaderPart (NOT to MainDocumentPart)
/// 3. Feed the image stream
/// 4. Build Drawing element with inline image
/// 5. Link HeaderPart to sectPr
///
/// Image sizing uses EMU (English Metric Units):
/// 914400 EMU = 1 inch
/// 360000 EMU = 1 cm
///
/// XML for inline image:
/// <w:drawing>
/// <wp:inline distT="0" distB="0" distL="0" distR="0">
/// <wp:extent cx="914400" cy="457200"/>
/// <wp:docPr id="1" name="Logo"/>
/// <a:graphic>
/// <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
/// <pic:pic>
/// <pic:nvPicPr>...</pic:nvPicPr>
/// <pic:blipFill><a:blip r:embed="rIdImg"/></pic:blipFill>
/// <pic:spPr>...</pic:spPr>
/// </pic:pic>
/// </a:graphicData>
/// </a:graphic>
/// </wp:inline>
/// </w:drawing>
///
/// GOTCHA: The ImagePart must be added to the HeaderPart, not the MainDocumentPart.
/// If you add it to MainDocumentPart, the relationship ID won't resolve in the header.
/// </summary>
public static void AddHeaderWithLogo(MainDocumentPart mainPart, SectionProperties sectPr, string imagePath)
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
// Add image part to the HEADER part (not main document part)
var imagePart = headerPart.AddImagePart(ImagePartType.Png);
using (var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
{
imagePart.FeedData(stream);
}
var imageRelId = headerPart.GetIdOfPart(imagePart);
// Image dimensions in EMU: 1 inch wide x 0.5 inch tall
long widthEmu = 914400; // 1 inch
long heightEmu = 457200; // 0.5 inch
// Build the Drawing element with inline image
var drawing = new Drawing(
new DW.Inline(
new DW.Extent { Cx = widthEmu, Cy = heightEmu },
new DW.EffectExtent { LeftEdge = 0, TopEdge = 0, RightEdge = 0, BottomEdge = 0 },
new DW.DocProperties { Id = 1U, Name = "Logo" },
new A.Graphic(
new A.GraphicData(
new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties { Id = 0U, Name = "logo.png" },
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip { Embed = imageRelId },
new A.Stretch(new A.FillRectangle())),
new PIC.ShapeProperties(
new A.Transform2D(
new A.Offset { X = 0, Y = 0 },
new A.Extents { Cx = widthEmu, Cy = heightEmu }),
new A.PresetGeometry(
new A.AdjustValueList())
{ Preset = A.ShapeTypeValues.Rectangle }))
) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" })
)
{
DistanceFromTop = 0U,
DistanceFromBottom = 0U,
DistanceFromLeft = 0U,
DistanceFromRight = 0U
});
headerPart.Header = new Header(
new Paragraph(new Run(drawing)));
headerPart.Header.Save();
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 8. AddTableLayoutHeader — 3-column invisible table
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Creates a header with a 3-column invisible table for precise layout:
/// Left cell: Logo placeholder text
/// Center cell: Document title (centered)
/// Right cell: Page number (right-aligned)
///
/// The table has no borders, so it's invisible but provides column alignment.
///
/// XML structure:
/// <w:hdr>
/// <w:tbl>
/// <w:tblPr>
/// <w:tblW w:w="5000" w:type="pct"/>
/// <w:tblBorders>
/// <w:top w:val="none"/> <w:left w:val="none"/> ...
/// </w:tblBorders>
/// </w:tblPr>
/// <w:tblGrid>
/// <w:gridCol w:w="3120"/> <w:gridCol w:w="3120"/> <w:gridCol w:w="3120"/>
/// </w:tblGrid>
/// <w:tr>
/// <w:tc> <!-- left: logo text --> </w:tc>
/// <w:tc> <!-- center: title --> </w:tc>
/// <w:tc> <!-- right: page num --> </w:tc>
/// </w:tr>
/// </w:tbl>
/// </w:hdr>
/// </summary>
public static void AddTableLayoutHeader(MainDocumentPart mainPart, SectionProperties sectPr)
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
// Invisible table (no borders)
var table = new Table();
var tblPr = new TableProperties(
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
new TableBorders(
new TopBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new LeftBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new BottomBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new RightBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new InsideHorizontalBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new InsideVerticalBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" }
),
// Fixed layout so columns don't shift
new TableLayout { Type = TableLayoutValues.Fixed });
table.Append(tblPr);
var grid = new TableGrid(
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" });
table.Append(grid);
var row = new TableRow();
// Left cell: logo/company name
var leftCell = new TableCell(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Left }),
new Run(
new RunProperties(new Bold(), new FontSize { Val = "18" }),
new Text("ACME Corp"))));
row.Append(leftCell);
// Center cell: document title
var centerCell = new TableCell(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(new FontSize { Val = "18" }),
new Text("Technical Report"))));
row.Append(centerCell);
// Right cell: page number
var pageNumPara = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Right }));
pageNumPara.Append(new Run(
new RunProperties(new FontSize { Val = "18" }),
new Text("Page ") { Space = SpaceProcessingModeValues.Preserve }));
pageNumPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
pageNumPara.Append(new Run(new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
pageNumPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
var rightCell = new TableCell(pageNumPara);
row.Append(rightCell);
table.Append(row);
headerPart.Header = new Header(table);
headerPart.Header.Save();
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 9. AddChineseGongWenFooter — "-X-" format, SimSun 14pt
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a Chinese government document (公文) style footer:
/// - Page number in "-X-" format (e.g., "- 1 -")
/// - Centered at bottom
/// - SimSun (宋体) font, 14pt (Chinese 四号)
///
/// XML:
/// <w:ftr>
/// <w:p>
/// <w:pPr><w:jc w:val="center"/></w:pPr>
/// <w:r>
/// <w:rPr>
/// <w:rFonts w:ascii="SimSun" w:eastAsia="SimSun"/>
/// <w:sz w:val="28"/>
/// </w:rPr>
/// <w:t xml:space="preserve">- </w:t>
/// </w:r>
/// <w:r>..PAGE field..</w:r>
/// <w:r>
/// <w:rPr>...</w:rPr>
/// <w:t xml:space="preserve"> -</w:t>
/// </w:r>
/// </w:p>
/// </w:ftr>
///
/// Chinese font size reference:
/// 四号 = 14pt = sz val="28" (half-points)
/// 小四 = 12pt = sz val="24"
/// 五号 = 10.5pt = sz val="21"
/// </summary>
public static void AddChineseGongWenFooter(MainDocumentPart mainPart, SectionProperties sectPr)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
// Common run properties for the footer: SimSun 14pt (四号)
// 14pt = 28 half-points
RunProperties MakeGongWenRunProps() => new RunProperties(
new RunFonts { Ascii = "SimSun", EastAsia = "SimSun", HighAnsi = "SimSun" },
new FontSize { Val = "28" },
new FontSizeComplexScript { Val = "28" });
var paragraph = new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }));
// "- " prefix
paragraph.Append(new Run(
MakeGongWenRunProps(),
new Text("- ") { Space = SpaceProcessingModeValues.Preserve }));
// PAGE field with same formatting
paragraph.Append(new Run(
MakeGongWenRunProps(),
new FieldChar { FieldCharType = FieldCharValues.Begin }));
paragraph.Append(new Run(
MakeGongWenRunProps(),
new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
paragraph.Append(new Run(
MakeGongWenRunProps(),
new FieldChar { FieldCharType = FieldCharValues.End }));
// " -" suffix
paragraph.Append(new Run(
MakeGongWenRunProps(),
new Text(" -") { Space = SpaceProcessingModeValues.Preserve }));
footerPart.Footer = new Footer(paragraph);
footerPart.Footer.Save();
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 10. AddHeaderWithHorizontalLine — bottom border line
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Adds a header with a horizontal line (bottom border) beneath the text.
/// This is a common style: header text with a line separating it from content.
///
/// The line is achieved via a paragraph bottom border in the header, NOT a
/// separate drawing element.
///
/// XML:
/// <w:hdr>
/// <w:p>
/// <w:pPr>
/// <w:pBdr>
/// <w:bottom w:val="single" w:sz="6" w:space="1" w:color="000000"/>
/// </w:pBdr>
/// <w:jc w:val="center"/>
/// </w:pPr>
/// <w:r><w:t>Document Header</w:t></w:r>
/// </w:p>
/// </w:hdr>
///
/// Border space attribute: space between text and border line, in points.
/// Border size: in eighth-points (6 = 0.75pt).
/// </summary>
public static void AddHeaderWithHorizontalLine(MainDocumentPart mainPart, SectionProperties sectPr)
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
var paragraph = new Paragraph(
new ParagraphProperties(
new ParagraphBorders(
new BottomBorder
{
Val = BorderValues.Single,
Size = 6, // 0.75pt line (in eighth-points)
Space = 1, // 1pt spacing between text and line
Color = "000000"
}),
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(
new Bold(),
new FontSize { Val = "20" }), // 10pt
new Text("Document Header")));
headerPart.Header = new Header(paragraph);
headerPart.Header.Save();
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// ──────────────────────────────────────────────────────────────
// 11. ChangeHeaderPerSection — different headers per section
// ──────────────────────────────────────────────────────────────
/// <summary>
/// Creates a document with multiple sections, each having its own header.
///
/// In OOXML, sections are delimited by SectionProperties:
/// - Inner sections: sectPr inside a Paragraph's ParagraphProperties (section break)
/// - Last section: sectPr as direct child of Body
///
/// Each sectPr can reference different HeaderPart/FooterPart via its own
/// HeaderReference/FooterReference elements.
///
/// XML structure for multi-section document:
/// <w:body>
/// <!-- Section 1 content -->
/// <w:p><w:r><w:t>Section 1 content</w:t></w:r></w:p>
/// <w:p>
/// <w:pPr>
/// <w:sectPr> <!-- Section 1 break -->
/// <w:headerReference w:type="default" r:id="rId_hdr1"/>
/// <w:type w:val="nextPage"/>
/// </w:sectPr>
/// </w:pPr>
/// </w:p>
///
/// <!-- Section 2 content -->
/// <w:p><w:r><w:t>Section 2 content</w:t></w:r></w:p>
///
/// <!-- Final section properties (last child of body) -->
/// <w:sectPr>
/// <w:headerReference w:type="default" r:id="rId_hdr2"/>
/// </w:sectPr>
/// </w:body>
///
/// GOTCHA: A section break sectPr is placed inside a paragraph's ParagraphProperties.
/// The paragraph that contains the sectPr is the LAST paragraph of that section.
///
/// GOTCHA: If a section does not have its own HeaderReference, it inherits
/// the header from the previous section. To have NO header in a section,
/// you must explicitly link to an empty HeaderPart.
/// </summary>
public static void ChangeHeaderPerSection(MainDocumentPart mainPart, Body body)
{
// --- Create two different header parts ---
// Header for Section 1
var header1Part = mainPart.AddNewPart<HeaderPart>();
header1Part.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Left }),
new Run(new Text("Section 1 — Introduction"))));
header1Part.Header.Save();
// Header for Section 2
var header2Part = mainPart.AddNewPart<HeaderPart>();
header2Part.Header = new Header(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Left }),
new Run(new Text("Section 2 — Analysis"))));
header2Part.Header.Save();
// --- Section 1 content ---
body.Append(new Paragraph(
new Run(new Text("This is content in Section 1."))));
body.Append(new Paragraph(
new Run(new Text("More Section 1 content..."))));
// --- Section 1 break: sectPr inside a paragraph's pPr ---
// This paragraph is the LAST paragraph of Section 1.
var sect1Pr = new SectionProperties(
new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(header1Part)
},
// Section break type: start next section on a new page
new SectionType { Val = SectionMarkValues.NextPage });
// Page size and margins for section 1 (required for valid sectPr)
sect1Pr.Append(new DocumentFormat.OpenXml.Wordprocessing.PageSize
{
Width = (UInt32Value)12240U, // Letter width: 8.5" = 12240 DXA
Height = (UInt32Value)15840U // Letter height: 11" = 15840 DXA
});
sect1Pr.Append(new PageMargin
{
Top = 1440,
Bottom = 1440,
Left = (UInt32Value)1440U,
Right = (UInt32Value)1440U
});
// Wrap the sectPr in a paragraph's ParagraphProperties
var sectionBreakPara = new Paragraph(
new ParagraphProperties(sect1Pr));
body.Append(sectionBreakPara);
// --- Section 2 content ---
body.Append(new Paragraph(
new Run(new Text("This is content in Section 2."))));
body.Append(new Paragraph(
new Run(new Text("More Section 2 content..."))));
// --- Final section: sectPr as last child of Body ---
// This is the sectPr for the LAST section of the document.
var finalSectPr = new SectionProperties(
new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = mainPart.GetIdOfPart(header2Part)
});
finalSectPr.Append(new DocumentFormat.OpenXml.Wordprocessing.PageSize
{
Width = (UInt32Value)12240U,
Height = (UInt32Value)15840U
});
finalSectPr.Append(new PageMargin
{
Top = 1440,
Bottom = 1440,
Left = (UInt32Value)1440U,
Right = (UInt32Value)1440U
});
body.Append(finalSectPr);
}
}