lpdf-fld.lmt /size: 52 Kb    last modification: 2024-01-16 09:02
1if not modules then modules = { } end modules ['lpdf-fld'] = {
2    version   = 1.001,
3    comment   = "companion to lpdf-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- TURN OFF: preferences -> forms -> highlight -> etc
10
11-- The problem with widgets is that so far each version of acrobat has some
12-- rendering problem. I tried to keep up with this but it makes no sense to do so as
13-- one cannot rely on the viewer not changing. Especially Btn fields are tricky as
14-- their appearences need to be synchronized in the case of children but e.g.
15-- acrobat 10 does not retain the state and forces a check symbol. If you make a
16-- file in acrobat then it has MK entries that seem to overload the already present
17-- appearance streams (they're probably only meant for printing) as it looks like
18-- the viewer has some fallback on (auto generated) MK behaviour built in. So ...
19-- hard to test. Unfortunately not even the default appearance is generated. This
20-- will probably be solved at some point.
21--
22-- Also, for some reason the viewer does not always show custom appearances when
23-- fields are being rolled over or clicked upon, and circles or checks pop up when
24-- you don't expect them. I fear that this kind of instability eventually will kill
25-- pdf forms. After all, the manual says: "individual annotation handlers may ignore
26-- this entry and provide their own appearances" and one might wonder what
27-- 'individual' means here, but effectively this renders the whole concept of
28-- appearances useless.
29--
30-- Okay, here is one observation. A pdf file contains objects and one might consider
31-- each one to be a static entity when read in. However, acrobat starts rendering
32-- and seems to manipulate (appearance streams) of objects in place (this is visible
33-- when the file is saved again). And, combined with some other caching and hashing,
34-- this might give side effects for shared objects. So, it seems that for some cases
35-- one can best be not too clever and not share but duplicate information. Of course
36-- this defeats the whole purpose of these objects. Of course I can be wrong.
37--
38-- A rarther weird side effect of the viewer is that the highlighting of fields
39-- obscures values, unless you uses one of the BS variants, and this makes custum
40-- appearances rather useless as there is no way to control this apart from changing
41-- the viewer preferences. It could of course be a bug but it would be nice if the
42-- highlighting was at least transparent. I have no clue why the built in shapes
43-- work ok (some xform based appearances are generated) while equally valid other
44-- xforms fail. It looks like acrobat appearances come on top (being refered to in
45-- the MK) while custom ones are behind the highlight rectangle. One can disable the
46-- "Show border hover color for fields" option in the preferences. If you load
47-- java-imp-rhh this side effect gets disabled and you get what you expect (it took
48-- me a while to figure out this hack).
49--
50-- When highlighting is enabled, those default symbols flash up, so it looks like we
51-- have some inteference between this setting and custom appearances.
52--
53-- Anyhow, the NeedAppearances is really needed in order to get a rendering for
54-- printing especially when highlighting (those colorfull foregrounds) is on.
55
56local tostring, tonumber, next = tostring, tonumber, next
57local gmatch, lower, format, formatters = string.gmatch, string.lower, string.format, string.formatters
58local lpegmatch = lpeg.match
59local bpfactor, todimen = number.dimenfactors.bp, string.todimen
60local sortedhash = table.sortedhash
61local trace_fields = false  trackers.register("backends.fields", function(v) trace_fields = v end)
62
63local report_fields = logs.reporter("backend","fields")
64
65local variables               = interfaces.variables
66local context                 = context
67
68local references              = structures.references
69local settings_to_array       = utilities.parsers.settings_to_array
70
71local pdfbackend              = backends.registered.pdf
72local nodeinjections          = pdfbackend.nodeinjections
73local codeinjections          = pdfbackend.codeinjections
74local registrations           = pdfbackend.registrations
75
76local registeredsymbol        = codeinjections.registeredsymbol
77
78local lpdf                    = lpdf
79local pdfstream               = lpdf.stream
80local pdfdictionary           = lpdf.dictionary
81local pdfarray                = lpdf.array
82local pdfreference            = lpdf.reference
83local pdfunicode              = lpdf.unicode
84local pdfstring               = lpdf.string
85local pdfconstant             = lpdf.constant
86local pdfaction               = lpdf.action
87local pdfflushobject          = lpdf.flushobject
88local pdfshareobjectreference = lpdf.shareobjectreference
89local pdfshareobject          = lpdf.shareobject
90local pdfreserveobject        = lpdf.reserveobject
91local pdfpagereference        = lpdf.pagereference
92local pdfmajorversion         = lpdf.majorversion
93local pdfcolor                = lpdf.color
94local pdfcolorvalues          = lpdf.colorvalues
95local pdflayerreference       = lpdf.layerreference
96
97local hpack_node              = nodes.hpack
98
99local submitoutputformat      = 0 --  0=unknown 1=HTML 2=FDF 3=XML   => not yet used, needs to be checked
100
101local pdf_widget              = pdfconstant("Widget")
102local pdf_tx                  = pdfconstant("Tx")
103local pdf_sig                 = pdfconstant("Sig")
104local pdf_ch                  = pdfconstant("Ch")
105local pdf_btn                 = pdfconstant("Btn")
106local pdf_yes                 = pdfconstant("Yes")
107local pdf_off                 = pdfconstant("Off")
108local pdf_p                   = pdfconstant("P") -- None Invert Outline Push
109local pdf_n                   = pdfconstant("N") -- None Invert Outline Push
110--
111local pdf_no_rect             = pdfarray { 0, 0, 0, 0 }
112
113local signature               = nil
114
115local splitter = lpeg.splitat("=>")
116
117local formats = {
118    html = 1, fdf = 2, xml = 3,
119}
120
121function codeinjections.setformsmethod(name)
122    submitoutputformat = formats[lower(name)] or formats.xml
123end
124
125local flag = { -- /Ff
126    ReadOnly          = 0x00000001, -- 2^ 0
127    Required          = 0x00000002, -- 2^ 1
128    NoExport          = 0x00000004, -- 2^ 2
129    MultiLine         = 0x00001000, -- 2^12
130    Password          = 0x00002000, -- 2^13
131    NoToggleToOff     = 0x00004000, -- 2^14
132    Radio             = 0x00008000, -- 2^15
133    PushButton        = 0x00010000, -- 2^16
134    PopUp             = 0x00020000, -- 2^17
135    Edit              = 0x00040000, -- 2^18
136    Sort              = 0x00080000, -- 2^19
137    FileSelect        = 0x00100000, -- 2^20
138    DoNotSpellCheck   = 0x00400000, -- 2^22
139    DoNotScroll       = 0x00800000, -- 2^23
140    Comb              = 0x01000000, -- 2^24
141    RichText          = 0x02000000, -- 2^25
142    RadiosInUnison    = 0x02000000, -- 2^25
143    CommitOnSelChange = 0x04000000, -- 2^26
144}
145
146local plus = { -- /F
147    Invisible         = 0x00000001, -- 2^0
148    Hidden            = 0x00000002, -- 2^1
149    Printable         = 0x00000004, -- 2^2
150    Print             = 0x00000004, -- 2^2
151    NoZoom            = 0x00000008, -- 2^3
152    NoRotate          = 0x00000010, -- 2^4
153    NoView            = 0x00000020, -- 2^5
154    ReadOnly          = 0x00000040, -- 2^6
155    Locked            = 0x00000080, -- 2^7
156    ToggleNoView      = 0x00000100, -- 2^8
157    LockedContents    = 0x00000200, -- 2^9
158    AutoView          = 0x00000100, -- 2^8
159}
160
161-- todo: check what is interfaced
162
163flag.readonly    = flag.ReadOnly
164flag.required    = flag.Required
165flag.protected   = flag.Password
166flag.sorted      = flag.Sort
167flag.unavailable = flag.NoExport
168flag.nocheck     = flag.DoNotSpellCheck
169flag.fixed       = flag.DoNotScroll
170flag.file        = flag.FileSelect
171
172plus.hidden      = plus.Hidden
173plus.printable   = plus.Printable
174plus.auto        = plus.AutoView
175
176lpdf.flags.widgets     = flag
177lpdf.flags.annotations = plus
178
179-- some day .. lpeg with function or table
180
181local function fieldflag(specification) -- /Ff
182    local o, n = specification.option, 0
183    if o and o ~= "" then
184        for f in gmatch(o,"[^, ]+") do
185            n = n + (flag[f] or 0)
186        end
187    end
188    return n
189end
190
191local function fieldplus(specification) -- /F
192    local o, n = specification.option, 0
193    if o and o ~= "" then
194        for p in gmatch(o,"[^, ]+") do
195            n = n + (plus[p] or 0)
196        end
197    end
198-- n = n + 4
199    return n
200end
201
202-- keep:
203--
204-- local function checked(what)
205--     local set, bug = references.identify("",what)
206--     if not bug and #set > 0 then
207--         local r, n = pdfaction(set)
208--         return pdfshareobjectreference(r)
209--     end
210-- end
211--
212-- local function fieldactions(specification) -- share actions
213--     local d, a = { }, nil
214--     a = specification.mousedown
215--      or specification.clickin           if a and a ~= "" then d.D  = checked(a) end
216--     a = specification.mouseup
217--      or specification.clickout          if a and a ~= "" then d.U  = checked(a) end
218--     a = specification.regionin          if a and a ~= "" then d.E  = checked(a) end -- Enter
219--     a = specification.regionout         if a and a ~= "" then d.X  = checked(a) end -- eXit
220--     a = specification.afterkey          if a and a ~= "" then d.K  = checked(a) end
221--     a = specification.format            if a and a ~= "" then d.F  = checked(a) end
222--     a = specification.validate          if a and a ~= "" then d.V  = checked(a) end
223--     a = specification.calculate         if a and a ~= "" then d.C  = checked(a) end
224--     a = specification.focusin           if a and a ~= "" then d.Fo = checked(a) end
225--     a = specification.focusout          if a and a ~= "" then d.Bl = checked(a) end
226--     a = specification.openpage          if a and a ~= "" then d.PO = checked(a) end
227--     a = specification.closepage         if a and a ~= "" then d.PC = checked(a) end
228--  -- a = specification.visiblepage       if a and a ~= "" then d.PV = checked(a) end
229--  -- a = specification.invisiblepage     if a and a ~= "" then d.PI = checked(a) end
230--     return next(d) and pdfdictionary(d)
231-- end
232
233local mapping = {
234    mousedown         = "D",    clickin  = "D",
235    mouseup           = "U",    clickout = "U",
236    regionin          = "E",
237    regionout         = "X",
238    afterkey          = "K",
239    format            = "F",
240    validate          = "V",
241    calculate         = "C",
242    focusin           = "Fo",
243    focusout          = "Bl",
244    openpage          = "PO",
245    closepage         = "PC",
246 -- visiblepage       = "PV",
247 -- invisiblepage     = "PI",
248}
249
250local function fieldactions(specification) -- share actions
251    local d = nil
252    for key, target in sortedhash(mapping) do -- sort so that we can compare pdf
253        local code = specification[key]
254        if code and code ~= "" then
255         -- local a = checked(code)
256            local set, bug = references.identify("",code)
257            if not bug and #set > 0 then
258                local a = pdfaction(set) -- r, n
259                if a then
260                    local r = pdfshareobjectreference(a)
261                    if d then
262                        d[target] = r
263                    else
264                        d = pdfdictionary { [target] = r }
265                    end
266                else
267                    report_fields("invalid field action %a, case %s",code,2)
268                end
269            else
270                report_fields("invalid field action %a, case %s",code,1)
271            end
272        end
273    end
274 -- if d then
275 --     d = pdfshareobjectreference(d) -- not much overlap or maybe only some patterns
276 -- end
277    return d
278end
279
280-- fonts and color
281
282local pdfdocencodingvector, pdfdocencodingcapsule
283
284-- The pdf doc encoding vector is needed in order to trigger propper unicode. Interesting is that when
285-- a glyph is not in the vector, it is still visible as it is taken from some other font. Messy.
286
287-- To be checked: only when text/line fields.
288
289local function checkpdfdocencoding()
290    report_fields("adding pdfdoc encoding vector")
291    local encoding = dofile(resolvers.findfile("lpdf-enc.lmt")) -- no checking, fatal if not present
292    pdfdocencodingvector = pdfreference(pdfflushobject(encoding))
293    local capsule = pdfdictionary {
294        PDFDocEncoding = pdfdocencodingvector
295    }
296    pdfdocencodingcapsule = pdfreference(pdfflushobject(capsule))
297    checkpdfdocencoding = function() end
298end
299
300local fontnames = {
301    rm = {
302        tf = "Times-Roman",
303        bf = "Times-Bold",
304        it = "Times-Italic",
305        sl = "Times-Italic",
306        bi = "Times-BoldItalic",
307        bs = "Times-BoldItalic",
308    },
309    ss = {
310        tf = "Helvetica",
311        bf = "Helvetica-Bold",
312        it = "Helvetica-Oblique",
313        sl = "Helvetica-Oblique",
314        bi = "Helvetica-BoldOblique",
315        bs = "Helvetica-BoldOblique",
316    },
317    tt = {
318        tf = "Courier",
319        bf = "Courier-Bold",
320        it = "Courier-Oblique",
321        sl = "Courier-Oblique",
322        bi = "Courier-BoldOblique",
323        bs = "Courier-BoldOblique",
324    },
325    symbol = {
326        dingbats = "ZapfDingbats",
327    }
328}
329
330local usedfonts = { }
331
332local function fieldsurrounding(specification)
333    local fontsize        = specification.fontsize or "12pt"
334    local fontstyle       = specification.fontstyle or "rm"
335    local fontalternative = specification.fontalternative or "tf"
336    local colorvalue      = tonumber(specification.colorvalue)
337    local s = fontnames[fontstyle]
338    if not s then
339        fontstyle, s = "rm", fontnames.rm
340    end
341    local a = s[fontalternative]
342    if not a then
343        alternative, a = "tf", s.tf
344    end
345    local tag = fontstyle .. fontalternative
346    fontsize  = todimen(fontsize)
347    fontsize  = fontsize and (bpfactor * fontsize) or 12
348    fontraise = 0.1 * fontsize -- todo: figure out what the natural one is and compensate for strutdp
349    local fontcode  = formatters["%0.4F Tf %0.4F Ts"](fontsize,fontraise)
350    -- we could test for colorvalue being 1 (black) and omit it then
351    local colorcode = pdfcolor(3,colorvalue) -- we force an rgb color space
352    if trace_fields then
353        report_fields("using font, style %a, alternative %a, size %p, tag %a, code %a",fontstyle,fontalternative,fontsize,tag,fontcode)
354        report_fields("using color, value %a, code %a",colorvalue,colorcode)
355    end
356    local stream = pdfstream {
357        pdfconstant(tag),
358        formatters["%s %s"](fontcode,colorcode)
359    }
360    usedfonts[tag] = a -- the name
361    -- move up with "x.y Ts"
362    return tostring(stream)
363end
364
365-- Can we use any font?
366
367codeinjections.fieldsurrounding = fieldsurrounding
368
369local function registerfonts()
370    if next(usedfonts) then
371        checkpdfdocencoding() -- already done
372        local pdffontlist    = pdfdictionary()
373        local pdffonttype    = pdfconstant("Font")
374        local pdffontsubtype = pdfconstant("Type1")
375        for tag, name in sortedhash(usedfonts) do
376            local f = pdfdictionary {
377                Type     = pdffonttype,
378                Subtype  = pdffontsubtype,
379                Name     = pdfconstant(tag),
380                BaseFont = pdfconstant(name),
381                Encoding = pdfdocencodingvector,
382            }
383            pdffontlist[tag] = pdfreference(pdfflushobject(f))
384        end
385        return pdffontlist
386    end
387end
388
389-- symbols
390
391local function fieldappearances(specification)
392    -- todo: caching
393    local values  = specification.values
394    local default = specification.default -- todo
395    if not values then
396        -- error
397        return
398    end
399    local v = settings_to_array(values)
400    local n, r, d
401    if #v == 1 then
402        n, r, d = v[1], v[1], v[1]
403    elseif #v == 2 then
404        n, r, d = v[1], v[1], v[2]
405    else
406        n, r, d = v[1], v[2], v[3]
407    end
408    local appearance = pdfdictionary {
409        N = registeredsymbol(n),
410        R = registeredsymbol(r),
411        D = registeredsymbol(d),
412    }
413    return pdfshareobjectreference(appearance)
414--     return pdfreference(pdfflushobject(appearance))
415end
416
417-- The rendering part of form support has always been crappy and didn't really
418-- improve over time. Did bugs become features? Who knows. Why provide for instance
419-- control over appearance and then ignore it when the mouse clicks someplace else.
420-- Strangely enough a lot of effort went into JavaScript support while basic
421-- appearance control of checkboxes stayed poor. I found this link when googling for
422-- conformation after the n^th time looking into this behaviour:
423--
424-- https://stackoverflow.com/questions/15479855/pdf-appearance-streams-checkbox-not-shown-correctly-after-focus-lost
425--
426-- ... "In particular check boxes, therefore, whenever not interacting with the user, shall
427-- be displayed using their normal captions, not their appearances."
428--
429-- So: don't use check boxes. In fact, even radio buttons can have these funny "flash some
430-- funny symbol" side effect when clocking on them. I tried all combinations if /H and /AP
431-- and /AS and ... Because (afaiks) the acrobat interface assumes that one uses dingbats no
432-- one really cared about getting custom appeances done well. This erratic behaviour might
433-- as well be the reason why no open source viewer ever bothered implementing forms. It's
434-- probably also why most forms out there look kind of bad.
435
436local function fieldstates_precheck(specification)
437    local values  = specification.values
438    local default = specification.default
439    if not values or values == "" then
440        return
441    end
442    local yes = settings_to_array(values)[1]
443    local yesshown, yesvalue = lpegmatch(splitter,yes)
444    if not (yesshown and yesvalue) then
445        yesshown = yes
446    end
447    return default == settings_to_array(yesshown)[1] and pdf_yes or pdf_off
448end
449
450local function fieldstates_check(specification)
451    -- we don't use Opt here (too messy for radio buttons)
452    local values  = specification.values
453    local default = specification.default
454    if not values or values == "" then
455        -- error
456        return
457    end
458    local v = settings_to_array(values)
459    local yes, off, yesn, yesr, yesd, offn, offr, offd
460    if #v == 1 then
461        yes, off = v[1], v[1]
462    else
463        yes, off = v[1], v[2]
464    end
465    local yesshown, yesvalue = lpegmatch(splitter,yes)
466    if not (yesshown and yesvalue) then
467        yesshown = yes, yes
468    end
469    yes = settings_to_array(yesshown)
470    local offshown, offvalue = lpegmatch(splitter,off)
471    if not (offshown and offvalue) then
472        offshown = off, off
473    end
474    off = settings_to_array(offshown)
475    if #yes == 1 then
476        yesn, yesr, yesd = yes[1], yes[1], yes[1]
477    elseif #yes == 2 then
478        yesn, yesr, yesd = yes[1], yes[1], yes[2]
479    else
480        yesn, yesr, yesd = yes[1], yes[2], yes[3]
481    end
482    if #off == 1 then
483        offn, offr, offd = off[1], off[1], off[1]
484    elseif #off == 2 then
485        offn, offr, offd = off[1], off[1], off[2]
486    else
487        offn, offr, offd = off[1], off[2], off[3]
488    end
489    if not yesvalue then
490        yesvalue = yesdefault or yesn
491    end
492    if not offvalue then
493        offvalue = offn
494    end
495    if default == yesn then
496        default  = pdf_yes
497        yesvalue = yesvalue == yesn and "Yes" or "Off"
498    else
499        default  = pdf_off
500        yesvalue = "Off"
501    end
502    local appearance
503 -- if false then
504    if true then
505        -- needs testing
506        appearance = pdfdictionary { -- maybe also cache components
507            N = pdfshareobjectreference(pdfdictionary { Yes = registeredsymbol(yesn), Off = registeredsymbol(offn) }),
508            R = pdfshareobjectreference(pdfdictionary { Yes = registeredsymbol(yesr), Off = registeredsymbol(offr) }),
509            D = pdfshareobjectreference(pdfdictionary { Yes = registeredsymbol(yesd), Off = registeredsymbol(offd) }),
510        }
511    else
512        appearance = pdfdictionary { -- maybe also cache components
513            N = pdfdictionary { Yes = registeredsymbol(yesn), Off = registeredsymbol(offn) },
514            R = pdfdictionary { Yes = registeredsymbol(yesr), Off = registeredsymbol(offr) },
515            D = pdfdictionary { Yes = registeredsymbol(yesd), Off = registeredsymbol(offd) }
516        }
517    end
518    local appearanceref = pdfshareobjectreference(appearance)
519 -- local appearanceref = pdfreference(pdfflushobject(appearance))
520    return appearanceref, default, yesvalue
521end
522
523-- It looks like there is always a (MK related) symbol used and that the appearances
524-- are only used as ornaments behind a symbol. So, contrary to what we did when
525-- widgets showed up, we now limit ourself to more dumb definitions. Especially when
526-- highlighting is enabled weird interferences happen. So, we play safe (some nice
527-- code has been removed that worked well till recently).
528
529local function fieldstates_radio(specification,name,parent)
530    local values  = values  or specification.values
531    local default = default or parent.default -- specification.default
532    if not values or values == "" then
533        -- error
534        return
535    end
536    local v = settings_to_array(values)
537    local yes, off, yesn, yesr, yesd, offn, offr, offd
538    if #v == 1 then
539        yes, off = v[1], v[1]
540    else
541        yes, off = v[1], v[2]
542    end
543    -- yes keys might be the same in the three appearances within a field
544    -- but can best be different among fields ... don't ask why
545    local yessymbols, yesvalue = lpegmatch(splitter,yes) -- n,r,d=>x
546    if not (yessymbols and yesvalue) then
547        yessymbols = yes
548    end
549    if not yesvalue then
550        yesvalue = name
551    end
552    yessymbols = settings_to_array(yessymbols)
553    if #yessymbols == 1 then
554        yesn = yessymbols[1]
555        yesr = yesn
556        yesd = yesr
557    elseif #yessymbols == 2 then
558        yesn = yessymbols[1]
559        yesr = yessymbols[2]
560        yesd = yesr
561    else
562        yesn = yessymbols[1]
563        yesr = yessymbols[2]
564        yesd = yessymbols[3]
565    end
566    -- we don't care about names, as all will be /Off
567    local offsymbols = lpegmatch(splitter,off) or off
568    offsymbols = settings_to_array(offsymbols)
569    if #offsymbols == 1 then
570        offn = offsymbols[1]
571        offr = offn
572        offd = offr
573    elseif #offsymbols == 2 then
574        offn = offsymbols[1]
575        offr = offsymbols[2]
576        offd = offr
577    else
578        offn = offsymbols[1]
579        offr = offsymbols[2]
580        offd = offsymbols[3]
581    end
582    if default == name then
583        default = pdfconstant(name)
584    else
585        default = pdf_off
586    end
587    --
588    local appearance
589    if false then -- needs testing
590        appearance = pdfdictionary { -- maybe also cache components
591            N = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesn), Off = registeredsymbol(offn) }),
592            R = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesr), Off = registeredsymbol(offr) }),
593            D = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesd), Off = registeredsymbol(offd) }),
594        }
595    else
596        appearance = pdfdictionary { -- maybe also cache components
597            N = pdfdictionary { [name] = registeredsymbol(yesn), Off = registeredsymbol(offn) },
598            R = pdfdictionary { [name] = registeredsymbol(yesr), Off = registeredsymbol(offr) },
599            D = pdfdictionary { [name] = registeredsymbol(yesd), Off = registeredsymbol(offd) }
600        }
601    end
602    local appearanceref = pdfshareobjectreference(appearance) -- pdfreference(pdfflushobject(appearance))
603    return appearanceref, default, yesvalue
604end
605
606local function fielddefault(field,pdf_yes)
607    local default = field.default
608    if not default or default == "" then
609        local values = settings_to_array(field.values)
610        default = values[1]
611    end
612    if not default or default == "" then
613        return pdf_off
614    else
615        return pdf_yes or pdfconstant(default)
616    end
617end
618
619local function fieldoptions(specification)
620    local values  = specification.values
621    local default = specification.default
622    if values then
623        local v = settings_to_array(values)
624        for i=1,#v do
625            local vi = v[i]
626            local shown, value = lpegmatch(splitter,vi)
627            if shown and value then
628                v[i] = pdfarray { pdfunicode(value), shown }
629            else
630                v[i] = pdfunicode(v[i])
631            end
632        end
633        return pdfarray(v)
634    end
635end
636
637local mapping = {
638    -- acrobat compliant (messy, probably some pdfdoc encoding interference here)
639    check   = "4", -- 0x34
640    circle  = "l", -- 0x6C
641    cross   = "8", -- 0x38
642    diamond = "u", -- 0x75
643    square  = "n", -- 0x6E
644    star    = "H", -- 0x48
645}
646
647local function todingbat(n)
648    if n and n ~= "" then
649        return mapping[n] or ""
650    end
651end
652
653local function fieldrendering(specification)
654    local bvalue = tonumber(specification.backgroundcolorvalue)
655    local fvalue = tonumber(specification.framecolorvalue)
656    local svalue = specification.fontsymbol
657    if bvalue or fvalue or (svalue and svalue ~= "") then
658        return pdfdictionary {
659            BG = bvalue and pdfarray { pdfcolorvalues(3,bvalue) } or nil, -- or zero_bg,
660            BC = fvalue and pdfarray { pdfcolorvalues(3,fvalue) } or nil, -- or zero_bc,
661            CA = svalue and pdfstring (svalue) or nil,
662        }
663    end
664end
665
666-- layers
667
668local function fieldlayer(specification) -- we can move this in line
669    local layer = specification.layer
670    return (layer and pdflayerreference(layer)) or nil
671end
672
673-- defining
674
675local fields, radios, clones, fieldsets, calculationset = { }, { }, { }, { }, nil
676
677local xfdftemplate <const> = [[
678<?xml version='1.0' encoding='UTF-8'?>
679
680<xfdf xmlns='http://ns.adobe.com/xfdf/'>
681  <f href='%s.pdf'/>
682  <fields>
683%s
684  </fields>
685</xfdf>
686]]
687
688function codeinjections.exportformdata(name)
689    local result = { }
690    for k, v in sortedhash(fields) do
691        result[#result+1] = formatters["    <field name='%s'><value>%s</value></field>"](v.name or k,v.default or "")
692    end
693    local base = file.basename(tex.jobname)
694    local xfdf = format(xfdftemplate,base,table.concat(result,"\n"))
695    if not name or name == "" then
696        name = base
697    end
698    io.savedata(file.addsuffix(name,"xfdf"),xfdf)
699end
700
701function codeinjections.definefieldset(tag,list)
702    fieldsets[tag] = list
703end
704
705function codeinjections.getfieldset(tag)
706    return fieldsets[tag]
707end
708
709local function fieldsetlist(tag)
710    if tag then
711        local ft = fieldsets[tag]
712        if ft then
713            local a = pdfarray()
714            for name in gmatch(list,"[^, ]+") do
715                local f = field[name]
716                if f and f.pobj then
717                    a[#a+1] = pdfreference(f.pobj)
718                end
719            end
720            return a
721        end
722    end
723end
724
725function codeinjections.setfieldcalculationset(tag)
726    calculationset = tag
727end
728
729interfaces.implement {
730    name      = "setfieldcalculationset",
731    actions   = codeinjections.setfieldcalculationset,
732    arguments = "string",
733}
734
735local function predefinesymbols(specification)
736    local values = specification.values
737    if values then
738        local symbols = settings_to_array(values)
739        for i=1,#symbols do
740            local symbol = symbols[i]
741            local a, b = lpegmatch(splitter,symbol)
742            codeinjections.presetsymbol(a or symbol)
743        end
744    end
745end
746
747function codeinjections.getdefaultfieldvalue(name)
748    local f = fields[name]
749    if f then
750        local values  = f.values
751        local default = f.default
752        if not default or default == "" then
753            local symbols = settings_to_array(values)
754            local symbol = symbols[1]
755            if symbol then
756                local a, b = lpegmatch(splitter,symbol) -- splits at =>
757                default = a or symbol
758            end
759        end
760        return default
761    end
762end
763
764function codeinjections.definefield(specification)
765    local n = specification.name
766    local f = fields[n]
767    if not f then
768        local fieldtype = specification.type
769        if not fieldtype then
770            if trace_fields then
771                report_fields("invalid definition for %a, unknown type",n)
772            end
773        elseif fieldtype == "radio" then
774            local values = specification.values
775            if values and values ~= "" then
776                values = settings_to_array(values)
777                for v=1,#values do
778                    radios[values[v]] = { parent = n }
779                end
780                fields[n] = specification
781                if trace_fields then
782                    report_fields("defining %a as type %a",n,"radio")
783                end
784            elseif trace_fields then
785                report_fields("invalid definition of radio %a, missing values",n)
786            end
787        elseif fieldtype == "sub" then
788            -- not in main field list !
789            local radio = radios[n]
790            if radio then
791                -- merge specification
792                for key, value in next, specification do
793                    radio[key] = value
794                end
795                if trace_fields then
796                    local p = radios[n] and radios[n].parent
797                    report_fields("defining %a as type sub of radio %a",n,p)
798                end
799            elseif trace_fields then
800                report_fields("invalid definition of radio sub %a, no parent given",n)
801            end
802            predefinesymbols(specification)
803        elseif fieldtype == "text" or fieldtype == "line" then
804            fields[n] = specification
805            if trace_fields then
806                report_fields("defining %a as type %a",n,fieldtype)
807            end
808            if specification.values ~= "" and specification.default == "" then
809                specification.default, specification.values = specification.values, nil
810            end
811        else
812            fields[n] = specification
813            if trace_fields then
814                report_fields("defining %a as type %a",n,fieldtype)
815            end
816            predefinesymbols(specification)
817        end
818    elseif trace_fields then
819        report_fields("invalid definition for %a, already defined",n)
820    end
821end
822
823function codeinjections.clonefield(specification) -- obsolete
824    local p = specification.parent
825    local c = specification.children
826    local v = specification.alternative
827    if not p or not c then
828        if trace_fields then
829            report_fields("invalid clone, children %a, parent %a, alternative %a",c,p,v)
830        end
831        return
832    end
833    local x = fields[p] or radios[p]
834    if not x then
835        if trace_fields then
836            report_fields("invalid clone, unknown parent %a",p)
837        end
838        return
839    end
840    for n in gmatch(c,"[^, ]+") do
841        local f, r, c = fields[n], radios[n], clones[n]
842        if f or r or c then
843            if trace_fields then
844                report_fields("already cloned, child %a, parent %a, alternative %a",n,p,v)
845            end
846        else
847            if trace_fields then
848                report_fields("cloning, child %a, parent %a, alternative %a",n,p,v)
849            end
850            clones[n] = specification
851            predefinesymbols(specification)
852        end
853    end
854end
855
856function codeinjections.getfieldcategory(name)
857    local f = fields[name] or radios[name] or clones[name]
858    if f then
859        local g = f.category
860        if not g or g == "" then
861            local v, p, t = f.alternative, f.parent, f.type
862            if v == "clone" or v == "copy" then
863                f = fields[p] or radios[p]
864                g = f and f.category
865            elseif t == "sub" then
866                f = fields[p]
867                g = f and f.category
868            end
869        end
870        return g
871    end
872end
873
874--
875
876function codeinjections.validfieldcategory(name)
877    return fields[name] or radios[name] or clones[name]
878end
879
880function codeinjections.validfieldset(name)
881    return fieldsets[tag]
882end
883
884function codeinjections.validfield(name)
885    return fields[name]
886end
887
888--
889
890local alignments = {
891    flushleft  = 0, right  = 0,
892    center     = 1, middle = 1,
893    flushright = 2, left   = 2,
894}
895
896local function fieldalignment(specification)
897    return alignments[specification.align] or 0
898end
899
900local function enhance(specification,option)
901    local so = specification.option
902    if so and so ~= "" then
903        specification.option = so .. "," .. option
904    else
905        specification.option = option
906    end
907    return specification
908end
909
910-- finish (if we also collect parents we can inline the kids which is
911-- more efficient ... but hardly anyone used widgets so ...)
912
913local collected     = pdfarray()
914local forceencoding = false
915
916-- todo : check #opt
917
918local function finishfields()
919    local sometext = forceencoding
920    local somefont = next(usedfonts)
921    for name, field in sortedhash(fields) do
922        local kids = field.kids
923        if kids then
924            pdfflushobject(field.kidsnum,kids)
925        end
926        local opt = field.opt
927        if opt then
928            pdfflushobject(field.optnum,opt)
929        end
930        local type = field.type
931        if not sometext and (type == "text" or type == "line") then
932            sometext = true
933        end
934    end
935    for name, field in sortedhash(radios) do
936        local kids = field.kids
937        if kids then
938            pdfflushobject(field.kidsnum,kids)
939        end
940        local opt = field.opt
941        if opt then
942            pdfflushobject(field.optnum,opt)
943        end
944    end
945    if #collected > 0 then
946        local acroform = pdfdictionary {
947            Fields          = pdfreference(pdfflushobject(collected)),
948            CO              = fieldsetlist(calculationset),
949        }
950        if signature then
951            acroform.SigFlags = 3
952        elseif pdfmajorversion() == 1 then
953            acroform.NeedAppearances = true
954        else
955            -- depricated
956        end
957        if sometext or somefont then
958            checkpdfdocencoding()
959            if sometext then
960                usedfonts.tttf = fontnames.tt.tf
961                acroform.DA = "/tttf 12 Tf 0 g"
962            end
963            acroform.DR = pdfdictionary {
964                Font     = registerfonts(),
965                Encoding = pdfdocencodingcapsule,
966            }
967        end
968        -- maybe:
969     -- if sometext then
970     --     checkpdfdocencoding()
971     --     if sometext then
972     --         usedfonts.tttf = fontnames.tt.tf
973     --         acroform.DA = "/tttf 12 Tf 0 g"
974     --     end
975     --     acroform.DR = pdfdictionary {
976     --         Font     = registerfonts(),
977     --         Encoding = pdfdocencodingcapsule,
978     --     }
979     -- elseif somefont then
980     --     acroform.DR = pdfdictionary {
981     --         Font     = registerfonts(),
982     --     }
983     -- end
984        lpdf.addtocatalog("AcroForm",pdfreference(pdfflushobject(acroform)))
985    end
986end
987
988lpdf.registerdocumentfinalizer(finishfields,"form fields")
989
990local methods = { }
991
992function nodeinjections.typesetfield(name,specification)
993    local field = fields[name] or radios[name] or clones[name]
994    if not field then
995        report_fields( "unknown child %a",name)
996        -- unknown field
997        return
998    end
999    local alternative, parent = field.alternative, field.parent
1000    if alternative == "copy" or alternative == "clone" then -- only in clones
1001        field = fields[parent] or radios[parent]
1002    end
1003    local method = methods[field.type]
1004    if method then
1005        return method(name,specification,alternative)
1006    else
1007        report_fields( "unknown method %a for child %a",field.type,name)
1008    end
1009end
1010
1011local function save_parent(field,specification,d)
1012    local kidsnum = pdfreserveobject()
1013    d.Kids        = pdfreference(kidsnum)
1014    field.kidsnum = kidsnum
1015    field.kids    = pdfarray()
1016--     if d.Opt then
1017--         local optnum = pdfreserveobject()
1018--         d.Opt        = pdfreference(optnum)
1019--         field.optnum = optnum
1020--         field.opt    = pdfarray()
1021--     end
1022    local pnum = pdfflushobject(d)
1023    field.pobj = pnum
1024    collected[#collected+1] = pdfreference(pnum)
1025end
1026
1027local function save_kid(field,specification,d,optname)
1028    local kn = pdfreserveobject()
1029    field.kids[#field.kids+1] = pdfreference(kn)
1030--     if optname then
1031--         local opt = field.opt
1032--         if opt then
1033--             opt[#opt+1] = optname
1034--         end
1035--     end
1036    local width  = specification.width  or 0
1037    local height = specification.height or 0
1038    local depth  = specification.depth  or 0
1039    local box = hpack_node(nodeinjections.annotation(width,height,depth,d(),kn))
1040    -- redundant
1041    box.width  = width
1042    box.height = height
1043    box.depth  = depth
1044    return box
1045end
1046
1047local function makelineparent(field,specification)
1048    local text   = pdfunicode(field.default)
1049    local length = tonumber(specification.length or 0) or 0
1050    local d      = pdfdictionary {
1051        Subtype  = pdf_widget,
1052        T        = pdfunicode(specification.title),
1053        F        = fieldplus(specification),
1054        Ff       = fieldflag(specification),
1055        OC       = fieldlayer(specification),
1056        DA       = fieldsurrounding(specification),
1057        AA       = fieldactions(specification),
1058        FT       = pdf_tx,
1059        Q        = fieldalignment(specification),
1060        MaxLen   = length == 0 and 1000 or length,
1061        DV       = text,
1062        V        = text,
1063    }
1064    save_parent(field,specification,d)
1065end
1066
1067local function makelinechild(name,specification)
1068    local field  = clones[name]
1069    local parent = nil
1070    if field then
1071        parent = fields[field.parent]
1072        if not parent.pobj then
1073            if trace_fields then
1074                report_fields("forcing parent text %a",parent.name)
1075            end
1076            makelineparent(parent,specification)
1077        end
1078    else
1079        parent = fields[name]
1080        field  = parent
1081        if not parent.pobj then
1082            if trace_fields then
1083                report_fields("using parent text %a",name)
1084            end
1085            makelineparent(parent,specification)
1086        end
1087    end
1088    if trace_fields then
1089        report_fields("using child text %a",name)
1090    end
1091    -- we could save a little by not setting some key/value when it's the
1092    -- same as parent but it would cost more memory to keep track of it
1093    local d = pdfdictionary {
1094        Subtype = pdf_widget,
1095        Parent  = pdfreference(parent.pobj),
1096        F       = fieldplus(specification),
1097        OC      = fieldlayer(specification),
1098        DA      = fieldsurrounding(specification),
1099        AA      = fieldactions(specification),
1100        MK      = fieldrendering(specification),
1101        Q       = fieldalignment(specification),
1102    }
1103    return save_kid(parent,specification,d)
1104end
1105
1106function methods.line(name,specification)
1107    return makelinechild(name,specification)
1108end
1109
1110function methods.text(name,specification)
1111    return makelinechild(name,enhance(specification,"MultiLine"))
1112end
1113
1114-- copy of line ... probably also needs a /Lock
1115
1116local function makesignatureparent(field,specification)
1117    local text   = pdfunicode(field.default)
1118    local length = tonumber(specification.length or 0) or 0
1119    local value  = lpdf.registersignature(field.values)
1120    local d      = pdfdictionary {
1121        Subtype  = pdf_widget,
1122        T        = pdfunicode(specification.title),
1123        F        = fieldplus(specification),
1124        Ff       = fieldflag(specification),
1125        OC       = fieldlayer(specification),
1126        DA       = fieldsurrounding(specification),
1127        AA       = fieldactions(specification),
1128        FT       = pdf_sig,
1129        Q        = fieldalignment(specification),
1130        MaxLen   = length == 0 and 1000 or length,
1131        DV       = not value and text or nil,
1132        V        = value or text,
1133    }
1134    save_parent(field,specification,d)
1135end
1136
1137local function makesignaturechild(name,specification)
1138    local field  = clones[name]
1139    local parent = nil
1140    if field then
1141        parent = fields[field.parent]
1142        if not parent.pobj then
1143            if trace_fields then
1144                report_fields("forcing parent signature %a",parent.name)
1145            end
1146            makesignatureparent(parent,specification)
1147        end
1148    else
1149        parent = fields[name]
1150        field  = parent
1151        if not parent.pobj then
1152            if trace_fields then
1153                report_fields("using parent text %a",name)
1154            end
1155            makesignatureparent(parent,specification)
1156        end
1157    end
1158    if trace_fields then
1159        report_fields("using child text %a",name)
1160    end
1161    -- we could save a little by not setting some key/value when it's the
1162    -- same as parent but it would cost more memory to keep track of it
1163    local d = pdfdictionary {
1164        Subtype = pdf_widget,
1165        Parent  = pdfreference(parent.pobj),
1166        F       = fieldplus(specification),
1167        OC      = fieldlayer(specification),
1168        DA      = fieldsurrounding(specification),
1169        AA      = fieldactions(specification),
1170        MK      = fieldrendering(specification),
1171        Q       = fieldalignment(specification),
1172-- AP = pdfdictionary {
1173--     N = pdfreference(lpdf.flushstreamobject("q 0 0 100 100 re f Q"))
1174-- }
1175    }
1176    return save_kid(parent,specification,d)
1177end
1178
1179function methods.signature(name,specification)
1180    return makesignaturechild(name,specification)
1181end
1182
1183--
1184
1185function methods.signed(name,specification)
1186    local field = fields[name]
1187    if field then
1188        specification.option = "hidden" -- for now
1189        local value = lpdf.registersignature(field.values)
1190        if not value or value == "" then
1191            value = "pkcs7"
1192        end
1193        local d = pdfdictionary {
1194            Subtype  = pdf_widget,
1195            T        = pdfunicode(specification.title),
1196            F        = fieldplus(specification),
1197            Ff       = fieldflag(specification),
1198            OC       = fieldlayer(specification),
1199         -- DA       = fieldsurrounding(specification),
1200         -- AA       = fieldactions(specification),
1201            FT       = pdf_sig,
1202         -- Q        = fieldalignment(specification),
1203            V        = lpdf.registersignature(value),
1204        }
1205        --
1206        local kn = pdfreserveobject()
1207        local width  = specification.width  or 0
1208        local height = specification.height or 0
1209        local depth  = specification.depth  or 0
1210        local box = hpack_node(nodeinjections.annotation(width,height,depth,d(),kn))
1211        -- redundant
1212        box.width  = width
1213        box.height = height
1214        box.depth  = depth
1215        collected[#collected+1] = pdfreference(kn)
1216        return box
1217    end
1218end
1219
1220--
1221
1222local function makechoiceparent(field,specification)
1223    local d = pdfdictionary {
1224        Subtype  = pdf_widget,
1225        T        = pdfunicode(specification.title),
1226        F        = fieldplus(specification),
1227        Ff       = fieldflag(specification),
1228        OC       = fieldlayer(specification),
1229        AA       = fieldactions(specification),
1230        FT       = pdf_ch,
1231        Opt      = fieldoptions(field), -- todo
1232    }
1233    save_parent(field,specification,d)
1234end
1235
1236local function makechoicechild(name,specification)
1237    local field  = clones[name]
1238    local parent = nil
1239    if field then
1240        parent = fields[field.parent]
1241        if not parent.pobj then
1242            if trace_fields then
1243                report_fields("forcing parent choice %a",parent.name)
1244            end
1245            makechoiceparent(parent,specification,extras)
1246        end
1247    else
1248        parent = fields[name]
1249        field  = parent
1250        if not parent.pobj then
1251            if trace_fields then
1252                report_fields("using parent choice %a",name)
1253            end
1254            makechoiceparent(parent,specification,extras)
1255        end
1256    end
1257    if trace_fields then
1258        report_fields("using child choice %a",name)
1259    end
1260    local d = pdfdictionary {
1261        Subtype = pdf_widget,
1262        Parent  = pdfreference(parent.pobj),
1263        F       = fieldplus(specification),
1264        OC      = fieldlayer(specification),
1265        AA      = fieldactions(specification),
1266    }
1267    return save_kid(parent,specification,d) -- do opt here
1268end
1269
1270function methods.choice(name,specification)
1271    return makechoicechild(name,specification)
1272end
1273
1274function methods.popup(name,specification)
1275    return makechoicechild(name,enhance(specification,"PopUp"))
1276end
1277
1278function methods.combo(name,specification)
1279    return makechoicechild(name,enhance(specification,"PopUp,Edit"))
1280end
1281
1282local function makecheckparent(field,specification)
1283    local default = fieldstates_precheck(field)
1284    local d = pdfdictionary {
1285        T  = pdfunicode(specification.title), -- todo: when tracing use a string
1286        F  = fieldplus(specification),
1287        Ff = fieldflag(specification),
1288        OC = fieldlayer(specification),
1289        AA = fieldactions(specification), -- can be shared
1290        FT = pdf_btn,
1291        V  = fielddefault(field,default),
1292    }
1293    save_parent(field,specification,d)
1294end
1295
1296local function makecheckchild(name,specification)
1297    local field  = clones[name]
1298    local parent = nil
1299    if field then
1300        parent = fields[field.parent]
1301        if not parent.pobj then
1302            if trace_fields then
1303                report_fields("forcing parent check %a",parent.name)
1304            end
1305            makecheckparent(parent,specification,extras)
1306        end
1307    else
1308        parent = fields[name]
1309        field  = parent
1310        if not parent.pobj then
1311            if trace_fields then
1312                report_fields("using parent check %a",name)
1313            end
1314            makecheckparent(parent,specification,extras)
1315        end
1316    end
1317    if trace_fields then
1318        report_fields("using child check %a",name)
1319    end
1320    local d = pdfdictionary {
1321        Subtype = pdf_widget,
1322        Parent  = pdfreference(parent.pobj),
1323        F       = fieldplus(specification),
1324        OC      = fieldlayer(specification),
1325        AA      = fieldactions(specification), -- can be shared
1326        H       = pdf_n,
1327    }
1328    local fontsymbol = specification.fontsymbol
1329    if fontsymbol and fontsymbol ~= "" then
1330        specification.fontsymbol = todingbat(fontsymbol)
1331        specification.fontstyle = "symbol"
1332        specification.fontalternative = "dingbats"
1333        d.DA = fieldsurrounding(specification)
1334        d.MK = fieldrendering(specification)
1335        return save_kid(parent,specification,d)
1336    else
1337        local appearance, default, value = fieldstates_check(field)
1338        d.AS = default
1339        d.AP = appearance
1340        return save_kid(parent,specification,d)
1341    end
1342end
1343
1344function methods.check(name,specification)
1345    return makecheckchild(name,specification)
1346end
1347
1348local function makepushparent(field,specification) -- check if we can share with the previous
1349    local d = pdfdictionary {
1350        Subtype = pdf_widget,
1351        T       = pdfunicode(specification.title),
1352        F       = fieldplus(specification),
1353        Ff      = fieldflag(specification),
1354        OC      = fieldlayer(specification),
1355        AA      = fieldactions(specification), -- can be shared
1356        FT      = pdf_btn,
1357        AP      = fieldappearances(field),
1358        H       = pdf_p,
1359    }
1360    save_parent(field,specification,d)
1361end
1362
1363local function makepushchild(name,specification)
1364    local field, parent = clones[name], nil
1365    if field then
1366        parent = fields[field.parent]
1367        if not parent.pobj then
1368            if trace_fields then
1369                report_fields("forcing parent push %a",parent.name)
1370            end
1371            makepushparent(parent,specification)
1372        end
1373    else
1374        parent = fields[name]
1375        field = parent
1376        if not parent.pobj then
1377            if trace_fields then
1378                report_fields("using parent push %a",name)
1379            end
1380            makepushparent(parent,specification)
1381        end
1382    end
1383    if trace_fields then
1384        report_fields("using child push %a",name)
1385    end
1386    local fontsymbol = specification.fontsymbol
1387    local d = pdfdictionary {
1388        Subtype = pdf_widget,
1389        Parent  = pdfreference(field.pobj),
1390        F       = fieldplus(specification),
1391        OC      = fieldlayer(specification),
1392        AA      = fieldactions(specification), -- can be shared
1393        H       = pdf_p,
1394    }
1395    if fontsymbol and fontsymbol ~= "" then
1396        specification.fontsymbol = todingbat(fontsymbol)
1397        specification.fontstyle = "symbol"
1398        specification.fontalternative = "dingbats"
1399        d.DA = fieldsurrounding(specification)
1400        d.MK = fieldrendering(specification)
1401    else
1402        d.AP = fieldappearances(field)
1403    end
1404    return save_kid(parent,specification,d)
1405end
1406
1407function methods.push(name,specification)
1408    return makepushchild(name,enhance(specification,"PushButton"))
1409end
1410
1411local function makeradioparent(field,specification)
1412    specification = enhance(specification,"Radio,RadiosInUnison,Print,NoToggleToOff")
1413    local d = pdfdictionary {
1414        T  = field.name,
1415        FT = pdf_btn,
1416     -- F  = fieldplus(specification),
1417        Ff = fieldflag(specification),
1418     -- H  = pdf_n,
1419        V  = fielddefault(field),
1420    }
1421    save_parent(field,specification,d)
1422end
1423
1424-- local function makeradiochild(name,specification)
1425--     local field  = clones[name]
1426--     local parent = nil
1427--     local pname  = nil
1428--     if field then
1429--         pname  = field.parent
1430--         field  = radios[pname]
1431--         parent = fields[pname]
1432--         if not parent.pobj then
1433--             if trace_fields then
1434--                 report_fields("forcing parent radio %a",parent.name)
1435--             end
1436--             makeradioparent(parent,parent)
1437--         end
1438--     else
1439--         field = radios[name]
1440--         if not field then
1441--             report_fields("there is some problem with field %a",name)
1442--             return nil
1443--         end
1444--         pname  = field.parent
1445--         parent = fields[pname]
1446--         if not parent.pobj then
1447--             if trace_fields then
1448--                 report_fields("using parent radio %a",name)
1449--             end
1450--             makeradioparent(parent,parent)
1451--         end
1452--     end
1453--     if trace_fields then
1454--         report_fields("using child radio %a with values %a and default %a",name,field.values,field.default)
1455--     end
1456--     local fontsymbol = specification.fontsymbol
1457--  -- fontsymbol = "circle"
1458--     local d = pdfdictionary {
1459--         Subtype = pdf_widget,
1460--         Parent  = pdfreference(parent.pobj),
1461--         F       = fieldplus(specification),
1462--         OC      = fieldlayer(specification),
1463--         AA      = fieldactions(specification),
1464--         H       = pdf_n,
1465-- -- H       = pdf_p,
1466-- -- P       = pdfpagereference(true),
1467--     }
1468--     if fontsymbol and fontsymbol ~= "" then
1469--         specification.fontsymbol = todingbat(fontsymbol)
1470--         specification.fontstyle = "symbol"
1471--         specification.fontalternative = "dingbats"
1472--         d.DA = fieldsurrounding(specification)
1473--         d.MK = fieldrendering(specification)
1474--         return save_kid(parent,specification,d) -- todo: what if no value
1475--     else
1476--         local appearance, default, value = fieldstates_radio(field,name,fields[pname])
1477--         d.AP = appearance
1478--         d.AS = default -- /Whatever
1479--         return save_kid(parent,specification,d,value)
1480--     end
1481-- end
1482
1483local function makeradiochild(name,specification)
1484    local field, parent = clones[name], nil
1485    if field then
1486        field  = radios[field.parent]
1487        parent = fields[field.parent]
1488        if not parent.pobj then
1489            if trace_fields then
1490                report_fields("forcing parent radio %a",parent.name)
1491            end
1492            makeradioparent(parent,parent)
1493        end
1494    else
1495        field = radios[name]
1496        if not field then
1497            report_fields("there is some problem with field %a",name)
1498            return nil
1499        end
1500        parent = fields[field.parent]
1501        if not parent.pobj then
1502            if trace_fields then
1503                report_fields("using parent radio %a",name)
1504            end
1505            makeradioparent(parent,parent)
1506        end
1507    end
1508    if trace_fields then
1509        report_fields("using child radio %a with values %a and default %a",name,field.values,field.default)
1510    end
1511    local fontsymbol = specification.fontsymbol
1512 -- fontsymbol = "circle"
1513    local d = pdfdictionary {
1514        Subtype = pdf_widget,
1515        Parent  = pdfreference(parent.pobj),
1516        F       = fieldplus(specification),
1517        OC      = fieldlayer(specification),
1518        AA      = fieldactions(specification),
1519        H       = pdf_n,
1520    }
1521    if fontsymbol and fontsymbol ~= "" then
1522        specification.fontsymbol = todingbat(fontsymbol)
1523        specification.fontstyle = "symbol"
1524        specification.fontalternative = "dingbats"
1525        d.DA = fieldsurrounding(specification)
1526        d.MK = fieldrendering(specification)
1527    end
1528    local appearance, default, value = fieldstates_radio(field,name,fields[field.parent])
1529    d.AP = appearance
1530    d.AS = default -- /Whatever
1531-- d.MK = pdfdictionary { BC = pdfarray {0}, BG = pdfarray { 1 } }
1532d.BS = pdfdictionary { S = pdfconstant("I"), W = 1 }
1533    return save_kid(parent,specification,d,value)
1534end
1535
1536function methods.sub(name,specification)
1537    return makeradiochild(name,enhance(specification,"Radio,RadiosInUnison"))
1538end
1539