Public/Convert-PCMarkdownToWord.ps1

function Convert-PCMarkdownToWord {
    <#
    .SYNOPSIS
        Renders a markdown file into a themed Word document.

    .DESCRIPTION
        Walks the markdown through the PowerCraft.Word.Core kernel: headings, paragraphs, emphasis, lists,
        tables, code, quotes, links and images map onto the theme's named styles. Images on their own line
        become numbered Word captions. Table of contents and list of figures are real fields, placed at
        <!-- INSERT_TOC_HERE --> and <!-- INSERT_LOF_HERE --> markers when present, otherwise per the
        placement parameters. An optional title-page document is merged ahead of the body with [Token]
        substitution. Writes a build report beside the output when -ReportPath is given.

        Cross-platform. No Word, no COM, no Pandoc.

    .PARAMETER Path
        The markdown file.

    .PARAMETER OutputPath
        The .docx to write. Defaults to the markdown name with a .docx extension.

    .PARAMETER Theme
        A .docx whose styles the renderer uses. Omit for the neutral default theme. See New-PCWordTheme.

    .PARAMETER TitlePage
        A .docx merged ahead of the body, with [Token] placeholders substituted from -Tokens.
        [Title] defaults to the first level-one heading and [Date] to today.

    .PARAMETER Tokens
        Hashtable of token values for the title page, for example @{ SUBTITLE = 'Draft for review' }.

    .PARAMETER Toc
        Where the table of contents goes when no marker is present: AfterTitle (default), BeforeContent, Marker, None.

    .PARAMETER ListOfFigures
        Where the list of figures goes when no marker is present: AfterToc (default), AfterTitle, BeforeContent, Marker, None.

    .PARAMETER HeadingOffset
        Added to heading levels. The default -1 maps # to Title and ## to Heading 1, as the legacy pipeline did.

    .PARAMETER Deterministic
        Fix document timestamps so repeated builds of the same input are structurally identical.

    .PARAMETER Force
        Overwrite an existing output file.

    .PARAMETER ReportPath
        Write the build report as JSON to this path.

    .EXAMPLE
        Convert-PCMarkdownToWord .\update.md -Theme .\Context\style-reference.docx -TitlePage .\Context\title-page.docx -Tokens @{ SUBTITLE = 'Status and milestones' } -Force
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('FullName', 'MarkdownFile')]
        [string]$Path,

        [Parameter(Position = 1)]
        [string]$OutputPath,

        [string]$Theme,

        [string]$TitlePage,

        [hashtable]$Tokens,

        [string]$Title,

        [ValidateSet('AfterTitle', 'BeforeContent', 'Marker', 'None')]
        [string]$Toc = 'AfterTitle',

        [ValidateSet('AfterToc', 'AfterTitle', 'BeforeContent', 'Marker', 'None')]
        [string]$ListOfFigures = 'AfterToc',

        [string]$TocHeading = 'Table of Contents',

        [string]$ListOfFiguresHeading = 'List of Figures',

        [int]$HeadingOffset = -1,

        [switch]$KeepFirstHeading,

        [switch]$NoUpdateFieldsOnOpen,

        [switch]$Deterministic,

        [switch]$Force,

        [string]$ReportPath
    )

    process {
        $markdown = (Resolve-Path -LiteralPath $Path -ErrorAction Stop).Path
        $request = [PowerCraft.Word.Core.DocumentBuildRequest]::new()
        $request.MarkdownPath = $markdown
        if ($OutputPath) { $request.OutputPath = $OutputPath }
        if ($Theme) { $request.ThemePath = (Resolve-Path -LiteralPath $Theme -ErrorAction Stop).Path }
        if ($TitlePage) { $request.TitlePagePath = (Resolve-Path -LiteralPath $TitlePage -ErrorAction Stop).Path }
        if ($Title) { $request.Title = $Title }
        if ($Tokens) { foreach ($key in $Tokens.Keys) { $request.Tokens[[string]$key] = [string]$Tokens[$key] } }
        $request.Toc = [PowerCraft.Word.Core.FieldPlacement]$Toc
        $request.ListOfFigures = [PowerCraft.Word.Core.FieldPlacement]$ListOfFigures
        $request.TocHeadingText = $TocHeading
        $request.LofHeadingText = $ListOfFiguresHeading
        $request.HeadingOffset = $HeadingOffset
        if ($KeepFirstHeading) { $request.RemoveFirstHeading = $false }
        $request.UpdateFieldsOnOpen = -not $NoUpdateFieldsOnOpen
        $request.Deterministic = [bool]$Deterministic
        $request.Overwrite = [bool]$Force

        $target = if ($OutputPath) { $OutputPath } else { [IO.Path]::ChangeExtension($markdown, '.docx') }
        if (-not $PSCmdlet.ShouldProcess($target, 'Render markdown to Word')) { return }

        $result = [PowerCraft.Word.Core.DocumentBuilder]::Build($request)
        $report = $result.Report

        foreach ($warning in $report.Warnings) { Write-Warning $warning }
        if ($ReportPath) {
            [IO.File]::WriteAllText((Join-Path (Get-Location) $ReportPath | ForEach-Object { if ([IO.Path]::IsPathRooted($ReportPath)) { $ReportPath } else { $_ } }), $report.ToJson())
        }

        [pscustomobject]@{
            OutputPath       = $result.OutputPath
            Title            = $report.Title
            Theme            = $report.Theme
            Headings         = $report.Headings
            Images           = $report.Images
            Captions         = $report.Captions
            Tables           = $report.Tables
            Fields           = @($report.Fields)
            TokensUnresolved = @($report.TokensUnresolved)
            StylesMissing    = @($report.StylesMissing)
            ValidationErrors = $report.ValidationErrors
            Warnings         = @($report.Warnings)
            ReportPath       = $ReportPath
        }
    }
}