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