if not modules then modules = { } end modules ['lpdf-fld'] = {
    version   = 1.001,
    comment   = "companion to lpdf-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- cleaned up, e.g. no longer older viewers
-- always kids so no longer explicit main / clone / copy
-- some optimizations removed (will come bakc if needed)

local gmatch, lower, format = string.gmatch, string.lower, string.format
local lpegmatch = lpeg.match

local trace_fields = false  trackers.register("widgets.fields",   function(v) trace_fields   = v end)

local texsprint, ctxcatcodes = tex.sprint, tex.ctxcatcodes

local variables = interfaces.variables

local nodeinjections = backends.pdf.nodeinjections
local codeinjections = backends.pdf.codeinjections
local registrations  = backends.pdf.registrations

local registeredsymbol = codeinjections.registeredsymbol

local pdfstream        = lpdf.stream
local pdfdictionary    = lpdf.dictionary
local pdfarray         = lpdf.array
local pdfreference     = lpdf.reference
local pdfunicode       = lpdf.unicode
local pdfstring        = lpdf.string
local pdfconstant      = lpdf.constant
local pdftoeight       = lpdf.toeight
local pdfflushobject   = lpdf.flushobject
local pdfreserveobject = lpdf.reserveobject
local pdfannotation    = nodes.pdfannotation

local submitoutputformat = 0 --  0=unknown 1=HTML 2=FDF 3=XML   => not yet used, needs to be checked

local splitter = lpeg.splitat("=>")

local formats = {
    html = 1, fdf = 2, xml = 3,
}

function codeinjections.setformsmethod(name)
    submitoutputformat = formats[lower(name)] or 3
end

local flag = {
    MultiLine       =     4096, --  13
    NoToggleToOff   =    16384, --  15
    Radio           =    32768, --  16
    PushButton      =    65536, --  17
    PopUp           =   131072, --  18
    Edit            =   262144, --  19
    RadiosInUnison  = 33554432, --  26
    DoNotSpellCheck =  4194304, --  23
    DoNotScroll     =  8388608, --  24
    ReadOnly        =        1, --   1
    Required        =        2, --   2
    NoExport        =        4, --   3
    Password        =     8192, --  14
    Sort            =   524288, --  20
    FileSelect      =  1048576, --  21
}

local plus = {
    Invisible       =        1, --   1
    Hidden          =        2, --   2
    Printable       =        4, --   3
    NoView          =       32, --   6
    ToggleNoView    =      256, --   9
    AutoView        =      256, -- 288 (6+9)
}

-- todo: check what is interfaced

flag.readonly    = flag.ReadOnly
flag.required    = flag.Required
flag.protected   = flag.Password
flag.sorted      = flag.Sort
flag.unavailable = flag.NoExport
flag.nocheck     = flag.DoNotSpellCheck
flag.fixed       = flag.DoNotScroll
flag.file        = flag.FileSelect

plus.hidden      = plus.Hidden
plus.printable   = plus.Printable
plus.auto        = plus.AutoView

-- some day .. lpeg with function or table

local function fieldflag(specification)
    local o, n = specification.options, 0
    if o and o ~= "" then
        for f in gmatch(o,"[^, ]+") do
            n = n + (flag[f] or 0)
        end
    end
    return n
end

local function fieldplus(specification)
    local o, n = specification.options, 0
    if o and o ~= "" then
        for p in gmatch(o,"[^, ]+") do
            n = n + (plus[p] or 0)
        end
    end
    return n
end


local function checked(what)
    if what and what ~= "" then
        local set, bug = jobreferences.identify("",what)
        return not bug and #set > 0 and lpdf.pdfaction(set)
    end
end

local function fieldactions(specification) -- share actions
--~ print(table.serialize(specification))
    local d, a = { }, nil
    a = specification.mousedown         if a and a ~= "" then d.D  = checked(a) end
    a = specification.mouseup           if a and a ~= "" then d.U  = checked(a) end
    a = specification.regionin          if a and a ~= "" then d.E  = checked(a) end -- Enter
    a = specification.regionout         if a and a ~= "" then d.X  = checked(a) end -- eXit
    a = specification.afterkeystroke    if a and a ~= "" then d.K  = checked(a) end
    a = specification.formatresult      if a and a ~= "" then d.F  = checked(a) end
    a = specification.validateresult    if a and a ~= "" then d.V  = checked(a) end
    a = specification.calculatewhatever if a and a ~= "" then d.C  = checked(a) end
    a = specification.focusin           if a and a ~= "" then d.Fo = checked(a) end
    a = specification.focusout          if a and a ~= "" then d.Bl = checked(a) end
 -- a = specification.openpage          if a and a ~= "" then d.PO = checked(a) end
 -- a = specification.closepage         if a and a ~= "" then d.PC = checked(a) end
 -- a = specification.visiblepage       if a and a ~= "" then d.PV = checked(a) end
 -- a = specification.invisiblepage     if a and a ~= "" then d.PI = checked(a) end
    return next(d) and pdfdictionary(d)
end

-- fonts and color

local fontnames = {
    rm = {
        tf = "Times-Roman",
        bf = "Times-Bold",
        it = "Times-Italic",
        sl = "Times-Italic",
        bi = "Times-BoldItalic",
        bs = "Times-BoldItalic",
    },
    ss = {
        tf = "Helvetica",
        bf = "Helvetica-Bold",
        it = "Helvetica-Oblique",
        sl = "Helvetica-Oblique",
        bi = "Helvetica-BoldOblique",
        bs = "Helvetica-BoldOblique",
    },
    tt = {
        tf = "Courier",
        bf = "Courier-Bold",
        it = "Courier-Oblique",
        sl = "Courier-Oblique",
        bi = "Courier-BoldOblique",
        bs = "Courier-BoldOblique",
    }
}

local usedfonts = { }

local function fieldsurrounding(specification)
    local size        = specification.fontsize or "12pt"
    local style       = specification.fontstyle or "rm"
    local alternative = specification.fontalternative or "tf"
    local s = fontnames[style]
    if not s then
        style, s = "rm", fontnames.rm
    end
    local a = s[alternative]
    if not a then
        alternative, a = "tf", s.tf
    end
    local tag = style .. alternative
    size = string.todimen(size)
    local stream = pdfstream {
        pdfconstant(tag),
        format("%0.4f Tf",(size and (number.dimenfactors.bp * size)) or 12),
    }
    usedfonts[tag] = a -- the name
    -- add color to stream: 0 g
    -- move up with "x.y Ts"
    return tostring(stream)
end

local function registerfonts()
    if next(usedfonts) then
        local d = pdfdictionary()
        for tag, name in next, usedfonts do
            local f = pdfdictionary {
                Type     = pdfconstant("Font"),
                Subtype  = pdfconstant("Type1"), -- todo
                Name     = pdfconstant(tag),
                BaseFont = pdfconstant(name),
            }
            d[tag] = pdfreference(pdfflushobject(f))
        end
        return d
    end
end

-- cache

local function fieldattributes(specification)
--~     return pdfarray {
--~     --    BG = -- backgroundcolor
--~     --    BC = -- framecolor
--~     }
    return nil
end

-- symbols

local function fieldappearances(specification)
    -- todo: caching
    local values = specification.values
    local default = specification.default -- todo
    if not values then
        -- error
        return
    end
    local v = aux.settings_to_array(values)
    local n, r, d
    if #v == 1 then
        n, r, d = v[1], v[1], v[1]
    elseif #v == 2 then
        n, r, d = v[1], v[1], v[2]
    else
        n, r, d = v[1], v[2], v[3]
    end
    local appearance = pdfdictionary { -- cache this one
        N = registeredsymbol(n), R = registeredsymbol(r), D = registeredsymbol(d),
    }
    return lpdf.sharedobj(tostring(appearance))
end

local function fieldstates(specification,forceyes,values,default)
    -- we don't use Opt here (too messy for radio buttons)
    local values, default = values or specification.values, default or specification.default
    if not values then
        -- error
        return
    end
    local v = aux.settings_to_array(values)
    local yes, off
    if #v == 1 then
        yes, off = v[1], v[1]
    else
        yes, off = v[1], v[2]
    end
    local yesshown, yesvalue = lpegmatch(splitter,yes)
    if not (yesshown and yesvalue) then
        yesshown = yes, yes
    end
    yes = aux.settings_to_array(yesshown)
    local offshown, offvalue = lpegmatch(splitter,off)
    if not (offshown and offvalue) then
        offshown = off, off
    end
    off = aux.settings_to_array(offshown)
    if #yes == 1 then
        yesn, yesr, yesd = yes[1], yes[1], yes[1]
    elseif #yes == 2 then
        yesn, yesr, yesd = yes[1], yes[1], yes[2]
    else
        yesn, yesr, yesd = yes[1], yes[2], yes[3]
    end
    if #off == 1 then
        offn, offr, offd = off[1], off[1], off[1]
    elseif #off == 2 then
        offn, offr, offd = off[1], off[1], off[2]
    else
        offn, offr, offd = off[1], off[2], off[3]
    end
    if not yesvalue then
        yesvalue = yesn
    end
    if not offvalue then
        offvalue = offn
    end
    if forceyes == true then
        forceyes = forceyes and "On" -- spec likes Yes more but we've used On for ages now
    else
        -- false or string
    end
    if default == yesn then
        default = pdfconstant(forceyes or yesn)
    else
        default = pdfconstant("Off")
    end
    local appearance = pdfdictionary { -- maybe also cache components
        N = pdfdictionary { [forceyes or yesn] = registeredsymbol(yesn), Off = registeredsymbol(offn) },
        R = pdfdictionary { [forceyes or yesr] = registeredsymbol(yesr), Off = registeredsymbol(offr) },
        D = pdfdictionary { [forceyes or yesd] = registeredsymbol(yesd), Off = registeredsymbol(offd) }
    }
    local appearanceref = lpdf.sharedobj(tostring(appearance))
    return appearanceref, default
end

local function fieldoptions(specification)
    local values = specification.values
    local default = specification.default
    if values then
        local v = aux.settings_to_array(values)
        for i=1,#v do
            local vi = v[i]
            local shown, value = lpegmatch(splitter,vi)
            if shown and value then
                v[i] = pdfarray { pdfunicode(value), shown }
            else
                v[i] = pdfunicode(v[i])
            end
        end
        return pdfarray(v)
    end
end

local function radiodefault(parent,field,forceyes)
    local default, values = parent.default, parent.values
    if not default or default == "" then
        values = aux.settings_to_array(values)
        default = values[1]
    end
    local name = field.name
    local fieldvalues = aux.settings_to_array(field.values)
    local yes, off = fieldvalues[1], fieldvalues[2] or fieldvalues[1]
    if not default then
        return pdfconstant((forceyes and "On") or yes)
    elseif default == name then
        return pdfconstant((forceyes and "On") or default)
    else
        return pdfconstant("Off")
    end
end

-- layers

local function fieldlayer(specification) -- we can move this in line
    local layer = specification.layer
    return (layer and lpdf.layerreferences[layer]) or nil
end

-- defining

local fields, radios, clones, fieldsets, calculationset = { }, { }, { }, { }, nil

function codeinjections.definefieldset(tag,list)
    fieldsets[tag] = list
end

function codeinjections.getfieldset(tag)
    return fieldsets[tag]
end

local function fieldsetlist(tag)
    if tag then
        local ft = fieldsets[tag]
        if ft then
            local a = pdfarray()
            for name in gmatch(list,"[^, ]+") do
                local f = field[name]
                if f and f.pobj then
                    a[#a+1] = pdfreference(f.pobj)
                end
            end
            return a
        end
    end
end

function codeinjections.setfieldcalculationset(tag)
    calculationset = tag
end

local function predefinesymbols(specification)
    local values = specification.values
    if values then
        local symbols = aux.settings_to_array(values)
        for i=1,#symbols do
            local symbol = symbols[i]
            local a, b = lpegmatch(splitter,symbol)
            codeinjections.presetsymbol(a or symbol)
        end
    end
end

function codeinjections.getdefaultfieldvalue(name)
    local f = fields[name]
    if f then
        local values  = f.values
        local default = f.default
        if not default or default == "" then
            local symbols = aux.settings_to_array(values)
            local symbol = symbols[1]
            if symbol then
                local a, b = lpegmatch(splitter,symbol) -- splits at =>
                default = a or symbol
            end
        end
        if default then
            tex.sprint(ctxcatcodes,default)
        end
    end
end

function codeinjections.definefield(specification)
    local n = specification.name
    local f = fields[n]
    if not f then
        local kind = specification.kind
        if not kind then
            if trace_fields then
                logs.report("fields","invalid definition of '%s': unknown type",n)
            end
        elseif kind == "radio" then
            local values = specification.values
            if values and values ~= "" then
                values = aux.settings_to_array(values)
                for v=1,#values do
                    radios[values[v]] = { parent = n }
                end
                fields[n] = specification
                if trace_fields then
                    logs.report("fields","defining '%s' as radio",n or "?")
                end
            elseif trace_fields then
                logs.report("fields","invalid definition of radio '%s': missing values",n)
            end
        elseif kind == "sub" then
            -- not in main field list !
            local radio = radios[n]
            if radio then
                -- merge specification
                for key, value in next, specification do
                    radio[key] = value
                end
                if trace_fields then
                    local p = radios[n] and radios[n].parent
                    logs.report("fields","defining '%s' as sub of radio '%s'",n or "?",p or "?")
                end
            elseif trace_fields then
                logs.report("fields","invalid definition of radio sub '%s': no parent",n)
            end
            predefinesymbols(specification)
        elseif kind == "text" or kind == "line" then
            fields[n] = specification
            if trace_fields then
                logs.report("fields","defining '%s' as %s",n,kind)
            end
            if specification.values ~= "" and specification.default == "" then
                specification.default, specification.values = specification.values, nil
            end
        else
            fields[n] = specification
            if trace_fields then
                logs.report("fields","defining '%s' as %s",n,kind)
            end
            predefinesymbols(specification)
        end
    elseif trace_fields then
        logs.report("fields","invalid definition of '%s': already defined",n)
    end
end

function codeinjections.clonefield(specification)
    local p, c, v = specification.parent, specification.children, specification.variant
    if not p or not c then
        if trace_fields then
            logs.report("fields","invalid clone: children: '%s', parent '%s', variant: '%s'",p or "?",c or "?", v or "?")
        end
    else
        for n in gmatch(c,"[^, ]+") do
            local f, r, c, x = fields[n], radios[n], clones[n], fields[p]
            if f or r or c then
                if trace_fields then
                    logs.report("fields","already cloned: child: '%s', parent '%s', variant: '%s'",p or "?",n or "?", v or "?")
                end
            elseif x then
                if trace_fields then
                    logs.report("fields","invalid clone: child: '%s', variant: '%s', no parent",n or "?", v or "?")
                end
            else
                if trace_fields then
                    logs.report("fields","cloning: child: '%s', parent '%s', variant: '%s'",p or "?",n or "?", v or "?")
                end
                clones[n] = specification
                predefinesymbols(specification)
            end
        end
    end
end

function codeinjections.getfieldgroup(name)
    local f = fields[name] or radios[name] or clones[name]
    local g = f and f.group
    if not g or g == "" then
        local v, p, k = f.variant, f.parent, f.kind
        if v == "clone" or v == "copy" then
            f = fields[p] or radios[p]
            g = f and f.group
        elseif k == "sub" then
            f = fields[p]
            g = f and f.group
        end
    end
    if g then
        texsprint(ctxcatcodes,g)
    end
end

--

function codeinjections.doiffieldset(tag)
    commands.testcase(fieldsets[tag])
end

function codeinjections.doiffieldelse(name)
    commands.testcase(fields[name])
end

--

local alignments = {
    flushleft  = 0, right  = 0,
    center     = 1, middle = 1,
    flushright = 2, left   = 2,
}

local function fieldalignment(specification)
    return alignments[specification.align] or 0
end

local function enhance(specification,option)
    local so = specification.options
    if so and so ~= "" then
        specification.options = so .. "," .. option
    else
        specification.options = option
    end
    return specification
end

-- finish

local collected = pdfarray()

local function finishfields()
    for name, field in next, fields do
        local kids = field.kids
        if kids then
            pdfflushobject(field.kobj,kids)
        end
        local pobj = field.pobj
    end
    for name, field in next, radios do
        local kids = field.kids
        if kids then
            pdfflushobject(field.kobj,kids)
        end
    end
    if #collected > 0 then
        usedfonts.tttf = fontnames.tt.tf
        local acroform = pdfdictionary {
            NeedAppearances = true,
            Fields = pdfreference(pdfflushobject(collected)),
            DR     = pdfdictionary { Font = registerfonts() },
            CO     = fieldsetlist(calculationset),
            DA     = "/tttf 12 Tf 0 g",
        }
        lpdf.addtocatalog("AcroForm",pdfreference(pdfflushobject(acroform)))
    end
end

lpdf.registerdocumentfinalizer(finishfields)

local pdf_widget = pdfconstant("Widget")
local pdf_tx     = pdfconstant("Tx")
local pdf_ch     = pdfconstant("Ch")
local pdf_btn    = pdfconstant("Btn")
local pdf_yes    = pdfconstant("Yes")
local pdf_p      = pdfconstant("P") -- None Invert Outline Push
local pdf_n      = pdfconstant("N") -- None Invert Outline Push
--
local pdf_no_rect = pdfarray { 0, 0, 0, 0 }

local methods = { }

function codeinjections.typesetfield(name,specification)
    local field = fields[name] or radios[name] or clones[name]
    if not field then
        logs.report("fields", "unknown child '%s'",name)
        -- unknown field
        return
    end
    local variant, parent = field.variant, field.parent
    if variant == "copy" or variant == "clone" then -- only in clones
        field = fields[parent] or radios[parent]
    end
    local method = methods[field.kind]
    if method then
        method(name,specification,variant)
    else
        logs.report("fields", "unknown method '%s' for child '%s'",field.kind,name)
    end
end

-- can be optional multipass optimization (share objects)

local function save_parent(field,specification,d)
    local kn = pdfreserveobject()
    d.Kids = pdfreference(kn)
    field.kobj = kn
    field.kids = pdfarray()
    local pn = pdfflushobject(d)
    field.pobj = pn
    collected[#collected+1] = pdfreference(pn)
end

local function save_kid(field,specification,d)
    local kn = pdfreserveobject()
    field.kids[#field.kids+1] = pdfreference(kn)
    node.write(pdfannotation(specification.width,specification.height,0,d(),kn))
end

function methods.line(name,specification,variant,extras)
    local field = fields[name]
    if variant == "copy" or variant == "clone" then
        logs.report("fields","todo: clones of text fields")
    end
    local kind = field.kind
    if not field.pobj then
        if trace_fields then
            logs.report("fields","using parent text '%s'",name)
        end
        if extras then
            enhance(specification,extras)
        end
        local text = pdfunicode(field.default)
        local d = pdfdictionary {
            Subtype  = pdf_widget,
            T        = pdfunicode(specification.title),
            F        = fieldplus(specification),
            Ff       = fieldflag(specification),
            OC       = fieldlayer(specification),
            MK       = fieldsurrounding(specification), -- needed ?
            DA       = fieldsurrounding(specification),
            AA       = fieldactions(specification),
            FT       = pdf_tx,
            Q        = fieldalignment(specification),
            MaxLen   = (specification.length == 0 and 1000) or specification.length,
            DV       = text,
            V        = text,
        }
        save_parent(field,specification,d)
        field.specification = specification
    end
    specification = field.specification or { } -- todo: radio spec
    if trace_fields then
        logs.report("fields","using child text '%s'",name)
    end
    local d = pdfdictionary {
        Subtype = pdf_widget,
        Parent  = pdfreference(field.pobj),
        F       = fieldplus(specification),
        DA      = fieldattributes(specification),
        OC      = fieldlayer(specification),
        MK      = fieldsurrounding(specification),
        DA      = fieldsurrounding(specification),
        AA      = fieldactions(specification),
        Q       = fieldalignment(specification),
    }
    save_kid(field,specification,d)
end

function methods.text(name,specification,variant)
    methods.line(name,specification,variant,"MultiLine")
end

function methods.choice(name,specification,variant,extras)
    local field = fields[name]
    if variant == "copy" or variant == "clone" then
        logs.report("fields","todo: clones of choice fields")
    end
    local kind = field.kind
    local d
    if not field.pobj then
        if trace_fields then
            logs.report("fields","using parent choice '%s'",name)
        end
        if extras then
            enhance(specification,extras)
        end
        local d = pdfdictionary {
            Subtype  = pdf_widget,
            T        = pdfunicode(specification.title),
            F        = fieldplus(specification),
            Ff       = fieldflag(specification),
            OC       = fieldlayer(specification),
            AA       = fieldactions(specification),
            FT       = pdf_ch,
            Opt      = fieldoptions(field),
        }
        save_parent(field,specification,d)
        field.specification = specification
    end
    specification = field.specification or { }
    if trace_fields then
        logs.report("fields","using child choice '%s'",name)
    end
    local d = pdfdictionary {
        Subtype = pdf_widget,
        Parent  = pdfreference(field.pobj),
        F       = fieldplus(specification),
        DA      = fieldattributes(specification),
        OC      = fieldlayer(specification),
        AA      = fieldactions(specification),
    }
    save_kid(field,specification,d)
end

function methods.popup(name,specification,variant)
    methods.choice(name,specification,variant,"PopUp")
end
function methods.combo(name,specification,variant)
    methods.choice(name,specification,variant,"PopUp,Edit")
end

-- Probably no default appearance needed for first kid and no javascripts for the
-- parent ... I will look into it when I have to make a complex document.

function methods.check(name,specification,variant)
    -- no /Opt because (1) it's messy - see pdf spec, (2) it discouples kids and
    -- contrary to radio there is no way to associate then
    local field = fields[name]
    if variant == "copy" or variant == "clone" then
        logs.report("fields","todo: clones of check fields")
    end
    local kind = field.kind
    local appearance, default = fieldstates(field,true)
    if not field.pobj then
        if trace_fields then
            logs.report("fields","using parent check '%s'",name)
        end
        local d = pdfdictionary {
            Subtype  = pdf_widget,
            T        = pdfunicode(specification.title),
            F        = fieldplus(specification),
            Ff       = fieldflag(specification),
            OC       = fieldlayer(specification),
            AA       = fieldactions(specification),
            FT       = pdf_btn,
            DV       = default,
            V        = default,
            AS       = default,
            AP       = appearance,
            H        = pdf_n,
        }
        save_parent(field,specification,d)
        field.specification = specification
    end
    specification = field.specification or { } -- todo: radio spec
    if trace_fields then
        logs.report("fields","using child check '%s'",name)
    end
    local d = pdfdictionary {
        Subtype = pdf_widget,
        Parent  = pdfreference(field.pobj),
        F       = fieldplus(specification),
        DA      = fieldattributes(specification),
        OC      = fieldlayer(specification),
        AA      = fieldactions(specification),
        DV      = default,
        V       = default,
        AS      = default,
        AP      = appearance,
        H       = pdf_n,
    }
    save_kid(field,specification,d)
end

function methods.push(name,specification,variant)
    local field = fields[name]
    if variant == "copy" or variant == "clone" then
        logs.report("fields","todo: clones of push fields")
    end
    local kind = field.kind
    if not field.pobj then
        if trace_fields then
            logs.report("fields","using parent push '%s'",name)
        end
        enhance(specification,"PushButton")
        local d = pdfdictionary {
            Subtype  = pdf_widget,
            T        = pdfunicode(specification.title),
            F        = fieldplus(specification),
            Ff       = fieldflag(specification),
            OC       = fieldlayer(specification),
            AA       = fieldactions(specification),
            FT       = pdf_btn,
            AP       = fieldappearances(field),
            H        = pdf_p,
        }
        save_parent(field,specification,d)
        field.specification = specification
    end
    specification = field.specification or { } -- todo: radio spec
    if trace_fields then
        logs.report("fields","using child push '%s'",name)
    end
    local d = pdfdictionary {
        Subtype = pdf_widget,
        Parent  = pdfreference(field.pobj),
        F       = fieldplus(specification),
        DA      = fieldattributes(specification),
        OC      = fieldlayer(specification),
        AA      = fieldactions(specification),
        AP      = fieldappearances(field),
        H       = pdf_p,
    }
    save_kid(field,specification,d)
end

function methods.sub(name,specification,variant)
    local field = radios[name] or fields[name] or clones[name] -- fields in case of a clone, maybe use dedicated clones
    local values
    if variant == "copy" or variant == "clone" then
        name = field.parent
        values = field.values -- clone only, copy has nil so same as parent
        field = radios[name]
    else
        values = field.values
    end
    local parent = fields[field.parent]
    if not parent then
        return
    end
    local appearance = fieldstates(field,name,values) -- we need to force the 'On' name
    local default = radiodefault(parent,field)
    if not parent.pobj then
        if trace_fields then
            logs.report("fields","using parent '%s' of radio '%s' with values '%s' and default '%s'",parent.name,name,parent.values or "?",parent.default or "?")
        end
        local specification = parent.specification or { }
    --  enhance(specification,"Radio,RadiosInUnison")
        enhance(specification,"RadiosInUnison") -- maybe also PushButton as acrobat does
        local d = pdfdictionary {
            T    = parent.name,
            FT   = pdf_btn,
            Rect = pdf_no_rect,
            F    = fieldplus(specification),
            Ff   = fieldflag(specification),
            H    = pdf_n,
            V    = default,
        }
        save_parent(parent,specification,d)
    end
    if trace_fields then
        logs.report("fields","using child radio '%s' with values '%s'",name,values or "?")
    end
    local d = pdfdictionary {
        Subtype = pdf_widget,
        Parent  = pdfreference(parent.pobj),
        F       = fieldplus(specification),
        DA      = fieldattributes(specification),
        OC      = fieldlayer(specification),
        AA      = fieldactions(specification),
        AS      = default,
        AP      = appearance,
        H       = pdf_n,
    }
    save_kid(parent,specification,d)
end
