Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user