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
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
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")
109local pdf_n = pdfconstant("N")
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 = {
126 ReadOnly = 0x00000001,
127 Required = 0x00000002,
128 NoExport = 0x00000004,
129 MultiLine = 0x00001000,
130 Password = 0x00002000,
131 NoToggleToOff = 0x00004000,
132 Radio = 0x00008000,
133 PushButton = 0x00010000,
134 PopUp = 0x00020000,
135 Edit = 0x00040000,
136 Sort = 0x00080000,
137 FileSelect = 0x00100000,
138 DoNotSpellCheck = 0x00400000,
139 DoNotScroll = 0x00800000,
140 Comb = 0x01000000,
141 RichText = 0x02000000,
142 RadiosInUnison = 0x02000000,
143 CommitOnSelChange = 0x04000000,
144}
145
146local plus = {
147 Invisible = 0x00000001,
148 Hidden = 0x00000002,
149 Printable = 0x00000004,
150 Print = 0x00000004,
151 NoZoom = 0x00000008,
152 NoRotate = 0x00000010,
153 NoView = 0x00000020,
154 ReadOnly = 0x00000040,
155 Locked = 0x00000080,
156 ToggleNoView = 0x00000100,
157 LockedContents = 0x00000200,
158 AutoView = 0x00000100,
159}
160
161
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
180
181local function fieldflag(specification)
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)
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
199 return n
200end
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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
247
248}
249
250local function fieldactions(specification)
251 local d = nil
252 for key, target in sortedhash(mapping) do
253 local code = specification[key]
254 if code and code ~= "" then
255
256 local set, bug = references.identify("",code)
257 if not bug and #set > 0 then
258 local a = pdfaction(set)
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
275
276
277 return d
278end
279
280
281
282local pdfdocencodingvector, pdfdocencodingcapsule
283
284
285
286
287
288
289local function checkpdfdocencoding()
290 report_fields("adding pdfdoc encoding vector")
291 local encoding = dofile(resolvers.findfile("lpdf-enc.lmt"))
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
349 local fontcode = formatters["%0.4F Tf %0.4F Ts"](fontsize,fontraise)
350
351 local colorcode = pdfcolor(3,colorvalue)
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
361
362 return tostring(stream)
363end
364
365
366
367codeinjections.fieldsurrounding = fieldsurrounding
368
369local function registerfonts()
370 if next(usedfonts) then
371 checkpdfdocencoding()
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
390
391local function fieldappearances(specification)
392
393 local values = specification.values
394 local default = specification.default
395 if not values then
396
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
415end
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
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
452 local values = specification.values
453 local default = specification.default
454 if not values or values == "" then
455
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
504 if true then
505
506 appearance = pdfdictionary {
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 {
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
520 return appearanceref, default, yesvalue
521end
522
523
524
525
526
527
528
529local function fieldstates_radio(specification,name,parent)
530 local values = values or specification.values
531 local default = default or parent.default
532 if not values or values == "" then
533
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
544
545 local yessymbols, yesvalue = lpegmatch(splitter,yes)
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
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
590 appearance = pdfdictionary {
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 {
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)
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
639 check = "4",
640 circle = "l",
641 cross = "8",
642 diamond = "u",
643 square = "n",
644 star = "H",
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,
660 BC = fvalue and pdfarray { pdfcolorvalues(3,fvalue) } or nil,
661 CA = svalue and pdfstring (svalue) or nil,
662 }
663 end
664end
665
666
667
668local function fieldlayer(specification)
669 local layer = specification.layer
670 return (layer and pdflayerreference(layer)) or nil
671end
672
673
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)
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
789 local radio = radios[n]
790 if radio then
791
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)
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
911
912
913local collected = pdfarray()
914local forceencoding = false
915
916
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
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
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
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
997 return
998 end
999 local alternative, parent = field.alternative, field.parent
1000 if alternative == "copy" or alternative == "clone" then
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
1017
1018
1019
1020
1021
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
1031
1032
1033
1034
1035
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
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
1092
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
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
1162
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
1173
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"
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
1200
1201 FT = pdf_sig,
1202
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
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),
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)
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),
1286 F = fieldplus(specification),
1287 Ff = fieldflag(specification),
1288 OC = fieldlayer(specification),
1289 AA = fieldactions(specification),
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),
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)
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),
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),
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
1417 Ff = fieldflag(specification),
1418
1419 V = fielddefault(field),
1420 }
1421 save_parent(field,specification,d)
1422end
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
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
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
1531
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 |