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 backends, lpdf = backends, lpdf
66
67local variables = interfaces.variables
68local context = context
69
70local references = structures.references
71local settings_to_array = utilities.parsers.settings_to_array
72
73local pdfbackend = backends.pdf
74
75local nodeinjections = pdfbackend.nodeinjections
76local codeinjections = pdfbackend.codeinjections
77local registrations = pdfbackend.registrations
78
79local registeredsymbol = codeinjections.registeredsymbol
80
81local pdfstream = lpdf.stream
82local pdfdictionary = lpdf.dictionary
83local pdfarray = lpdf.array
84local pdfreference = lpdf.reference
85local pdfunicode = lpdf.unicode
86local pdfstring = lpdf.string
87local pdfconstant = lpdf.constant
88local pdfflushobject = lpdf.flushobject
89local pdfshareobjectreference = lpdf.shareobjectreference
90local pdfshareobject = lpdf.shareobject
91local pdfreserveobject = lpdf.reserveobject
92local pdfpagereference = lpdf.pagereference
93local pdfaction = lpdf.action
94local pdfmajorversion = lpdf.majorversion
95
96local pdfcolor = lpdf.color
97local pdfcolorvalues = lpdf.colorvalues
98local pdflayerreference = lpdf.layerreference
99
100local hpack_node = nodes.hpack
101
102local submitoutputformat = 0
103
104local pdf_widget = pdfconstant("Widget")
105local pdf_tx = pdfconstant("Tx")
106local pdf_sig = pdfconstant("Sig")
107local pdf_ch = pdfconstant("Ch")
108local pdf_btn = pdfconstant("Btn")
109local pdf_yes = pdfconstant("Yes")
110local pdf_off = pdfconstant("Off")
111local pdf_p = pdfconstant("P")
112local pdf_n = pdfconstant("N")
113
114local pdf_no_rect = pdfarray { 0, 0, 0, 0 }
115
116local splitter = lpeg.splitat("=>")
117
118local formats = {
119 html = 1, fdf = 2, xml = 3,
120}
121
122function codeinjections.setformsmethod(name)
123 submitoutputformat = formats[lower(name)] or formats.xml
124end
125
126local flag = {
127 ReadOnly = 0x00000001,
128 Required = 0x00000002,
129 NoExport = 0x00000004,
130 MultiLine = 0x00001000,
131 Password = 0x00002000,
132 NoToggleToOff = 0x00004000,
133 Radio = 0x00008000,
134 PushButton = 0x00010000,
135 PopUp = 0x00020000,
136 Edit = 0x00040000,
137 Sort = 0x00080000,
138 FileSelect = 0x00100000,
139 DoNotSpellCheck = 0x00400000,
140 DoNotScroll = 0x00800000,
141 Comb = 0x01000000,
142 RichText = 0x02000000,
143 RadiosInUnison = 0x02000000,
144 CommitOnSelChange = 0x04000000,
145}
146
147local plus = {
148 Invisible = 0x00000001,
149 Hidden = 0x00000002,
150 Printable = 0x00000004,
151 Print = 0x00000004,
152 NoZoom = 0x00000008,
153 NoRotate = 0x00000010,
154 NoView = 0x00000020,
155 ReadOnly = 0x00000040,
156 Locked = 0x00000080,
157 ToggleNoView = 0x00000100,
158 LockedContents = 0x00000200,
159 AutoView = 0x00000100,
160}
161
162
163
164flag.readonly = flag.ReadOnly
165flag.required = flag.Required
166flag.protected = flag.Password
167flag.sorted = flag.Sort
168flag.unavailable = flag.NoExport
169flag.nocheck = flag.DoNotSpellCheck
170flag.fixed = flag.DoNotScroll
171flag.file = flag.FileSelect
172
173plus.hidden = plus.Hidden
174plus.printable = plus.Printable
175plus.auto = plus.AutoView
176
177lpdf.flags.widgets = flag
178lpdf.flags.annotations = plus
179
180
181
182local function fieldflag(specification)
183 local o, n = specification.option, 0
184 if o and o ~= "" then
185 for f in gmatch(o,"[^, ]+") do
186 n = n + (flag[f] or 0)
187 end
188 end
189 return n
190end
191
192local function fieldplus(specification)
193 local o, n = specification.option, 0
194 if o and o ~= "" then
195 for p in gmatch(o,"[^, ]+") do
196 n = n + (plus[p] or 0)
197 end
198 end
199
200 return n
201end
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
233
234local mapping = {
235 mousedown = "D", clickin = "D",
236 mouseup = "U", clickout = "U",
237 regionin = "E",
238 regionout = "X",
239 afterkey = "K",
240 format = "F",
241 validate = "V",
242 calculate = "C",
243 focusin = "Fo",
244 focusout = "Bl",
245 openpage = "PO",
246 closepage = "PC",
247
248
249}
250
251local function fieldactions(specification)
252 local d = nil
253 for key, target in sortedhash(mapping) do
254 local code = specification[key]
255 if code and code ~= "" then
256
257 local set, bug = references.identify("",code)
258 if not bug and #set > 0 then
259 local a = pdfaction(set)
260 if a then
261 local r = pdfshareobjectreference(a)
262 if d then
263 d[target] = r
264 else
265 d = pdfdictionary { [target] = r }
266 end
267 else
268 report_fields("invalid field action %a, case %s",code,2)
269 end
270 else
271 report_fields("invalid field action %a, case %s",code,1)
272 end
273 end
274 end
275
276
277
278 return d
279end
280
281
282
283local pdfdocencodingvector, pdfdocencodingcapsule
284
285
286
287
288
289
290local function checkpdfdocencoding()
291 report_fields("adding pdfdoc encoding vector")
292 local encoding = dofile(resolvers.findfile("lpdf-enc.lua"))
293 pdfdocencodingvector = pdfreference(pdfflushobject(encoding))
294 local capsule = pdfdictionary {
295 PDFDocEncoding = pdfdocencodingvector
296 }
297 pdfdocencodingcapsule = pdfreference(pdfflushobject(capsule))
298 checkpdfdocencoding = function() end
299end
300
301local fontnames = {
302 rm = {
303 tf = "Times-Roman",
304 bf = "Times-Bold",
305 it = "Times-Italic",
306 sl = "Times-Italic",
307 bi = "Times-BoldItalic",
308 bs = "Times-BoldItalic",
309 },
310 ss = {
311 tf = "Helvetica",
312 bf = "Helvetica-Bold",
313 it = "Helvetica-Oblique",
314 sl = "Helvetica-Oblique",
315 bi = "Helvetica-BoldOblique",
316 bs = "Helvetica-BoldOblique",
317 },
318 tt = {
319 tf = "Courier",
320 bf = "Courier-Bold",
321 it = "Courier-Oblique",
322 sl = "Courier-Oblique",
323 bi = "Courier-BoldOblique",
324 bs = "Courier-BoldOblique",
325 },
326 symbol = {
327 dingbats = "ZapfDingbats",
328 }
329}
330
331local usedfonts = { }
332
333local function fieldsurrounding(specification)
334 local fontsize = specification.fontsize or "12pt"
335 local fontstyle = specification.fontstyle or "rm"
336 local fontalternative = specification.fontalternative or "tf"
337 local colorvalue = tonumber(specification.colorvalue)
338 local s = fontnames[fontstyle]
339 if not s then
340 fontstyle, s = "rm", fontnames.rm
341 end
342 local a = s[fontalternative]
343 if not a then
344 alternative, a = "tf", s.tf
345 end
346 local tag = fontstyle .. fontalternative
347 fontsize = todimen(fontsize)
348 fontsize = fontsize and (bpfactor * fontsize) or 12
349 fontraise = 0.1 * fontsize
350 local fontcode = formatters["%0.4F Tf %0.4F Ts"](fontsize,fontraise)
351
352 local colorcode = pdfcolor(3,colorvalue)
353 if trace_fields then
354 report_fields("using font, style %a, alternative %a, size %p, tag %a, code %a",fontstyle,fontalternative,fontsize,tag,fontcode)
355 report_fields("using color, value %a, code %a",colorvalue,colorcode)
356 end
357 local stream = pdfstream {
358 pdfconstant(tag),
359 formatters["%s %s"](fontcode,colorcode)
360 }
361 usedfonts[tag] = a
362
363 return tostring(stream)
364end
365
366
367
368codeinjections.fieldsurrounding = fieldsurrounding
369
370local function registerfonts()
371 if next(usedfonts) then
372 checkpdfdocencoding()
373 local pdffontlist = pdfdictionary()
374 local pdffonttype = pdfconstant("Font")
375 local pdffontsubtype = pdfconstant("Type1")
376 for tag, name in sortedhash(usedfonts) do
377 local f = pdfdictionary {
378 Type = pdffonttype,
379 Subtype = pdffontsubtype,
380 Name = pdfconstant(tag),
381 BaseFont = pdfconstant(name),
382 Encoding = pdfdocencodingvector,
383 }
384 pdffontlist[tag] = pdfreference(pdfflushobject(f))
385 end
386 return pdffontlist
387 end
388end
389
390
391
392local function fieldappearances(specification)
393
394 local values = specification.values
395 local default = specification.default
396 if not values then
397
398 return
399 end
400 local v = settings_to_array(values)
401 local n, r, d
402 if #v == 1 then
403 n, r, d = v[1], v[1], v[1]
404 elseif #v == 2 then
405 n, r, d = v[1], v[1], v[2]
406 else
407 n, r, d = v[1], v[2], v[3]
408 end
409 local appearance = pdfdictionary {
410 N = registeredsymbol(n),
411 R = registeredsymbol(r),
412 D = registeredsymbol(d),
413 }
414 return pdfshareobjectreference(appearance)
415
416end
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437local function fieldstates_precheck(specification)
438 local values = specification.values
439 local default = specification.default
440 if not values or values == "" then
441 return
442 end
443 local yes = settings_to_array(values)[1]
444 local yesshown, yesvalue = lpegmatch(splitter,yes)
445 if not (yesshown and yesvalue) then
446 yesshown = yes
447 end
448 return default == settings_to_array(yesshown)[1] and pdf_yes or pdf_off
449end
450
451local function fieldstates_check(specification)
452
453 local values = specification.values
454 local default = specification.default
455 if not values or values == "" then
456
457 return
458 end
459 local v = settings_to_array(values)
460 local yes, off, yesn, yesr, yesd, offn, offr, offd
461 if #v == 1 then
462 yes, off = v[1], v[1]
463 else
464 yes, off = v[1], v[2]
465 end
466 local yesshown, yesvalue = lpegmatch(splitter,yes)
467 if not (yesshown and yesvalue) then
468 yesshown = yes, yes
469 end
470 yes = settings_to_array(yesshown)
471 local offshown, offvalue = lpegmatch(splitter,off)
472 if not (offshown and offvalue) then
473 offshown = off, off
474 end
475 off = settings_to_array(offshown)
476 if #yes == 1 then
477 yesn, yesr, yesd = yes[1], yes[1], yes[1]
478 elseif #yes == 2 then
479 yesn, yesr, yesd = yes[1], yes[1], yes[2]
480 else
481 yesn, yesr, yesd = yes[1], yes[2], yes[3]
482 end
483 if #off == 1 then
484 offn, offr, offd = off[1], off[1], off[1]
485 elseif #off == 2 then
486 offn, offr, offd = off[1], off[1], off[2]
487 else
488 offn, offr, offd = off[1], off[2], off[3]
489 end
490 if not yesvalue then
491 yesvalue = yesdefault or yesn
492 end
493 if not offvalue then
494 offvalue = offn
495 end
496 if default == yesn then
497 default = pdf_yes
498 yesvalue = yesvalue == yesn and "Yes" or "Off"
499 else
500 default = pdf_off
501 yesvalue = "Off"
502 end
503 local appearance
504
505 if true then
506
507 appearance = pdfdictionary {
508 N = pdfshareobjectreference(pdfdictionary { Yes = registeredsymbol(yesn), Off = registeredsymbol(offn) }),
509 R = pdfshareobjectreference(pdfdictionary { Yes = registeredsymbol(yesr), Off = registeredsymbol(offr) }),
510 D = pdfshareobjectreference(pdfdictionary { Yes = registeredsymbol(yesd), Off = registeredsymbol(offd) }),
511 }
512 else
513 appearance = pdfdictionary {
514 N = pdfdictionary { Yes = registeredsymbol(yesn), Off = registeredsymbol(offn) },
515 R = pdfdictionary { Yes = registeredsymbol(yesr), Off = registeredsymbol(offr) },
516 D = pdfdictionary { Yes = registeredsymbol(yesd), Off = registeredsymbol(offd) }
517 }
518 end
519 local appearanceref = pdfshareobjectreference(appearance)
520
521 return appearanceref, default, yesvalue
522end
523
524
525
526
527
528
529
530local function fieldstates_radio(specification,name,parent)
531 local values = values or specification.values
532 local default = default or parent.default
533 if not values or values == "" then
534
535 return
536 end
537 local v = settings_to_array(values)
538 local yes, off, yesn, yesr, yesd, offn, offr, offd
539 if #v == 1 then
540 yes, off = v[1], v[1]
541 else
542 yes, off = v[1], v[2]
543 end
544
545
546 local yessymbols, yesvalue = lpegmatch(splitter,yes)
547 if not (yessymbols and yesvalue) then
548 yessymbols = yes
549 end
550 if not yesvalue then
551 yesvalue = name
552 end
553 yessymbols = settings_to_array(yessymbols)
554 if #yessymbols == 1 then
555 yesn = yessymbols[1]
556 yesr = yesn
557 yesd = yesr
558 elseif #yessymbols == 2 then
559 yesn = yessymbols[1]
560 yesr = yessymbols[2]
561 yesd = yesr
562 else
563 yesn = yessymbols[1]
564 yesr = yessymbols[2]
565 yesd = yessymbols[3]
566 end
567
568 local offsymbols = lpegmatch(splitter,off) or off
569 offsymbols = settings_to_array(offsymbols)
570 if #offsymbols == 1 then
571 offn = offsymbols[1]
572 offr = offn
573 offd = offr
574 elseif #offsymbols == 2 then
575 offn = offsymbols[1]
576 offr = offsymbols[2]
577 offd = offr
578 else
579 offn = offsymbols[1]
580 offr = offsymbols[2]
581 offd = offsymbols[3]
582 end
583 if default == name then
584 default = pdfconstant(name)
585 else
586 default = pdf_off
587 end
588
589 local appearance
590 if false then
591 appearance = pdfdictionary {
592 N = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesn), Off = registeredsymbol(offn) }),
593 R = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesr), Off = registeredsymbol(offr) }),
594 D = pdfshareobjectreference(pdfdictionary { [name] = registeredsymbol(yesd), Off = registeredsymbol(offd) }),
595 }
596 else
597 appearance = pdfdictionary {
598 N = pdfdictionary { [name] = registeredsymbol(yesn), Off = registeredsymbol(offn) },
599 R = pdfdictionary { [name] = registeredsymbol(yesr), Off = registeredsymbol(offr) },
600 D = pdfdictionary { [name] = registeredsymbol(yesd), Off = registeredsymbol(offd) }
601 }
602 end
603 local appearanceref = pdfshareobjectreference(appearance)
604 return appearanceref, default, yesvalue
605end
606
607local function fielddefault(field,pdf_yes)
608 local default = field.default
609 if not default or default == "" then
610 local values = settings_to_array(field.values)
611 default = values[1]
612 end
613 if not default or default == "" then
614 return pdf_off
615 else
616 return pdf_yes or pdfconstant(default)
617 end
618end
619
620local function fieldoptions(specification)
621 local values = specification.values
622 local default = specification.default
623 if values then
624 local v = settings_to_array(values)
625 for i=1,#v do
626 local vi = v[i]
627 local shown, value = lpegmatch(splitter,vi)
628 if shown and value then
629 v[i] = pdfarray { pdfunicode(value), shown }
630 else
631 v[i] = pdfunicode(v[i])
632 end
633 end
634 return pdfarray(v)
635 end
636end
637
638local mapping = {
639
640 check = "4",
641 circle = "l",
642 cross = "8",
643 diamond = "u",
644 square = "n",
645 star = "H",
646}
647
648local function todingbat(n)
649 if n and n ~= "" then
650 return mapping[n] or ""
651 end
652end
653
654local function fieldrendering(specification)
655 local bvalue = tonumber(specification.backgroundcolorvalue)
656 local fvalue = tonumber(specification.framecolorvalue)
657 local svalue = specification.fontsymbol
658 if bvalue or fvalue or (svalue and svalue ~= "") then
659 return pdfdictionary {
660 BG = bvalue and pdfarray { pdfcolorvalues(3,bvalue) } or nil,
661 BC = fvalue and pdfarray { pdfcolorvalues(3,fvalue) } or nil,
662 CA = svalue and pdfstring (svalue) or nil,
663 }
664 end
665end
666
667
668
669local function fieldlayer(specification)
670 local layer = specification.layer
671 return (layer and pdflayerreference(layer)) or nil
672end
673
674
675
676local fields, radios, clones, fieldsets, calculationset = { }, { }, { }, { }, nil
677
678local xfdftemplate = [[
679<?xml version='1.0' encoding='UTF-8'?>
680
681<xfdf xmlns='http://ns.adobe.com/xfdf/'>
682 <f href='%s.pdf'/>
683 <fields>
684%s
685 </fields>
686</xfdf>
687]]
688
689function codeinjections.exportformdata(name)
690 local result = { }
691 for k, v in sortedhash(fields) do
692 result[#result+1] = formatters[" <field name='%s'><value>%s</value></field>"](v.name or k,v.default or "")
693 end
694 local base = file.basename(tex.jobname)
695 local xfdf = format(xfdftemplate,base,table.concat(result,"\n"))
696 if not name or name == "" then
697 name = base
698 end
699 io.savedata(file.addsuffix(name,"xfdf"),xfdf)
700end
701
702function codeinjections.definefieldset(tag,list)
703 fieldsets[tag] = list
704end
705
706function codeinjections.getfieldset(tag)
707 return fieldsets[tag]
708end
709
710local function fieldsetlist(tag)
711 if tag then
712 local ft = fieldsets[tag]
713 if ft then
714 local a = pdfarray()
715 for name in gmatch(list,"[^, ]+") do
716 local f = field[name]
717 if f and f.pobj then
718 a[#a+1] = pdfreference(f.pobj)
719 end
720 end
721 return a
722 end
723 end
724end
725
726function codeinjections.setfieldcalculationset(tag)
727 calculationset = tag
728end
729
730interfaces.implement {
731 name = "setfieldcalculationset",
732 actions = codeinjections.setfieldcalculationset,
733 arguments = "string",
734}
735
736local function predefinesymbols(specification)
737 local values = specification.values
738 if values then
739 local symbols = settings_to_array(values)
740 for i=1,#symbols do
741 local symbol = symbols[i]
742 local a, b = lpegmatch(splitter,symbol)
743 codeinjections.presetsymbol(a or symbol)
744 end
745 end
746end
747
748function codeinjections.getdefaultfieldvalue(name)
749 local f = fields[name]
750 if f then
751 local values = f.values
752 local default = f.default
753 if not default or default == "" then
754 local symbols = settings_to_array(values)
755 local symbol = symbols[1]
756 if symbol then
757 local a, b = lpegmatch(splitter,symbol)
758 default = a or symbol
759 end
760 end
761 return default
762 end
763end
764
765function codeinjections.definefield(specification)
766 local n = specification.name
767 local f = fields[n]
768 if not f then
769 local fieldtype = specification.type
770 if not fieldtype then
771 if trace_fields then
772 report_fields("invalid definition for %a, unknown type",n)
773 end
774 elseif fieldtype == "radio" then
775 local values = specification.values
776 if values and values ~= "" then
777 values = settings_to_array(values)
778 for v=1,#values do
779 radios[values[v]] = { parent = n }
780 end
781 fields[n] = specification
782 if trace_fields then
783 report_fields("defining %a as type %a",n,"radio")
784 end
785 elseif trace_fields then
786 report_fields("invalid definition of radio %a, missing values",n)
787 end
788 elseif fieldtype == "sub" then
789
790 local radio = radios[n]
791 if radio then
792
793 for key, value in next, specification do
794 radio[key] = value
795 end
796 if trace_fields then
797 local p = radios[n] and radios[n].parent
798 report_fields("defining %a as type sub of radio %a",n,p)
799 end
800 elseif trace_fields then
801 report_fields("invalid definition of radio sub %a, no parent given",n)
802 end
803 predefinesymbols(specification)
804 elseif fieldtype == "text" or fieldtype == "line" then
805 fields[n] = specification
806 if trace_fields then
807 report_fields("defining %a as type %a",n,fieldtype)
808 end
809 if specification.values ~= "" and specification.default == "" then
810 specification.default, specification.values = specification.values, nil
811 end
812 else
813 fields[n] = specification
814 if trace_fields then
815 report_fields("defining %a as type %a",n,fieldtype)
816 end
817 predefinesymbols(specification)
818 end
819 elseif trace_fields then
820 report_fields("invalid definition for %a, already defined",n)
821 end
822end
823
824function codeinjections.clonefield(specification)
825 local p = specification.parent
826 local c = specification.children
827 local v = specification.alternative
828 if not p or not c then
829 if trace_fields then
830 report_fields("invalid clone, children %a, parent %a, alternative %a",c,p,v)
831 end
832 return
833 end
834 local x = fields[p] or radios[p]
835 if not x then
836 if trace_fields then
837 report_fields("invalid clone, unknown parent %a",p)
838 end
839 return
840 end
841 for n in gmatch(c,"[^, ]+") do
842 local f, r, c = fields[n], radios[n], clones[n]
843 if f or r or c then
844 if trace_fields then
845 report_fields("already cloned, child %a, parent %a, alternative %a",n,p,v)
846 end
847 else
848 if trace_fields then
849 report_fields("cloning, child %a, parent %a, alternative %a",n,p,v)
850 end
851 clones[n] = specification
852 predefinesymbols(specification)
853 end
854 end
855end
856
857function codeinjections.getfieldcategory(name)
858 local f = fields[name] or radios[name] or clones[name]
859 if f then
860 local g = f.category
861 if not g or g == "" then
862 local v, p, t = f.alternative, f.parent, f.type
863 if v == "clone" or v == "copy" then
864 f = fields[p] or radios[p]
865 g = f and f.category
866 elseif t == "sub" then
867 f = fields[p]
868 g = f and f.category
869 end
870 end
871 return g
872 end
873end
874
875
876
877function codeinjections.validfieldcategory(name)
878 return fields[name] or radios[name] or clones[name]
879end
880
881function codeinjections.validfieldset(name)
882 return fieldsets[tag]
883end
884
885function codeinjections.validfield(name)
886 return fields[name]
887end
888
889
890
891local alignments = {
892 flushleft = 0, right = 0,
893 center = 1, middle = 1,
894 flushright = 2, left = 2,
895}
896
897local function fieldalignment(specification)
898 return alignments[specification.align] or 0
899end
900
901local function enhance(specification,option)
902 local so = specification.option
903 if so and so ~= "" then
904 specification.option = so .. "," .. option
905 else
906 specification.option = option
907 end
908 return specification
909end
910
911
912
913
914local collected = pdfarray()
915local forceencoding = false
916
917
918
919local function finishfields()
920 local sometext = forceencoding
921 local somefont = next(usedfonts)
922 for name, field in sortedhash(fields) do
923 local kids = field.kids
924 if kids then
925 pdfflushobject(field.kidsnum,kids)
926 end
927 local opt = field.opt
928 if opt then
929 pdfflushobject(field.optnum,opt)
930 end
931 local type = field.type
932 if not sometext and (type == "text" or type == "line") then
933 sometext = true
934 end
935 end
936 for name, field in sortedhash(radios) do
937 local kids = field.kids
938 if kids then
939 pdfflushobject(field.kidsnum,kids)
940 end
941 local opt = field.opt
942 if opt then
943 pdfflushobject(field.optnum,opt)
944 end
945 end
946 if #collected > 0 then
947 local acroform = pdfdictionary {
948 NeedAppearances = pdfmajorversion() == 1 or nil,
949 Fields = pdfreference(pdfflushobject(collected)),
950 CO = fieldsetlist(calculationset),
951 }
952 if sometext or somefont then
953 checkpdfdocencoding()
954 if sometext then
955 usedfonts.tttf = fontnames.tt.tf
956 acroform.DA = "/tttf 12 Tf 0 g"
957 end
958 acroform.DR = pdfdictionary {
959 Font = registerfonts(),
960 Encoding = pdfdocencodingcapsule,
961 }
962 end
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979 lpdf.addtocatalog("AcroForm",pdfreference(pdfflushobject(acroform)))
980 end
981end
982
983lpdf.registerdocumentfinalizer(finishfields,"form fields")
984
985local methods = { }
986
987function nodeinjections.typesetfield(name,specification)
988 local field = fields[name] or radios[name] or clones[name]
989 if not field then
990 report_fields( "unknown child %a",name)
991
992 return
993 end
994 local alternative, parent = field.alternative, field.parent
995 if alternative == "copy" or alternative == "clone" then
996 field = fields[parent] or radios[parent]
997 end
998 local method = methods[field.type]
999 if method then
1000 return method(name,specification,alternative)
1001 else
1002 report_fields( "unknown method %a for child %a",field.type,name)
1003 end
1004end
1005
1006local function save_parent(field,specification,d)
1007 local kidsnum = pdfreserveobject()
1008 d.Kids = pdfreference(kidsnum)
1009 field.kidsnum = kidsnum
1010 field.kids = pdfarray()
1011
1012
1013
1014
1015
1016
1017 local pnum = pdfflushobject(d)
1018 field.pobj = pnum
1019 collected[#collected+1] = pdfreference(pnum)
1020end
1021
1022local function save_kid(field,specification,d,optname)
1023 local kn = pdfreserveobject()
1024 field.kids[#field.kids+1] = pdfreference(kn)
1025
1026
1027
1028
1029
1030
1031 local width = specification.width or 0
1032 local height = specification.height or 0
1033 local depth = specification.depth or 0
1034 local box = hpack_node(nodeinjections.annotation(width,height,depth,d(),kn))
1035
1036 box.width = width
1037 box.height = height
1038 box.depth = depth
1039 return box
1040end
1041
1042local function makelineparent(field,specification)
1043 local text = pdfunicode(field.default)
1044 local length = tonumber(specification.length or 0) or 0
1045 local d = pdfdictionary {
1046 Subtype = pdf_widget,
1047 T = pdfunicode(specification.title),
1048 F = fieldplus(specification),
1049 Ff = fieldflag(specification),
1050 OC = fieldlayer(specification),
1051 DA = fieldsurrounding(specification),
1052 AA = fieldactions(specification),
1053 FT = pdf_tx,
1054 Q = fieldalignment(specification),
1055 MaxLen = length == 0 and 1000 or length,
1056 DV = text,
1057 V = text,
1058 }
1059 save_parent(field,specification,d)
1060end
1061
1062local function makelinechild(name,specification)
1063 local field = clones[name]
1064 local parent = nil
1065 if field then
1066 parent = fields[field.parent]
1067 if not parent.pobj then
1068 if trace_fields then
1069 report_fields("forcing parent text %a",parent.name)
1070 end
1071 makelineparent(parent,specification)
1072 end
1073 else
1074 parent = fields[name]
1075 field = parent
1076 if not parent.pobj then
1077 if trace_fields then
1078 report_fields("using parent text %a",name)
1079 end
1080 makelineparent(parent,specification)
1081 end
1082 end
1083 if trace_fields then
1084 report_fields("using child text %a",name)
1085 end
1086
1087
1088 local d = pdfdictionary {
1089 Subtype = pdf_widget,
1090 Parent = pdfreference(parent.pobj),
1091 F = fieldplus(specification),
1092 OC = fieldlayer(specification),
1093 DA = fieldsurrounding(specification),
1094 AA = fieldactions(specification),
1095 MK = fieldrendering(specification),
1096 Q = fieldalignment(specification),
1097 }
1098 return save_kid(parent,specification,d)
1099end
1100
1101function methods.line(name,specification)
1102 return makelinechild(name,specification)
1103end
1104
1105function methods.text(name,specification)
1106 return makelinechild(name,enhance(specification,"MultiLine"))
1107end
1108
1109
1110
1111local function makesignatureparent(field,specification)
1112 local text = pdfunicode(field.default)
1113 local length = tonumber(specification.length or 0) or 0
1114 local d = pdfdictionary {
1115 Subtype = pdf_widget,
1116 T = pdfunicode(specification.title),
1117 F = fieldplus(specification),
1118 Ff = fieldflag(specification),
1119 OC = fieldlayer(specification),
1120 DA = fieldsurrounding(specification),
1121 AA = fieldactions(specification),
1122 FT = pdf_sig,
1123 Q = fieldalignment(specification),
1124 MaxLen = length == 0 and 1000 or length,
1125 DV = text,
1126 V = text,
1127 }
1128 save_parent(field,specification,d)
1129end
1130
1131local function makesignaturechild(name,specification)
1132 local field = clones[name]
1133 local parent = nil
1134 if field then
1135 parent = fields[field.parent]
1136 if not parent.pobj then
1137 if trace_fields then
1138 report_fields("forcing parent signature %a",parent.name)
1139 end
1140 makesignatureparent(parent,specification)
1141 end
1142 else
1143 parent = fields[name]
1144 field = parent
1145 if not parent.pobj then
1146 if trace_fields then
1147 report_fields("using parent text %a",name)
1148 end
1149 makesignatureparent(parent,specification)
1150 end
1151 end
1152 if trace_fields then
1153 report_fields("using child text %a",name)
1154 end
1155
1156
1157 local d = pdfdictionary {
1158 Subtype = pdf_widget,
1159 Parent = pdfreference(parent.pobj),
1160 F = fieldplus(specification),
1161 OC = fieldlayer(specification),
1162 DA = fieldsurrounding(specification),
1163 AA = fieldactions(specification),
1164 MK = fieldrendering(specification),
1165 Q = fieldalignment(specification),
1166 }
1167 return save_kid(parent,specification,d)
1168end
1169
1170function methods.signature(name,specification)
1171 return makesignaturechild(name,specification)
1172end
1173
1174
1175local function makechoiceparent(field,specification)
1176 local d = pdfdictionary {
1177 Subtype = pdf_widget,
1178 T = pdfunicode(specification.title),
1179 F = fieldplus(specification),
1180 Ff = fieldflag(specification),
1181 OC = fieldlayer(specification),
1182 AA = fieldactions(specification),
1183 FT = pdf_ch,
1184 Opt = fieldoptions(field),
1185 }
1186 save_parent(field,specification,d)
1187end
1188
1189local function makechoicechild(name,specification)
1190 local field = clones[name]
1191 local parent = nil
1192 if field then
1193 parent = fields[field.parent]
1194 if not parent.pobj then
1195 if trace_fields then
1196 report_fields("forcing parent choice %a",parent.name)
1197 end
1198 makechoiceparent(parent,specification,extras)
1199 end
1200 else
1201 parent = fields[name]
1202 field = parent
1203 if not parent.pobj then
1204 if trace_fields then
1205 report_fields("using parent choice %a",name)
1206 end
1207 makechoiceparent(parent,specification,extras)
1208 end
1209 end
1210 if trace_fields then
1211 report_fields("using child choice %a",name)
1212 end
1213 local d = pdfdictionary {
1214 Subtype = pdf_widget,
1215 Parent = pdfreference(parent.pobj),
1216 F = fieldplus(specification),
1217 OC = fieldlayer(specification),
1218 AA = fieldactions(specification),
1219 }
1220 return save_kid(parent,specification,d)
1221end
1222
1223function methods.choice(name,specification)
1224 return makechoicechild(name,specification)
1225end
1226
1227function methods.popup(name,specification)
1228 return makechoicechild(name,enhance(specification,"PopUp"))
1229end
1230
1231function methods.combo(name,specification)
1232 return makechoicechild(name,enhance(specification,"PopUp,Edit"))
1233end
1234
1235local function makecheckparent(field,specification)
1236 local default = fieldstates_precheck(field)
1237 local d = pdfdictionary {
1238 T = pdfunicode(specification.title),
1239 F = fieldplus(specification),
1240 Ff = fieldflag(specification),
1241 OC = fieldlayer(specification),
1242 AA = fieldactions(specification),
1243 FT = pdf_btn,
1244 V = fielddefault(field,default),
1245 }
1246 save_parent(field,specification,d)
1247end
1248
1249local function makecheckchild(name,specification)
1250 local field = clones[name]
1251 local parent = nil
1252 if field then
1253 parent = fields[field.parent]
1254 if not parent.pobj then
1255 if trace_fields then
1256 report_fields("forcing parent check %a",parent.name)
1257 end
1258 makecheckparent(parent,specification,extras)
1259 end
1260 else
1261 parent = fields[name]
1262 field = parent
1263 if not parent.pobj then
1264 if trace_fields then
1265 report_fields("using parent check %a",name)
1266 end
1267 makecheckparent(parent,specification,extras)
1268 end
1269 end
1270 if trace_fields then
1271 report_fields("using child check %a",name)
1272 end
1273 local d = pdfdictionary {
1274 Subtype = pdf_widget,
1275 Parent = pdfreference(parent.pobj),
1276 F = fieldplus(specification),
1277 OC = fieldlayer(specification),
1278 AA = fieldactions(specification),
1279 H = pdf_n,
1280 }
1281 local fontsymbol = specification.fontsymbol
1282 if fontsymbol and fontsymbol ~= "" then
1283 specification.fontsymbol = todingbat(fontsymbol)
1284 specification.fontstyle = "symbol"
1285 specification.fontalternative = "dingbats"
1286 d.DA = fieldsurrounding(specification)
1287 d.MK = fieldrendering(specification)
1288 return save_kid(parent,specification,d)
1289 else
1290 local appearance, default, value = fieldstates_check(field)
1291 d.AS = default
1292 d.AP = appearance
1293 return save_kid(parent,specification,d)
1294 end
1295end
1296
1297function methods.check(name,specification)
1298 return makecheckchild(name,specification)
1299end
1300
1301local function makepushparent(field,specification)
1302 local d = pdfdictionary {
1303 Subtype = pdf_widget,
1304 T = pdfunicode(specification.title),
1305 F = fieldplus(specification),
1306 Ff = fieldflag(specification),
1307 OC = fieldlayer(specification),
1308 AA = fieldactions(specification),
1309 FT = pdf_btn,
1310 AP = fieldappearances(field),
1311 H = pdf_p,
1312 }
1313 save_parent(field,specification,d)
1314end
1315
1316local function makepushchild(name,specification)
1317 local field, parent = clones[name], nil
1318 if field then
1319 parent = fields[field.parent]
1320 if not parent.pobj then
1321 if trace_fields then
1322 report_fields("forcing parent push %a",parent.name)
1323 end
1324 makepushparent(parent,specification)
1325 end
1326 else
1327 parent = fields[name]
1328 field = parent
1329 if not parent.pobj then
1330 if trace_fields then
1331 report_fields("using parent push %a",name)
1332 end
1333 makepushparent(parent,specification)
1334 end
1335 end
1336 if trace_fields then
1337 report_fields("using child push %a",name)
1338 end
1339 local fontsymbol = specification.fontsymbol
1340 local d = pdfdictionary {
1341 Subtype = pdf_widget,
1342 Parent = pdfreference(field.pobj),
1343 F = fieldplus(specification),
1344 OC = fieldlayer(specification),
1345 AA = fieldactions(specification),
1346 H = pdf_p,
1347 }
1348 if fontsymbol and fontsymbol ~= "" then
1349 specification.fontsymbol = todingbat(fontsymbol)
1350 specification.fontstyle = "symbol"
1351 specification.fontalternative = "dingbats"
1352 d.DA = fieldsurrounding(specification)
1353 d.MK = fieldrendering(specification)
1354 else
1355 d.AP = fieldappearances(field)
1356 end
1357 return save_kid(parent,specification,d)
1358end
1359
1360function methods.push(name,specification)
1361 return makepushchild(name,enhance(specification,"PushButton"))
1362end
1363
1364local function makeradioparent(field,specification)
1365 specification = enhance(specification,"Radio,RadiosInUnison,Print,NoToggleToOff")
1366 local d = pdfdictionary {
1367 T = field.name,
1368 FT = pdf_btn,
1369
1370 Ff = fieldflag(specification),
1371
1372 V = fielddefault(field),
1373 }
1374 save_parent(field,specification,d)
1375end
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436local function makeradiochild(name,specification)
1437 local field, parent = clones[name], nil
1438 if field then
1439 field = radios[field.parent]
1440 parent = fields[field.parent]
1441 if not parent.pobj then
1442 if trace_fields then
1443 report_fields("forcing parent radio %a",parent.name)
1444 end
1445 makeradioparent(parent,parent)
1446 end
1447 else
1448 field = radios[name]
1449 if not field then
1450 report_fields("there is some problem with field %a",name)
1451 return nil
1452 end
1453 parent = fields[field.parent]
1454 if not parent.pobj then
1455 if trace_fields then
1456 report_fields("using parent radio %a",name)
1457 end
1458 makeradioparent(parent,parent)
1459 end
1460 end
1461 if trace_fields then
1462 report_fields("using child radio %a with values %a and default %a",name,field.values,field.default)
1463 end
1464 local fontsymbol = specification.fontsymbol
1465
1466 local d = pdfdictionary {
1467 Subtype = pdf_widget,
1468 Parent = pdfreference(parent.pobj),
1469 F = fieldplus(specification),
1470 OC = fieldlayer(specification),
1471 AA = fieldactions(specification),
1472 H = pdf_n,
1473 }
1474 if fontsymbol and fontsymbol ~= "" then
1475 specification.fontsymbol = todingbat(fontsymbol)
1476 specification.fontstyle = "symbol"
1477 specification.fontalternative = "dingbats"
1478 d.DA = fieldsurrounding(specification)
1479 d.MK = fieldrendering(specification)
1480 end
1481 local appearance, default, value = fieldstates_radio(field,name,fields[field.parent])
1482 d.AP = appearance
1483 d.AS = default
1484
1485d.BS = pdfdictionary { S = pdfconstant("I"), W = 1 }
1486 return save_kid(parent,specification,d,value)
1487end
1488
1489function methods.sub(name,specification)
1490 return makeradiochild(name,enhance(specification,"Radio,RadiosInUnison"))
1491end
1492 |