*! addlogo 1.0.0  Eric Booth  18jul2026
*! Stamp a logo onto a Stata graph: export the graph, composite a logo, save it.
*!
*! Syntax:
*!   addlogo [using imagefile] , logo(file) saving(file) [options]
*!
*! With no  using , the CURRENT graph (or the one in name()) is exported first.
*! A  using  ending in .gph is opened and rendered; any other  using  is treated
*! as an already-exported image (png/tif/jpg/...).
*!
*! Options:
*!   logo(file)      logo image, ideally a PNG with transparency          (required)
*!   saving(file)    output file; format inferred from extension          (required)
*!   position(str)   nw n ne w c e sw s se                     (default: se)
*!   scale(#)        logo width as a fraction of graph width    (default: .12)
*!   pad(#)          margin from edge as a fraction of width    (default: .02)
*!   opacity(#)      0..1 alpha multiplier for the logo         (default: 1)
*!   width(#)        export width in px for the current graph   (default: 2400)
*!   name(str)       export a specific named graph in memory
*!   engine(str)     auto | python | magick                    (default: auto)
*!   pyexec(str)     python interpreter that has Pillow
*!   magickexec(str) path to the ImageMagick 'magick' binary
*!   replace         overwrite saving() if it exists
*!
*! Set-once defaults (e.g. in profile.do) avoid macOS PATH surprises:
*!   global ADDLOGO_PY     "/opt/homebrew/bin/python3"   // must have Pillow
*!   global ADDLOGO_MAGICK "/opt/homebrew/bin/magick"

program define addlogo, rclass
    version 16.0

    syntax [using/] ,                       ///
        Logo(string)                        ///
        SAVing(string)                      ///
        [                                   ///
        POSition(string)                    ///
        SCale(real 0.12)                    ///
        PAD(real 0.02)                      ///
        OPacity(real 1.0)                   ///
        Width(integer 2400)                 ///
        Name(string)                        ///
        ENGine(string)                      ///
        PYexec(string)                      ///
        MAGICKexec(string)                  ///
        replace                             ///
        ]

    // ---------- defaults & light validation ----------
    if ("`position'" == "") local position se
    local position = lower("`position'")
    if (`"`engine'"' == "") local engine auto
    local engine = lower("`engine'")
    if (!inlist("`engine'","auto","python","magick")) {
        di as error "addlogo: engine() must be auto, python, or magick"
        exit 198
    }
    if (`opacity' < 0 | `opacity' > 1) {
        di as error "addlogo: opacity() must be between 0 and 1"
        exit 198
    }

    capture confirm file "`logo'"
    if (_rc) {
        di as error "addlogo: logo file not found: `logo'"
        exit 601
    }

    // ---------- overwrite guard ----------
    if ("`replace'" == "") {
        capture confirm file "`saving'"
        if (_rc == 0) {
            di as error "addlogo: `saving' already exists (specify -replace-)"
            exit 602
        }
    }

    // ---------- resolve the base image ----------
    // The intermediate export goes to a clean sibling of saving(): Stata
    // tempfile names contain a period, which confuses graph export's file-
    // type (suffix) detection.
    local dot = strrpos(`"`saving'"', ".")
    if (`dot' > 0) local stub = substr(`"`saving'"', 1, `dot' - 1)
    else           local stub `"`saving'"'
    local basepng `"`stub'__addlogo_base.png"'
    local madebase 0

    if (`"`using'"' != "") {
        capture confirm file `"`using'"'
        if (_rc) {
            di as error "addlogo: file not found: `using'"
            exit 601
        }
        if (substr(lower(`"`using'"'), -4, 4) == ".gph") {
            capture noisily graph use `"`using'"'
            if (_rc) exit _rc
            capture noisily graph export `"`basepng'"', width(`width') replace
            if (_rc) exit _rc
            local base `"`basepng'"'
            local madebase 1
        }
        else {
            local base `"`using'"'
        }
    }
    else {
        if ("`name'" != "") local gname name(`name')
        capture noisily graph export `"`basepng'"', width(`width') replace `gname'
        if (_rc) {
            di as error "addlogo: could not export the current graph " ///
                        "(is a graph open, or pass a name()/using?)"
            exit _rc
        }
        local base `"`basepng'"'
        local madebase 1
    }

    // ---------- resolve engine executables (survive minimal GUI PATH) ----------
    local magick ""
    if (`"`magickexec'"' != "")            local magick `"`magickexec'"'
    else if (`"$ADDLOGO_MAGICK"' != "")    local magick "$ADDLOGO_MAGICK"
    else {
        foreach c in /opt/homebrew/bin/magick /usr/local/bin/magick {
            if ("`magick'" == "") {
                capture confirm file "`c'"
                if (_rc == 0) local magick "`c'"
            }
        }
    }

    local py ""
    if (`"`pyexec'"' != "")          local py `"`pyexec'"'
    else if (`"$ADDLOGO_PY"' != "")  local py "$ADDLOGO_PY"
    else {
        foreach c in /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3 {
            if ("`py'" == "") {
                capture confirm file "`c'"
                if (_rc == 0) local py "`c'"
            }
        }
    }
    if ("`py'" == "") local py "python3"

    if ("`engine'" == "auto") {
        if ("`magick'" != "") local engine magick
        else                  local engine python
    }
    if ("`engine'" == "magick" & "`magick'" == "") local magick "magick"

    // fresh write so the success test below is meaningful
    capture erase "`saving'"

    // ---------- composite ----------
    if ("`engine'" == "python") {
        capture findfile addlogo_helper.py
        if (_rc) {
            di as error "addlogo: cannot find addlogo_helper.py on the adopath " ///
                        "(reinstall the package; keep it beside addlogo.ado)"
            exit 601
        }
        local pyscript "`r(fn)'"
        shell "`py'" "`pyscript'" --base "`base'" --logo "`logo'" ///
              --out "`saving'" --position "`position'" ///
              --scale `scale' --pad `pad' --opacity `opacity'
    }
    else {   // magick
        // base width, to size the logo & pad relative to the graph
        tempfile wtmp
        shell "`magick'" identify -format "%w" "`base'" > "`wtmp'" 2>/dev/null
        capture file close _alf
        file open _alf using "`wtmp'", read text
        file read _alf _wline
        file close _alf
        local bw = real(trim("`_wline'"))
        if (`bw' >= . | `bw' <= 0) {
            di as error "addlogo: could not read base width via ImageMagick"
            exit 459
        }
        local lw = round(`scale' * `bw')
        if (`lw' < 1) local lw 1
        local padpx = round(`pad' * `bw')

        // gravity + offsets from the chosen anchor
        local grav SouthEast
        if ("`position'" == "nw") local grav NorthWest
        if ("`position'" == "n")  local grav North
        if ("`position'" == "ne") local grav NorthEast
        if ("`position'" == "w")  local grav West
        if ("`position'" == "c")  local grav Center
        if ("`position'" == "e")  local grav East
        if ("`position'" == "sw") local grav SouthWest
        if ("`position'" == "s")  local grav South
        if ("`position'" == "se") local grav SouthEast
        local dx `padpx'
        local dy `padpx'
        if (inlist("`position'","n","c","s")) local dx 0
        if (inlist("`position'","w","c","e")) local dy 0

        // resize + apply opacity, then composite
        tempfile lgt
        local logopng "`lgt'.png"
        shell "`magick'" "`logo'" -resize `lw'x ///
              -channel A -evaluate multiply `opacity' +channel "`logopng'"
        shell "`magick'" "`base'" "`logopng'" ///
              -gravity `grav' -geometry +`dx'+`dy' -composite "`saving'"
    }

    if (`madebase') capture erase `"`basepng'"'

    // ---------- confirm & report ----------
    capture confirm file "`saving'"
    if (_rc) {
        di as error "addlogo: compositing failed (engine=`engine'); see messages above."
        exit 459
    }
    di as text "addlogo: wrote " as result `""`saving'""' ///
       as text "  [engine=`engine', pos=`position', scale=`scale', opacity=`opacity']"

    return local engine "`engine'"
    return local file `"`saving'"'
end