839 lines
35 KiB
C#
839 lines
35 KiB
C#
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);
|
|
}
|
|
}
|