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" } -- The problem with widgets is that so far each version of acrobat -- has some rendering problem. I tried to keep up with this but -- it makes no sense to do so as one cannot rely on the viewer -- not changing. Especially Btn fields are tricky as their appearences -- need to be synchronized in the case of children but e.g. acrobat -- 10 does not retain the state and forces a check symbol. If you -- make a file in acrobat then it has MK entries that seem to overload -- the already present appearance streams (they're probably only meant for -- printing) as it looks like the viewer has some fallback on (auto -- generated) MK behaviour built in. So ... hard to test. Unfortunately -- not even the default appearance is generated. This will probably be -- solved at some point. -- -- Also, for some reason the viewer does not always show custom appearances -- when fields are being rolled over or clicked upon, and circles or checks -- pop up when you don't expect them. I fear that this kind of instability -- eventually will kill pdf forms. After all, the manual says: "individual -- annotation handlers may ignore this entry and provide their own appearances" -- and one might wonder what 'individual' means here, but effectively this -- renders the whole concept of appearances useless. -- -- Okay, here is one observation. A pdf file contains objects and one might -- consider each one to be a static entity when read in. However, acrobat -- starts rendering and seems to manipulate (appearance streams) of objects -- in place (this is visible when the file is saved again). And, combined -- with some other caching and hashing, this might give side effects for -- shared objects. So, it seems that for some cases one can best be not too -- clever and not share but duplicate information. Of course this defeats the -- whole purpose of these objects. Of course I can be wrong. -- -- A rarther weird side effect of the viewer is that the highlighting of fields -- obscures values, unless you uses one of the BS variants, and this makes -- custum appearances rather useless as there is no way to control this apart -- from changing the viewer preferences. It could of course be a bug but it would -- be nice if the highlighting was at least transparent. I have no clue why the -- built in shapes work ok (some xform based appearances are generated) while -- equally valid other xforms fail. It looks like acrobat appearances come on -- top (being refered to in the MK) while custom ones are behind the highlight -- rectangle. One can disable the "Show border hover color for fields" option -- in the preferences. If you load java-imp-rhh this side effect gets disabled -- and you get what you expect (it took me a while to figure out this hack). -- -- When highlighting is enabled, those default symbols flash up, so it looks -- like we have some inteference between this setting and custom appearances. -- -- Anyhow, the NeedAppearances is really needed in order to get a rendering -- for printing especially when highlighting (those colorfull foregrounds) is -- on. local tostring, next = tostring, next local gmatch, lower, format, formatters = string.gmatch, string.lower, string.format, string.formatters local lpegmatch = lpeg.match local bpfactor, todimen = number.dimenfactors.bp, string.todimen local sortedhash = table.sortedhash local trace_fields = false trackers.register("backends.fields", function(v) trace_fields = v end) local report_fields = logs.reporter("backend","fields") local backends, lpdf = backends, lpdf local variables = interfaces.variables local context = context local references = structures.references local settings_to_array = utilities.parsers.settings_to_array local pdfbackend = backends.pdf local nodeinjections = pdfbackend.nodeinjections local codeinjections = pdfbackend.codeinjections local registrations = pdfbackend.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 pdfflushobject = lpdf.flushobject local pdfshareobjectreference = lpdf.shareobjectreference local pdfshareobject = lpdf.shareobject local pdfreserveobject = lpdf.reserveobject local pdfaction = lpdf.action local pdfcolor = lpdf.color local pdfcolorvalues = lpdf.colorvalues local pdflayerreference = lpdf.layerreference local hpack_node = node.hpack local submitoutputformat = 0 -- 0=unknown 1=HTML 2=FDF 3=XML => not yet used, needs to be checked local pdf_widget = pdfconstant("Widget") local pdf_tx = pdfconstant("Tx") local pdf_sig = pdfconstant("Sig") local pdf_ch = pdfconstant("Ch") local pdf_btn = pdfconstant("Btn") ----- pdf_yes = pdfconstant("Yes") local pdf_off = pdfconstant("Off") 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 splitter = lpeg.splitat("=>") local formats = { html = 1, fdf = 2, xml = 3, } function codeinjections.setformsmethod(name) submitoutputformat = formats[lower(name)] or formats.xml end local flag = { -- /Ff ReadOnly = 2^ 0, -- 1 Required = 2^ 1, -- 2 NoExport = 2^ 2, -- 3 MultiLine = 2^12, -- 13 Password = 2^13, -- 14 NoToggleToOff = 2^14, -- 15 Radio = 2^15, -- 16 PushButton = 2^16, -- 17 PopUp = 2^17, -- 18 Edit = 2^18, -- 19 Sort = 2^19, -- 20 FileSelect = 2^20, -- 21 DoNotSpellCheck = 2^22, -- 23 DoNotScroll = 2^23, -- 24 Comb = 2^24, -- 25 RichText = 2^25, -- 26 RadiosInUnison = 2^25, -- 26 CommitOnSelChange = 2^26, -- 27 } local plus = { -- /F Invisible = 2^0, -- 1 Hidden = 2^1, -- 2 Printable = 2^2, -- 3 Print = 2^2, -- 3 NoZoom = 2^3, -- 4 NoRotate = 2^4, -- 5 NoView = 2^5, -- 6 ReadOnly = 2^6, -- 7 Locked = 2^7, -- 8 ToggleNoView = 2^8, -- 9 LockedContents = 2^9, -- 10, AutoView = 2^8, -- 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) -- /Ff local o, n = specification.option, 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) -- /F local o, n = specification.option, 0 if o and o ~= "" then for p in gmatch(o,"[^, ]+") do n = n + (plus[p] or 0) end end -- n = n + 4 return n end -- keep: -- -- local function checked(what) -- local set, bug = references.identify("",what) -- if not bug and #set > 0 then -- local r, n = pdfaction(set) -- return pdfshareobjectreference(r) -- end -- end -- -- local function fieldactions(specification) -- share actions -- local d, a = { }, nil -- a = specification.mousedown -- or specification.clickin if a and a ~= "" then d.D = checked(a) end -- a = specification.mouseup -- or specification.clickout 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.afterkey if a and a ~= "" then d.K = checked(a) end -- a = specification.format if a and a ~= "" then d.F = checked(a) end -- a = specification.validate if a and a ~= "" then d.V = checked(a) end -- a = specification.calculate 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 local mapping = { mousedown = "D", clickin = "D", mouseup = "U", clickout = "U", regionin = "E", regionout = "X", afterkey = "K", format = "F", validate = "V", calculate = "C", focusin = "Fo", focusout = "Bl", openpage = "PO", closepage = "PC", -- visiblepage = "PV", -- invisiblepage = "PI", } local function fieldactions(specification) -- share actions local d = nil for key, target in sortedhash(mapping) do -- sort so that we can compare pdf local code = specification[key] if code and code ~= "" then -- local a = checked(code) local set, bug = references.identify("",code) if not bug and #set > 0 then local a = pdfaction(set) -- r, n if a then local r = pdfshareobjectreference(a) if d then d[target] = r else d = pdfdictionary { [target] = r } end else report_fields("invalid field action %a, case %s",code,2) end else report_fields("invalid field action %a, case %s",code,1) end end end -- if d then -- d = pdfshareobjectreference(d) -- not much overlap or maybe only some patterns -- end return d end -- fonts and color local pdfdocencodingvector, pdfdocencodingcapsule -- The pdf doc encoding vector is needed in order to trigger propper unicode. Interesting is that when -- a glyph is not in the vector, it is still visible as it is taken from some other font. Messy. -- To be checked: only when text/line fields. local function checkpdfdocencoding() report_fields("adding pdfdoc encoding vector") local encoding = dofile(resolvers.findfile("lpdf-enc.lua")) -- no checking, fatal if not present pdfdocencodingvector = pdfreference(pdfflushobject(encoding)) local capsule = pdfdictionary { PDFDocEncoding = pdfdocencodingvector } pdfdocencodingcapsule = pdfreference(pdfflushobject(capsule)) checkpdfdocencoding = function() end end 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", }, symbol = { dingbats = "ZapfDingbats", } } local usedfonts = { } local function fieldsurrounding(specification) local fontsize = specification.fontsize or "12pt" local fontstyle = specification.fontstyle or "rm" local fontalternative = specification.fontalternative or "tf" local colorvalue = tonumber(specification.colorvalue) local s = fontnames[fontstyle] if not s then fontstyle, s = "rm", fontnames.rm end local a = s[fontalternative] if not a then alternative, a = "tf", s.tf end local tag = fontstyle .. fontalternative fontsize = todimen(fontsize) fontsize = fontsize and (bpfactor * fontsize) or 12 fontraise = 0.1 * fontsize -- todo: figure out what the natural one is and compensate for strutdp local fontcode = formatters["%0.4f Tf %0.4f Ts"](fontsize,fontraise) -- we could test for colorvalue being 1 (black) and omit it then local colorcode = pdfcolor(3,colorvalue) -- we force an rgb color space if trace_fields then report_fields("using font, style %a, alternative %a, size %p, tag %a, code %a",fontstyle,fontalternative,fontsize,tag,fontcode) report_fields("using color, value %a, code %a",colorvalue,colorcode) end local stream = pdfstream { pdfconstant(tag), formatters["%s %s"](fontcode,colorcode) } usedfonts[tag] = a -- the name -- move up with "x.y Ts" return tostring(stream) end local function registerfonts() if next(usedfonts) then checkpdfdocencoding() -- already done local d = pdfdictionary() local pdffonttype, pdffontsubtype = pdfconstant("Font"), pdfconstant("Type1") -- for tag, name in next, usedfonts do for tag, name in sortedhash(usedfonts) do local f = pdfdictionary { Type = pdffonttype, Subtype = pdffontsubtype, Name = pdfconstant(tag), BaseFont = pdfconstant(name), Encoding = pdfdocencodingvector, } d[tag] = pdfreference(pdfflushobject(f)) end return d end 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 = 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 { N = registeredsymbol(n), R = registeredsymbol(r), D = registeredsymbol(d), } return pdfshareobjectreference(appearance) -- return pdfreference(pdfflushobject(appearance)) end local YesorOn = "Yes" -- somehow On is not always working out well any longer (why o why this change) -- beware ... maybe we should have unique /Yes1 ... we will probably -- change this one too. -- -- TODO: the same as radio .. play safe and use different names. local function fieldstates_check(specification,forceyes,values,default,yesdefault) -- 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 or values == "" then -- error return end local v = settings_to_array(values) local yes, off, yesn, yesr, yesd, offn, offr, offd 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 = settings_to_array(yesshown) local offshown, offvalue = lpegmatch(splitter,off) if not (offshown and offvalue) then offshown = off, off end off = 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 = yesdefault or yesn end if not offvalue then offvalue = offn end if forceyes == true then forceyes = YesorOn -- 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 = pdf_off end local appearance if false then -- needs testing appearance = pdfdictionary { -- maybe also cache components N = pdfshareobjectreference(pdfdictionary { [forceyes or yesn] = registeredsymbol(yesn), Off = registeredsymbol(offn) }), R = pdfshareobjectreference(pdfdictionary { [forceyes or yesr] = registeredsymbol(yesr), Off = registeredsymbol(offr) }), D = pdfshareobjectreference(pdfdictionary { [forceyes or yesd] = registeredsymbol(yesd), Off = registeredsymbol(offd) }), } else 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) } } end local appearanceref = pdfshareobjectreference(appearance) -- local appearanceref = pdfreference(pdfflushobject(appearance)) return appearanceref, default, yesvalue end -- It looks like there is always a (MK related) symbol used and that -- the appearances are only used as ornaments behind a symbol. So, -- contrary to what we did when widgets showed up, we now limit -- ourself to more dumb definitions. Especially when highlighting is -- enabled weird interferences happen. So, we play safe (some nice code -- has been removed that worked well till recently). local function fieldstates_radio(specification,name,parent) local values = values or specification.values local default = default or parent.default -- specification.default if not values or values == "" then -- error return end local v = settings_to_array(values) local yes, off, yesn, yesr, yesd, offn, offr, offd if #v == 1 then yes, off = v[1], v[1] else yes, off = v[1], v[2] end -- yes keys might be the same in the three appearances within a field -- but can best be different among fields ... don't ask why local yessymbols, yesvalue = lpegmatch(splitter,yes) -- n,r,d=>x if not (yessymbols and yesvalue) then yessymbols = yes end if not yesvalue then yesvalue = name end yessymbols = settings_to_array(yessymbols) if #yessymbols == 1 then yesn = yessymbols[1] yesr = yesn yesd = yesr elseif #yessymbols == 2 then yesn = yessymbols[1] yesr = yessymbols[2] yesd = yesr else yesn = yessymbols[1] yesr = yessymbols[2] yesd = yessymbols[3] end -- we don't care about names, as all will be /Off local offsymbols = lpegmatch(splitter,off) or off offsymbols = settings_to_array(offsymbols) if #offsymbols == 1 then offn = offsymbols[1] offr = offn offd = offr elseif #offsymbols == 2 then offn = offsymbols[1] offr = offsymbols[2] offd = offr else offn = offsymbols[1] offr = offsymbols[2] offd = offsymbols[3] end if default == name then default = pdfconstant(name) else default = pdf_off end -- local appearance if false then -- needs testing appearance = pdfdictionary { -- maybe also cache components N = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesn), Off = registeredsymbol(offn) }), R = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesr), Off = registeredsymbol(offr) }), D = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesd), Off = registeredsymbol(offd) }), } else appearance = pdfdictionary { -- maybe also cache components N = pdfdictionary { [name] = registeredsymbol(yesn), Off = registeredsymbol(offn) }, R = pdfdictionary { [name] = registeredsymbol(yesr), Off = registeredsymbol(offr) }, D = pdfdictionary { [name] = registeredsymbol(yesd), Off = registeredsymbol(offd) } } end local appearanceref = pdfshareobjectreference(appearance) -- pdfreference(pdfflushobject(appearance)) return appearanceref, default, yesvalue end local function fielddefault(field) local default = field.default if not default or default == "" then local values = settings_to_array(field.values) default = values[1] end if not default or default == "" then return pdf_off else return pdfconstant(default) end end local function fieldoptions(specification) local values = specification.values local default = specification.default if values then local v = 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 mapping = { -- acrobat compliant (messy, probably some pdfdoc encoding interference here) check = "4", -- 0x34 circle = "l", -- 0x6C cross = "8", -- 0x38 diamond = "u", -- 0x75 square = "n", -- 0x6E star = "H", -- 0x48 } local function todingbat(n) if n and n ~= "" then return mapping[n] or "" end end local function fieldrendering(specification) local bvalue = tonumber(specification.backgroundcolorvalue) local fvalue = tonumber(specification.framecolorvalue) local svalue = specification.fontsymbol if bvalue or fvalue or (svalue and svalue ~= "") then return pdfdictionary { BG = bvalue and pdfarray { pdfcolorvalues(3,bvalue) } or nil, -- or zero_bg, BC = fvalue and pdfarray { pdfcolorvalues(3,fvalue) } or nil, -- or zero_bc, CA = svalue and pdfstring (svalue) or nil, } end end -- layers local function fieldlayer(specification) -- we can move this in line local layer = specification.layer return (layer and pdflayerreference(layer)) or nil end -- defining local fields, radios, clones, fieldsets, calculationset = { }, { }, { }, { }, nil local xfdftemplate = [[ %s ]] function codeinjections.exportformdata(name) local result = { } for k, v in sortedhash(fields) do result[#result+1] = formatters[" %s"](v.name or k,v.default or "") end local base = file.basename(tex.jobname) local xfdf = format(xfdftemplate,base,table.concat(result,"\n")) if not name or name == "" then name = base end io.savedata(file.addsuffix(name,"xfdf"),xfdf) end 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 interfaces.implement { name = "setfieldcalculationset", actions = codeinjections.setfieldcalculationset, arguments = "string", } local function predefinesymbols(specification) local values = specification.values if values then local symbols = 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 = 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 return default end end function codeinjections.definefield(specification) local n = specification.name local f = fields[n] if not f then local fieldtype = specification.type if not fieldtype then if trace_fields then report_fields("invalid definition for %a, unknown type",n) end elseif fieldtype == "radio" then local values = specification.values if values and values ~= "" then values = settings_to_array(values) for v=1,#values do radios[values[v]] = { parent = n } end fields[n] = specification if trace_fields then report_fields("defining %a as type %a",n,"radio") end elseif trace_fields then report_fields("invalid definition of radio %a, missing values",n) end elseif fieldtype == "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 report_fields("defining %a as type sub of radio %a",n,p) end elseif trace_fields then report_fields("invalid definition of radio sub %a, no parent given",n) end predefinesymbols(specification) elseif fieldtype == "text" or fieldtype == "line" then fields[n] = specification if trace_fields then report_fields("defining %a as type %a",n,fieldtype) end if specification.values ~= "" and specification.default == "" then specification.default, specification.values = specification.values, nil end else fields[n] = specification if trace_fields then report_fields("defining %a as type %a",n,fieldtype) end predefinesymbols(specification) end elseif trace_fields then report_fields("invalid definition for %a, already defined",n) end end function codeinjections.clonefield(specification) -- obsolete local p, c, v = specification.parent, specification.children, specification.alternative if not p or not c then if trace_fields then report_fields("invalid clone, children %a, parent %a, alternative %a",c,p,v) end return end local x = fields[p] or radios[p] if not x then if trace_fields then report_fields("invalid clone, unknown parent %a",p) end return end for n in gmatch(c,"[^, ]+") do local f, r, c = fields[n], radios[n], clones[n] if f or r or c then if trace_fields then report_fields("already cloned, child %a, parent %a, alternative %a",n,p,v) end else if trace_fields then report_fields("cloning, child %a, parent %a, alternative %a",n,p,v) end clones[n] = specification predefinesymbols(specification) end end end function codeinjections.getfieldcategory(name) local f = fields[name] or radios[name] or clones[name] if f then local g = f.category if not g or g == "" then local v, p, t = f.alternative, f.parent, f.type if v == "clone" or v == "copy" then f = fields[p] or radios[p] g = f and f.category elseif t == "sub" then f = fields[p] g = f and f.category end end return g end end -- function codeinjections.validfieldcategory(name) return fields[name] or radios[name] or clones[name] end function codeinjections.validfieldset(name) return fieldsets[tag] end function codeinjections.validfield(name) return 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.option if so and so ~= "" then specification.option = so .. "," .. option else specification.option = option end return specification end -- finish local collected = pdfarray() local forceencoding = false local function finishfields() local sometext = forceencoding for name, field in sortedhash(fields) do local kids = field.kids if kids then pdfflushobject(field.kidsnum,kids) end local opt = field.opt if opt then pdfflushobject(field.optnum,opt) end local type = field.type if not sometext and (type == "text" or type == "line") then sometext = true end end for name, field in sortedhash(radios) do local kids = field.kids if kids then pdfflushobject(field.kidsnum,kids) end local opt = field.opt if opt then pdfflushobject(field.optnum,opt) end end if #collected > 0 then local acroform = pdfdictionary { NeedAppearances = true, Fields = pdfreference(pdfflushobject(collected)), CO = fieldsetlist(calculationset), } if sometext then checkpdfdocencoding() usedfonts.tttf = fontnames.tt.tf acroform.DA = "/tttf 12 Tf 0 g" acroform.DR = pdfdictionary { Font = registerfonts(), Encoding = pdfdocencodingcapsule, } end lpdf.addtocatalog("AcroForm",pdfreference(pdfflushobject(acroform))) end end lpdf.registerdocumentfinalizer(finishfields,"form fields") local methods = { } function nodeinjections.typesetfield(name,specification) local field = fields[name] or radios[name] or clones[name] if not field then report_fields( "unknown child %a",name) -- unknown field return end local alternative, parent = field.alternative, field.parent if alternative == "copy" or alternative == "clone" then -- only in clones field = fields[parent] or radios[parent] end local method = methods[field.type] if method then return method(name,specification,alternative) else report_fields( "unknown method %a for child %a",field.type,name) end end local function save_parent(field,specification,d,hasopt) local kidsnum = pdfreserveobject() d.Kids = pdfreference(kidsnum) field.kidsnum = kidsnum field.kids = pdfarray() if hasopt then local optnum = pdfreserveobject() d.Opt = pdfreference(optnum) field.optnum = optnum field.opt = pdfarray() end local pnum = pdfflushobject(d) field.pobj = pnum collected[#collected+1] = pdfreference(pnum) end local function save_kid(field,specification,d,optname) local kn = pdfreserveobject() field.kids[#field.kids+1] = pdfreference(kn) if optname then local opt = field.opt if opt then opt[#opt+1] = optname end end local width, height, depth = specification.width or 0, specification.height or 0, specification.depth local box = hpack_node(nodeinjections.annotation(width,height,depth,d(),kn)) box.width, box.height, box.depth = width, height, depth -- redundant return box end local function makelineparent(field,specification) local text = pdfunicode(field.default) local length = tonumber(specification.length or 0) or 0 local d = pdfdictionary { Subtype = pdf_widget, T = pdfunicode(specification.title), F = fieldplus(specification), Ff = fieldflag(specification), OC = fieldlayer(specification), DA = fieldsurrounding(specification), AA = fieldactions(specification), FT = pdf_tx, Q = fieldalignment(specification), MaxLen = length == 0 and 1000 or length, DV = text, V = text, } save_parent(field,specification,d) end local function makelinechild(name,specification) local field, parent = clones[name], nil if field then parent = fields[field.parent] if not parent.pobj then if trace_fields then report_fields("forcing parent text %a",parent.name) end makelineparent(parent,specification) end else parent = fields[name] field = parent if not parent.pobj then if trace_fields then report_fields("using parent text %a",name) end makelineparent(parent,specification) end end if trace_fields then report_fields("using child text %a",name) end -- we could save a little by not setting some key/value when it's the -- same as parent but it would cost more memory to keep track of it local d = pdfdictionary { Subtype = pdf_widget, Parent = pdfreference(parent.pobj), F = fieldplus(specification), OC = fieldlayer(specification), DA = fieldsurrounding(specification), AA = fieldactions(specification), MK = fieldrendering(specification), Q = fieldalignment(specification), } return save_kid(parent,specification,d) end function methods.line(name,specification) return makelinechild(name,specification) end function methods.text(name,specification) return makelinechild(name,enhance(specification,"MultiLine")) end -- copy of line ... probably also needs a /Lock local function makesignatureparent(field,specification) local text = pdfunicode(field.default) local length = tonumber(specification.length or 0) or 0 local d = pdfdictionary { Subtype = pdf_widget, T = pdfunicode(specification.title), F = fieldplus(specification), Ff = fieldflag(specification), OC = fieldlayer(specification), DA = fieldsurrounding(specification), AA = fieldactions(specification), FT = pdf_sig, Q = fieldalignment(specification), MaxLen = length == 0 and 1000 or length, DV = text, V = text, } save_parent(field,specification,d) end local function makesignaturechild(name,specification) local field, parent = clones[name], nil if field then parent = fields[field.parent] if not parent.pobj then if trace_fields then report_fields("forcing parent signature %a",parent.name) end makesignatureparent(parent,specification) end else parent = fields[name] field = parent if not parent.pobj then if trace_fields then report_fields("using parent text %a",name) end makesignatureparent(parent,specification) end end if trace_fields then report_fields("using child text %a",name) end -- we could save a little by not setting some key/value when it's the -- same as parent but it would cost more memory to keep track of it local d = pdfdictionary { Subtype = pdf_widget, Parent = pdfreference(parent.pobj), F = fieldplus(specification), OC = fieldlayer(specification), DA = fieldsurrounding(specification), AA = fieldactions(specification), MK = fieldrendering(specification), Q = fieldalignment(specification), } return save_kid(parent,specification,d) end function methods.signature(name,specification) return makesignaturechild(name,specification) end -- local function makechoiceparent(field,specification) 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), -- todo } save_parent(field,specification,d) end local function makechoicechild(name,specification) local field, parent = clones[name], nil if field then parent = fields[field.parent] if not parent.pobj then if trace_fields then report_fields("forcing parent choice %a",parent.name) end makechoiceparent(parent,specification,extras) end else parent = fields[name] field = parent if not parent.pobj then if trace_fields then report_fields("using parent choice %a",name) end makechoiceparent(parent,specification,extras) end end if trace_fields then report_fields("using child choice %a",name) end local d = pdfdictionary { Subtype = pdf_widget, Parent = pdfreference(parent.pobj), F = fieldplus(specification), OC = fieldlayer(specification), AA = fieldactions(specification), } return save_kid(parent,specification,d) -- do opt here end function methods.choice(name,specification) return makechoicechild(name,specification) end function methods.popup(name,specification) return makechoicechild(name,enhance(specification,"PopUp")) end function methods.combo(name,specification) return makechoicechild(name,enhance(specification,"PopUp,Edit")) end local function makecheckparent(field,specification) local d = pdfdictionary { T = pdfunicode(specification.title), -- todo: when tracing use a string F = fieldplus(specification), Ff = fieldflag(specification), OC = fieldlayer(specification), AA = fieldactions(specification), FT = pdf_btn, V = fielddefault(field), } save_parent(field,specification,d,true) end local function makecheckchild(name,specification) local field, parent = clones[name], nil if field then parent = fields[field.parent] if not parent.pobj then if trace_fields then report_fields("forcing parent check %a",parent.name) end makecheckparent(parent,specification,extras) end else parent = fields[name] field = parent if not parent.pobj then if trace_fields then report_fields("using parent check %a",name) end makecheckparent(parent,specification,extras) end end if trace_fields then report_fields("using child check %a",name) end local d = pdfdictionary { Subtype = pdf_widget, Parent = pdfreference(parent.pobj), F = fieldplus(specification), OC = fieldlayer(specification), AA = fieldactions(specification), H = pdf_n, } local fontsymbol = specification.fontsymbol if fontsymbol and fontsymbol ~= "" then specification.fontsymbol = todingbat(fontsymbol) specification.fontstyle = "symbol" specification.fontalternative = "dingbats" d.DA = fieldsurrounding(specification) d.MK = fieldrendering(specification) return save_kid(parent,specification,d) else local appearance, default, value = fieldstates_check(field,true) d.AS = default d.AP = appearance return save_kid(parent,specification,d,value) end end function methods.check(name,specification) return makecheckchild(name,specification) end local function makepushparent(field,specification) -- check if we can share with the previous 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) end local function makepushchild(name,specification) local field, parent = clones[name], nil if field then parent = fields[field.parent] if not parent.pobj then if trace_fields then report_fields("forcing parent push %a",parent.name) end makepushparent(parent,specification) end else parent = fields[name] field = parent if not parent.pobj then if trace_fields then report_fields("using parent push %a",name) end makepushparent(parent,specification) end end if trace_fields then report_fields("using child push %a",name) end local fontsymbol = specification.fontsymbol local d = pdfdictionary { Subtype = pdf_widget, Parent = pdfreference(field.pobj), F = fieldplus(specification), OC = fieldlayer(specification), AA = fieldactions(specification), H = pdf_p, } if fontsymbol and fontsymbol ~= "" then specification.fontsymbol = todingbat(fontsymbol) specification.fontstyle = "symbol" specification.fontalternative = "dingbats" d.DA = fieldsurrounding(specification) d.MK = fieldrendering(specification) else d.AP = fieldappearances(field) end return save_kid(parent,specification,d) end function methods.push(name,specification) return makepushchild(name,enhance(specification,"PushButton")) end local function makeradioparent(field,specification) -- specification = enhance(specification,"Radio,RadiosInUnison") specification = enhance(specification,"Radio,RadiosInUnison,Print,NoToggleToOff") -- specification = enhance(specification,"Radio,Print,NoToggleToOff") local d = pdfdictionary { T = field.name, FT = pdf_btn, -- F = fieldplus(specification), Ff = fieldflag(specification), -- H = pdf_n, V = fielddefault(field), } save_parent(field,specification,d,true) end -- local function makeradiochild(name,specification) -- local field, parent = clones[name], nil -- if field then -- field = radios[field.parent] -- parent = fields[field.parent] -- if not parent.pobj then -- if trace_fields then -- report_fields("forcing parent radio %a",parent.name) -- end -- makeradioparent(parent,parent) -- end -- else -- field = radios[name] -- if not field then -- report_fields("there is some problem with field %a",name) -- return nil -- end -- parent = fields[field.parent] -- if not parent.pobj then -- if trace_fields then -- report_fields("using parent radio %a",name) -- end -- makeradioparent(parent,parent) -- end -- end -- if trace_fields then -- report_fields("using child radio %a with values %a and default %a",name,field.values,field.default) -- end -- local fontsymbol = specification.fontsymbol -- fontsymbol="star" -- local d = pdfdictionary { -- Subtype = pdf_widget, -- Parent = pdfreference(parent.pobj), -- F = fieldplus(specification), -- OC = fieldlayer(specification), -- AA = fieldactions(specification), -- H = pdf_n, -- } -- if fontsymbol and fontsymbol ~= "" then -- local appearance, default, value = fieldstates_radio(field,true,false,false,name) -- false is also ok -- specification.fontsymbol = todingbat(fontsymbol) -- specification.fontstyle = "symbol" -- specification.fontalternative = "dingbats" -- d.DA = fieldsurrounding(specification) -- d.MK = fieldrendering(specification) -- d.AS = pdfconstant(value) -- default -- mandate when AP but confuses viewers -- d.AP = appearance -- return save_kid(parent,specification,d,value) -- -- return save_kid(parent,specification,d,name) -- else -- -- local appearance, default, value = fieldstates_radio(field,true) -- false is also ok -- local appearance, default, value = fieldstates_radio(field,true,false,false,name) -- false is also ok -- d.AS = default -- mandate when AP but confuses viewers -- d.AP = appearance -- return save_kid(parent,specification,d,value) -- end -- end local function makeradiochild(name,specification) local field, parent = clones[name], nil if field then field = radios[field.parent] parent = fields[field.parent] if not parent.pobj then if trace_fields then report_fields("forcing parent radio %a",parent.name) end makeradioparent(parent,parent) end else field = radios[name] if not field then report_fields("there is some problem with field %a",name) return nil end parent = fields[field.parent] if not parent.pobj then if trace_fields then report_fields("using parent radio %a",name) end makeradioparent(parent,parent) end end if trace_fields then report_fields("using child radio %a with values %a and default %a",name,field.values,field.default) end local fontsymbol = specification.fontsymbol -- fontsymbol = "circle" local d = pdfdictionary { Subtype = pdf_widget, Parent = pdfreference(parent.pobj), F = fieldplus(specification), OC = fieldlayer(specification), AA = fieldactions(specification), H = pdf_n, } if fontsymbol and fontsymbol ~= "" then specification.fontsymbol = todingbat(fontsymbol) specification.fontstyle = "symbol" specification.fontalternative = "dingbats" d.DA = fieldsurrounding(specification) d.MK = fieldrendering(specification) end local appearance, default, value = fieldstates_radio(field,name,fields[field.parent]) d.AP = appearance d.AS = default -- /Whatever return save_kid(parent,specification,d,value) end function methods.sub(name,specification) return makeradiochild(name,enhance(specification,"Radio,RadiosInUnison")) end