Docs/Convert-MdToPdf.ps1

# Convert-MdToPdf.ps1 - dependency-free Markdown -> PDF for the SqlCertForge docs.
# Pipeline: PowerShell 7 ConvertFrom-Markdown -> styled HTML -> headless Edge --print-to-pdf.
# Relative image paths (e.g. images/x.svg) resolve because the temp HTML is written
# beside the .md. Requires PowerShell 7 (ConvertFrom-Markdown) and Microsoft Edge.
[CmdletBinding()]
param(
    [Parameter(Mandatory)][string] $MdPath,
    [string] $PdfPath,
    [string] $Title = 'SqlCertForge'
)

$md = Get-Content -LiteralPath $MdPath -Raw
$bodyHtml = (ConvertFrom-Markdown -InputObject $md).Html

$css = @'
:root{--ink:#1c2530;--muted:#5b6b7b;--accent:#0b6bcb;--rule:#dde5ec;--codebg:#f4f7fa;}
*{box-sizing:border-box;} body{font-family:"Segoe UI",Arial,sans-serif;color:var(--ink);line-height:1.55;font-size:12.5px;max-width:800px;margin:0 auto;padding:8px 4px;}
h1{font-size:26px;border-bottom:3px solid var(--accent);padding-bottom:6px;margin-top:0;}
h2{font-size:19px;margin-top:26px;border-bottom:1px solid var(--rule);padding-bottom:3px;}
h3{font-size:15px;margin-top:18px;color:#243447;}
code{background:var(--codebg);padding:1px 5px;border-radius:4px;font-family:"Cascadia Code",Consolas,monospace;font-size:11.5px;}
pre{background:var(--codebg);border:1px solid var(--rule);border-radius:6px;padding:10px 12px;overflow:auto;}
pre code{background:none;padding:0;}
table{border-collapse:collapse;width:100%;margin:12px 0;font-size:11.5px;}
th,td{border:1px solid var(--rule);padding:6px 9px;text-align:left;vertical-align:top;}
th{background:#eef4fb;}
blockquote{border-left:4px solid var(--accent);margin:12px 0;padding:4px 14px;color:var(--muted);background:#f7fafd;}
img,svg{max-width:100%;height:auto;}
a{color:var(--accent);text-decoration:none;}
hr{border:none;border-top:1px solid var(--rule);margin:22px 0;}
@page{margin:14mm 12mm;}
'@


$doc = "<!DOCTYPE html><html lang='en'><head><meta charset='utf-8'><title>$Title</title><style>$css</style></head><body>$bodyHtml</body></html>"

$dir = Split-Path -Parent (Resolve-Path -LiteralPath $MdPath)
$htmlPath = Join-Path $dir ((Split-Path -Leaf $MdPath) -replace '\.md$', '.gen.html')
[System.IO.File]::WriteAllText($htmlPath, $doc, (New-Object System.Text.UTF8Encoding($true)))

if (-not $PdfPath) { $PdfPath = ($MdPath -replace '\.md$', '.pdf') }

$edge = @(
    "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe",
    "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe"
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $edge) { throw 'Microsoft Edge not found for PDF rendering.' }

$fileUri = 'file:///' + ($htmlPath -replace '\\', '/')

# Render to a temp file, then move it into place. Writing straight to $PdfPath
# fails silently when the target already exists and is held open (a PDF viewer is
# enough), which the old Test-Path check could never detect -- it saw the STALE
# file and reported success. Rendering fresh and comparing makes a no-op visible.
$tempPdf = Join-Path ([System.IO.Path]::GetTempPath()) ("scfpdf-" + [guid]::NewGuid().ToString('N') + ".pdf")
& $edge --headless=new --disable-gpu --no-pdf-header-footer "--print-to-pdf=$tempPdf" $fileUri 2>$null

# Edge writes asynchronously; wait for the file to appear AND stop growing.
$deadline = (Get-Date).AddSeconds(60)
$lastSize = -1
while ((Get-Date) -lt $deadline) {
    Start-Sleep -Milliseconds 500
    if (-not (Test-Path $tempPdf)) { continue }
    $size = (Get-Item $tempPdf).Length
    if ($size -gt 0 -and $size -eq $lastSize) { break }
    $lastSize = $size
}

if (-not (Test-Path $tempPdf) -or (Get-Item $tempPdf).Length -eq 0) {
    "PDF NOT created -- Edge produced nothing. Re-run without '2>`$null' on the Edge call to see why."
    return
}

try {
    Move-Item -LiteralPath $tempPdf -Destination $PdfPath -Force -ErrorAction Stop
    "PDF: $PdfPath ($([math]::Round((Get-Item $PdfPath).Length/1kb,1)) KB, written $((Get-Item $PdfPath).LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss')))"
} catch {
    "PDF RENDERED but could NOT replace '$PdfPath': $($_.Exception.Message). Close anything holding it open. The new file is at: $tempPdf"
}