Private/Set-VellumPdfElementMargin.ps1

function Set-VellumPdfElementMargin {
    <#
    .SYNOPSIS
        Applies MarginTop and/or MarginBottom to a VellumPdf layout element.
    .DESCRIPTION
        Internal helper that reads the element's current .Margins EdgeInsets value,
        overrides only the sides passed via -Top or -Bottom, and assigns the result
        back to .Margins. This keeps the margin-override logic in one place rather
        than duplicating the EdgeInsets construction across every Add-VellumPdf*
        cmdlet that supports element-level spacing.

        Only the parameters present in -BoundParameters (the caller's
        $PSBoundParameters) are applied; unbound sides are left at their current
        value.

        Bottom margins on Paragraph, Heading, List and Table only limit the
        element's box in VellumPdf.Layout - they add NO space before the next
        element. Callers for those elements pass -Document and -DeferBottom: the
        requested bottom space is then remembered on the document
        (PSVellumPendingSpace) and added to the TOP margin of the next element.
        Every caller that passes -Document also consumes any pending space.
        Image, LineSeparator and PieChart honour bottom margins natively and do
        not use -DeferBottom.
    .NOTES
        Callers pass their own $PSBoundParameters so this helper can test which
        parameters were actually bound versus simply holding a default value.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Mutates an in-memory layout element; performs no external/system state change.')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object]$Element,

        [double]$Top,

        [double]$Bottom,

        [Parameter(Mandatory)]
        [hashtable]$BoundParameters,

        [object]$Document,

        [switch]$DeferBottom
    )

    # Space deferred by the previous element's -MarginBottom joins this
    # element's top margin (and is consumed whether or not margins were bound).
    $pending = 0.0
    if ($Document) {
        $prop = $Document.PSObject.Properties['PSVellumPendingSpace']
        if ($prop -and $prop.Value) {
            $pending = [double]$prop.Value
            $prop.Value = 0.0
        }
    }

    $wantsTop    = $BoundParameters.ContainsKey('MarginTop')
    $wantsBottom = $BoundParameters.ContainsKey('MarginBottom')

    if ($Document -and $DeferBottom -and $wantsBottom -and $Bottom -ne 0) {
        if ($Document.PSObject.Properties['PSVellumPendingSpace']) {
            $Document.PSVellumPendingSpace = [double]$Bottom
        } else {
            $Document.PSObject.Properties.Add([psnoteproperty]::new('PSVellumPendingSpace', [double]$Bottom))
        }
    }

    if (-not $wantsTop -and -not $wantsBottom -and $pending -eq 0) {
        return
    }

    $cur = $Element.Margins
    $newTop    = ($(if ($wantsTop) { $Top } else { $cur.Top })) + $pending
    $newRight  = $cur.Right
    # A deferred bottom margin must not also shrink this element's own box.
    $newBottom = if ($wantsBottom -and -not ($Document -and $DeferBottom)) { $Bottom } else { $cur.Bottom }
    $newLeft   = $cur.Left

    $Element.Margins = [VellumPdf.Layout.Core.EdgeInsets]::new($newTop, $newRight, $newBottom, $newLeft)
}