1if not modules then modules = { } end modules ['mtx-pdf'] = {
2 version = 1.001,
3 comment = "companion to mtxrun.lua",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9local tonumber = tonumber
10local format, gmatch, gsub, match, find = string.format, string.gmatch, string.gsub, string.match, string.find
11local utfchar = utf.char
12local concat, insert, swapped = table.concat, table.insert, table.swapped
13local setmetatableindex, sortedhash, sortedkeys = table.setmetatableindex, table.sortedhash, table.sortedkeys
14
15local helpinfo = [[
16<?xml version="1.0"?>
17<application>
18 <metadata>
19 <entry name="name">mtx-pdf</entry>
20 <entry name="detail">ConTeXt PDF Helpers</entry>
21 <entry name="version">0.10</entry>
22 </metadata>
23 <flags>
24 <category name="basic">
25 <subcategory>
26 <flag name="info"><short>show some info about the given file</short></flag>
27 <flag name="metadata"><short>show metadata xml blob</short></flag>
28 <flag name="formdata"><short>show formdata</short></flag>
29 <flag name="pretty"><short>replace newlines in metadata</short></flag>
30 <flag name="fonts"><short>show used fonts (<ref name="detail"/>)</short></flag>
31 <flag name="object"><short>show object</short></flag>
32 <flag name="links"><short>show links</short></flag>
33 <flag name="highlights"><short>show highlights</short></flag>
34 <flag name="comments"><short>show comments</short></flag>
35 <flag name="sign"><short>sign document (assumes signature template)</short></flag>
36 <flag name="verify"><short>verify document</short></flag>
37 <flag name="detail"><short>print detail to the console</short></flag>
38 <flag name="userdata"><short>print userdata to the console</short></flag>
39 </subcategory>
40 <subcategory>
41 <example><command>mtxrun --script pdf --info foo.pdf</command></example>
42 <example><command>mtxrun --script pdf --metadata foo.pdf</command></example>
43 <example><command>mtxrun --script pdf --metadata --pretty foo.pdf</command></example>
44 <example><command>mtxrun --script pdf --stream=4 foo.pdf</command></example>
45 <example><command>mtxrun --script pdf --sign --certificate=somesign.pem --password=test --uselibrary somefile</command></example>
46 <example><command>mtxrun --script pdf --verify --certificate=somesign.pem --password=test --uselibrary somefile</command></example>
47 <example><command>mtxrun --script pdf --detail=nofpages somefile</command></example>
48 <example><command>mtxrun --script pdf --userdata=keylist [--format=lua|json|lines] somefile</command></example>
49 </subcategory>
50 </category>
51 </flags>
52</application>
53]]
54
55local application = logs.application {
56 name = "mtx-pdf",
57 banner = "ConTeXt PDF Helpers 0.10",
58 helpinfo = helpinfo,
59}
60
61local report = application.report
62
63if not pdfe then
64 dofile(resolvers.findfile("lpdf-epd.lua","tex"))
65elseif CONTEXTLMTXMODE then
66 dofile(resolvers.findfile("util-dim.lua","tex"))
67 dofile(resolvers.findfile("lpdf-ini.lmt","tex"))
68 dofile(resolvers.findfile("lpdf-pde.lmt","tex"))
69 dofile(resolvers.findfile("lpdf-sig.lmt","tex"))
70else
71 dofile(resolvers.findfile("lpdf-pde.lua","tex"))
72end
73
74scripts = scripts or { }
75scripts.pdf = scripts.pdf or { }
76
77local details = environment.argument("detail") or environment.argument("details")
78
79local function loadpdffile(filename)
80 if not filename or filename == "" then
81 report("no filename given")
82 elseif not lfs.isfile(filename) then
83 report("unknown file %a",filename)
84 else
85 local ownerpassword = environment.arguments.ownerpassword
86 local userpassword = environment.arguments.userpassword
87 if not ownerpassword then
88 ownerpassword = userpassword
89 end
90 if not userpassword then
91 userpassword = ownerpassword
92 end
93 local pdffile = lpdf.epdf.load(filename,userpassword,ownerpassword)
94 if pdffile then
95 return pdffile
96 else
97 report("no valid pdf file %a",filename)
98 end
99 end
100end
101
102
103
104
105
106
107
108
109do
110
111
112
113
114
115
116
117
118
119
120 local graphics = nil
121
122 function scripts.pdf.identify(filename)
123 if graphics == nil then
124 graphics = require("grph-img.lua") or false
125 end
126 if graphics then
127 local info = graphics.identify(filename)
128 if info and info.length then
129 report("filename : %s",filename)
130 report("filetype : %s",info.filetype)
131 report("filesize : %s",info.length)
132 report("colordepth : %s",info.colordepth)
133 report("colorspace : %s",graphics.colorspaces[info.colorspace])
134 report("size : %s %s",info.xsize,info.ysize)
135 report("resolution : %s %s",info.xres,info.yres)
136 report("boundingbox : 0 0 %s %s (bp)",graphics.bpsize(info))
137 end
138 end
139 end
140
141end
142
143function scripts.pdf.info(filename)
144 local pdffile = loadpdffile(filename)
145 if pdffile then
146 local catalog = pdffile.Catalog
147 local info = pdffile.Info
148 local pages = pdffile.pages
149 local nofpages = pdffile.nofpages
150 local metadata = catalog.Metadata
151
152 local unset = "<unset>"
153
154 local title = info.Title
155 local creator = info.Creator
156 local producer = info.Producer
157 local author = info.Author
158 local creationdate = info.CreationDate
159 local modificationdate = info.ModDate
160
161 if metadata then
162 metadata = metadata()
163 if metadata then
164 local m = xml.convert(metadata)
165 title = title or xml.text(m,"Description/title/**/*")
166 author = author or xml.text(m,"Description/author/**/*")
167 creator = creator or xml.text(m,"Description/CreatorTool")
168 producer = producer or xml.text(m,"Description/Producer")
169 creationdate = creationdate or xml.text(m,"Description/CreateDate")
170 modificationdate = modificationdate or xml.text(m,"Description/ModifyDate")
171 end
172 end
173
174 local function checked(str)
175 return str and str ~= "" and str or unset
176 end
177
178 report("%-17s > %s","filename", filename)
179 report("%-17s > %s","pdf version", catalog.Version or unset)
180 report("%-17s > %s","major version", pdffile.majorversion or unset)
181 report("%-17s > %s","minor version", pdffile.minorversion or unset)
182 report("%-17s > %s","number of pages", nofpages or 0)
183 report("%-17s > %s","title", checked(title))
184 report("%-17s > %s","creator", checked(creator))
185 report("%-17s > %s","producer", checked(producer))
186 report("%-17s > %s","author", checked(author))
187 report("%-17s > %s","creation date", checked(creationdate))
188 report("%-17s > %s","modification date", checked(modificationdate))
189
190 local function checked(what,str)
191 if str ~= nil and str ~= "" then
192 report("%-17s > %S",what,str)
193 end
194 end
195
196 local viewerpreferences = catalog.ViewerPreferences
197 local pagelayout = catalog.PageLayout
198 local pagemode = catalog.PageMode
199 local encrypted = pdffile.encrypted
200 local permissions = pdffile.permissions
201
202 checked("duplex", viewerpreferences and viewerpreferences.Duplex)
203 checked("page layout", pagelayout)
204 checked("page mode", pagemode)
205 checked("encrypted", encrypted and "yes" or nil)
206
207 if permissions then
208 report("%-17s > % t","permissions",table.sortedkeys(permissions))
209 end
210
211 local function somebox(what)
212 local box = string.lower(what)
213 local width, height, start
214 for i=1, nofpages do
215 local page = pages[i]
216 local bbox = page[what] or page.MediaBox or { 0, 0, 0, 0 }
217 local w, h = bbox[4]-bbox[2],bbox[3]-bbox[1]
218 if w ~= width or h ~= height then
219 if start then
220 report("%-17s > pages: %s-%s, width: %s, height: %s",box,start,i-1,width,height)
221 end
222 width, height, start = w, h, i
223 end
224 end
225 report("%-17s > pages: %s-%s, width: %s, height: %s",box,start,nofpages,width,height)
226 end
227
228 if details then
229 somebox("MediaBox")
230 somebox("ArtBox")
231 somebox("BleedBox")
232 somebox("CropBox")
233 somebox("TrimBox")
234 else
235 somebox("CropBox")
236 end
237
238
239 local annotations = 0
240 for i=1,nofpages do
241 local page = pages[i]
242 local a = page.Annots
243 if a then
244 annotations = annotations + #a
245 end
246 end
247 if annotations > 0 then
248 report("%-17s > %s", "annotations",annotations)
249 end
250
251
252
253 local d = pdffile.destinations
254 local k = d and sortedkeys(d)
255 if k and #k > 0 then
256 report("%-17s > %s", "destinations",#k)
257 end
258 local d = pdffile.javascripts
259 local k = d and sortedkeys(d)
260 if k and #k > 0 then
261 report("%-17s > %s", "javascripts",#k)
262 end
263 local d = pdffile.widgets
264 if d and #d > 0 then
265 report("%-17s > %s", "widgets",#d)
266 end
267 local d = pdffile.embeddedfiles
268 local k = d and sortedkeys(d)
269 if k and #k > 0 then
270 report("%-17s > %s", "embeddedfiles",#k)
271 end
272
273
274 end
275end
276
277function scripts.pdf.detail(filename,detail)
278 if detail == "pages" or detail == "nofpages" then
279 local pdffile = loadpdffile(filename)
280 print(pdffile and pdffile.nofpages or 0)
281 end
282end
283
284local function flagstoset(flag,flags)
285 local t = { }
286 if flags then
287 for k, v in next, flags do
288 if (flag & v) ~= 0 then
289 t[k] = true
290 end
291 end
292 end
293 return t
294end
295
296function scripts.pdf.formdata(filename,save)
297 local pdffile = loadpdffile(filename)
298 if pdffile then
299 local widgets = pdffile.widgets
300 if widgets then
301 local results = { { "type", "name", "value" } }
302 for i=1,#widgets do
303 local annotation = widgets[i]
304 local parent = annotation.Parent or { }
305 local name = annotation.T or parent.T
306 local what = annotation.FT or parent.FT
307 if name and what then
308 local value = annotation.V and tostring(annotation.V) or ""
309 if value and value ~= "" then
310 local wflags = flagstoset(annotation.Ff or parent.Ff or 0, widgetflags)
311 if what == "Tx" then
312 if wflags.MultiLine then
313 wflags.MultiLine = nil
314 what = "text"
315 else
316 what = "line"
317 end
318 local default = annotation.V or ""
319 elseif what == "Btn" then
320 if wflags.Radio or wflags.RadiosInUnison then
321 what = "radio"
322 elseif wflags.PushButton then
323 what = "push"
324 else
325 what = "check"
326 end
327 elseif what == "Ch" then
328
329 if wflags.PopUp then
330 wflags.PopUp = nil
331 if wflags.Edit then
332 what = "combo"
333 else
334 what = "popup"
335 end
336 else
337 what = "choice"
338 end
339 elseif what == "Sig" then
340 what = "signature"
341 else
342 what = nil
343 end
344 if what then
345 results[#results+1] = { what, name, value }
346 end
347 end
348 end
349 end
350 if save then
351 local values = { }
352 for i=2,#results do
353 local result= results[i]
354 values[#values+1] = {
355 type = result[1],
356 name = result[2],
357 value = result[3],
358 }
359 end
360 local data = {
361 filename = filename,
362 values = values,
363 }
364 local name = file.nameonly(filename) .. "-formdata"
365 if save == "json" then
366 name = file.addsuffix(name,"json")
367 io.savedata(name,utilities.json.tojson(data))
368 elseif save then
369 name = file.addsuffix(name,"lua")
370 table.save(name,data)
371 end
372 report("")
373 report("%i widgets found, %i values saved in %a",#widgets,#results-1,name)
374 report("")
375 end
376 utilities.formatters.formatcolumns(results)
377 report(results[1])
378 report("")
379 for i=2,#results do
380 report(results[i])
381 end
382 report("")
383 end
384 end
385end
386
387function scripts.pdf.signature(filename,save)
388 local pdffile = loadpdffile(filename)
389 if pdffile then
390 local widgets = pdffile.widgets
391 if widgets then
392 for i=1,#widgets do
393 local annotation = widgets[i]
394 local parent = annotation.Parent or { }
395 local name = annotation.T or parent.T
396 local what = annotation.FT or parent.FT
397 if what == "Sig" then
398 local value = annotation.V
399 if value then
400 local contents = tostring(value.Contents) or ""
401 report("")
402 if save then
403 local name = file.nameonly(filename) .. "-signature.bin"
404 report("signature saved in %a",name)
405 io.savedata(name,string.tobytes(contents))
406 else
407 report("signature: %s",contents)
408 end
409 report("")
410 return
411 end
412 end
413 end
414 end
415 report("there is no signature")
416 end
417end
418
419function scripts.pdf.sign(filename,save)
420 local pdffile = file.addsuffix(filename,"pdf")
421 if not lfs.isfile(pdffile) then
422 report("invalid pdf file %a",pdffile)
423 return
424 end
425 local certificate = environment.argument("certificate")
426 local password = environment.argument("password")
427 if type(certificate) ~= "string" or type(password) ~= "string" then
428 report("provide --certificate and --password")
429 return
430 end
431 lpdf.sign {
432 filename = pdffile,
433 certificate = certificate,
434 password = password,
435 purge = environment.argument("purge"),
436 uselibrary = environment.argument("uselibrary"),
437 }
438end
439
440function scripts.pdf.verify(filename,save)
441 local pdffile = file.addsuffix(filename,"pdf")
442 if not lfs.isfile(pdffile) then
443 report("invalid pdf file %a",pdffile)
444 return
445 end
446 local certificate = environment.argument("certificate")
447 local password = environment.argument("password")
448 if type(certificate) ~= "string" or type(password) ~= "string" then
449 report("provide --certificate and --password")
450 return
451 end
452 lpdf.verify {
453 filename = pdffile,
454 certificate = certificate,
455 password = password,
456 uselibrary = environment.argument("uselibrary"),
457 }
458end
459
460function scripts.pdf.metadata(filename,pretty)
461 local pdffile = loadpdffile(filename)
462 if pdffile then
463 local catalog = pdffile.Catalog
464 local metadata = catalog.Metadata
465 if metadata then
466 metadata = metadata()
467 if pretty then
468 metadata = gsub(metadata,"\r","\n")
469 end
470 report("metadata > \n\n%s\n",metadata)
471 else
472 report("no metadata")
473 end
474 end
475end
476
477local expanded = lpdf.epdf.expanded
478
479function scripts.pdf.userdata(filename,name,format)
480 local pdffile = loadpdffile(filename)
481 if pdffile then
482 local catalog = pdffile.Catalog
483 local userdata = catalog.LMTX_Userdata
484 if userdata then
485 if type(name) == "string" then
486 local t = { }
487 if type(format) == "string" then
488 for s in gmatch(name,"([^,]+)") do
489 t[s] = userdata[s]
490 end
491 if format == "lua" then
492 print(table.serialize(t,"userdata"))
493 elseif format == "json" then
494 print(utilities.json.tojson(t))
495 else
496 for k, v in sortedhash(t) do
497 print(k .. "=" .. v)
498 end
499 end
500 else
501 for s in gmatch(name,"([^,]+)") do
502 t[#t+1] = userdata[s]
503 end
504 print(concat(t," "))
505 end
506 else
507 for k, v in expanded(userdata) do
508 if k ~= "Type" then
509 report("%s : %s",k,v)
510 end
511 end
512 end
513 else
514 report("no userdata")
515 end
516 end
517end
518
519local function getfonts(pdffile)
520 local usedfonts = { }
521
522 local function collect(where,tag)
523 local resources = where.Resources
524 if resources then
525 local fontlist = resources.Font
526 if fontlist then
527 for k, v in expanded(fontlist) do
528 usedfonts[tag and (tag .. "." .. k) or k] = v
529 if v.Subtype == "Type3" then
530 collect(v,tag and (tag .. "." .. k) or k)
531 end
532 end
533 end
534 local objects = resources.XObject
535 if objects then
536 for k, v in expanded(objects) do
537 collect(v,tag and (tag .. "." .. k) or k)
538 end
539 end
540 end
541 end
542
543 for i=1,pdffile.nofpages do
544 collect(pdffile.pages[i])
545 end
546
547 return usedfonts
548end
549
550
551
552local function getunicodes(font)
553 local cid = font.ToUnicode
554 if cid then
555 cid = cid()
556 local counts = { }
557 local indices = { }
558
559
560
561
562
563 setmetatableindex(counts, function(t,k) t[k] = 0 return 0 end)
564 for s in gmatch(cid,"beginbfrange%s*(.-)%s*endbfrange") do
565 for first, last, offset in gmatch(s,"<([^>]+)>%s+<([^>]+)>%s+<([^>]+)>") do
566 local first = tonumber(first,16)
567 local last = tonumber(last,16)
568 local offset = tonumber(offset,16)
569 offset = offset - first
570 for i=first,last do
571 local c = i + offset
572 counts[c] = counts[c] + 1
573 indices[i] = true
574 end
575 end
576 end
577 for s in gmatch(cid,"beginbfchar%s*(.-)%s*endbfchar") do
578 for old, new in gmatch(s,"<([^>]+)>%s+<([^>]+)>") do
579 indices[tonumber(old,16)] = true
580 for n in gmatch(new,"....") do
581 local c = tonumber(n,16)
582 counts[c] = counts[c] + 1
583 end
584 end
585 end
586 return counts, indices
587 end
588end
589
590function scripts.pdf.fonts(filename)
591 local pdffile = loadpdffile(filename)
592 if pdffile then
593 local usedfonts = getfonts(pdffile)
594 local found = { }
595 local common = table.setmetatableindex("table")
596 for k, v in sortedhash(usedfonts) do
597 local basefont = v.BaseFont
598 local encoding = v.Encoding
599 local subtype = v.Subtype
600 local unicode = v.ToUnicode
601 local counts,
602 indices = getunicodes(v)
603 local codes = { }
604 local chars = { }
605
606 local names = { }
607 if counts then
608 codes = sortedkeys(counts)
609 for i=1,#codes do
610 local k = codes[i]
611 if k > 32 then
612 local c = utfchar(k)
613 chars[i] = c
614
615 else
616 chars[i] = k == 32 and "SPACE" or format("U+%03X",k)
617
618 end
619 end
620 if basefont and unicode then
621 local b = gsub(basefont,"^.*%+","")
622 local c = common[b]
623 for k in next, indices do
624 c[k] = true
625 end
626 end
627 for i=1,#codes do
628 codes[i] = format("U+%05X",codes[i])
629 end
630 end
631 local d = encoding and encoding.Differences
632 if d then
633 for i=1,#d do
634 local di = d[i]
635 if type(di) == "string" then
636 names[#names+1] = di
637 end
638 end
639 end
640 if not basefont then
641 local fontdescriptor = v.FontDescriptor
642 if fontdescriptor then
643 basefont = fontdescriptor.FontName
644 end
645 end
646 found[k] = {
647 basefont = basefont or "no basefont",
648 encoding = (d and "custom n=" .. #d) or "no encoding",
649 subtype = subtype or "no subtype",
650 unicode = unicode and "unicode" or "no vector",
651 chars = chars,
652 codes = codes,
653
654 names = names,
655 }
656 end
657
658 local haschar = false
659
660 local list = { }
661 for k, v in next, found do
662 local s = string.gsub(k,"(%d+)",function(s) return format("%05i",tonumber(s)) end)
663 list[s] = { k, v }
664 if #v.chars > 0 then
665 haschar = true
666 end
667 end
668
669 if details then
670 for k, v in sortedhash(found) do
671
672
673
674 report("id : %s", k)
675 report("basefont : %s", v.basefont)
676 report("encoding : % t", v.names)
677 report("subtype : %s", v.subtype)
678 report("unicode : %s", v.unicode)
679 if #v.chars > 0 then
680 report("characters : % t", v.chars)
681 end
682 if #v.codes > 0 then
683 report("codepoints : % t", v.codes)
684 end
685 report("")
686 end
687 for k, v in sortedhash(common) do
688 report("basefont : %s",k)
689 report("indices : % t", sortedkeys(v))
690 report("")
691 end
692 else
693 local results = { { "id", "basefont", "encoding", "subtype", "unicode", haschar and "characters" or nil } }
694 local shared = { }
695 for s, f in sortedhash(list) do
696 local k = f[1]
697 local v = f[2]
698 local basefont = v.basefont
699 local characters = shared[basefont] or (haschar and concat(v.chars," ")) or nil
700 results[#results+1] = { k, v.basefont, v.encoding, v.subtype, v.unicode, characters }
701 if not shared[basefont] then
702 shared[basefont] = "shared with " .. k
703 end
704 end
705 utilities.formatters.formatcolumns(results)
706 report(results[1])
707 report("")
708 for i=2,#results do
709 report(results[i])
710 end
711 report("")
712 end
713 end
714end
715
716function scripts.pdf.object(filename,n)
717 if n then
718 local pdffile = loadpdffile(filename)
719 if pdffile then
720 print(lpdf.epdf.verboseobject(pdffile,n) or "no object with number " .. n)
721 end
722 end
723end
724
725function scripts.pdf.links(filename,asked)
726 local pdffile = loadpdffile(filename)
727 if pdffile then
728
729 local pages = pdffile.pages
730 local nofpages = pdffile.nofpages
731
732 if asked and (asked < 1 or asked > nofpages) then
733 report("")
734 report("no page %i, last page %i",asked,nofpages)
735 report("")
736 return
737 end
738
739 local reverse = swapped(pages)
740
741 local function banner(pagenumber)
742 report("")
743 report("annotations @ page %i",pagenumber)
744 report("")
745 end
746
747 local function show(pagenumber)
748 local page = pages[pagenumber]
749 local annots = page.Annots
750 if annots then
751 local done = false
752 for i=1,#annots do
753 local annotation = annots[i]
754 local a = annotation.A
755 if not a then
756 local d = annotation.Dest
757 if d then
758 a = { S = "GoTo", D = d }
759 end
760 end
761 if a then
762 local S = a.S
763 if S == "GoTo" then
764 local D = a.D
765 local t = type(D)
766 if t == "table" then
767 local D1 = D[1]
768 local R1 = reverse[D1]
769 if not done then
770 banner(pagenumber)
771 done = true
772 end
773 if tonumber(R1) then
774 report("intern, page %i",R1 or 0)
775 else
776 report("intern, name %s",tostring(D1))
777 end
778 elseif t == "string" then
779 report("intern, name %s",D)
780 end
781 elseif S == "GoToR" then
782 local D = a.D
783 if D then
784 local F = a.F
785 if F then
786 local D1 = D[1]
787 if not done then
788 banner(pagenumber)
789 done = true
790 end
791 if tonumber(D1) then
792 report("extern, page %i, file %s",D1 + 1,F)
793 else
794 report("extern, page %i, file %s, name %s",0,F,D[1])
795 end
796 end
797 end
798 elseif S == "URI" then
799 local URI = a.URI
800 if URI then
801 report("extern, uri %a",URI)
802 end
803 end
804 end
805 end
806 end
807 end
808
809 if asked then
810 show(asked)
811 else
812 for pagenumber=1,nofpages do
813 show(pagenumber)
814 end
815 end
816
817 local destinations = pdffile.destinations
818 if destinations then
819 if asked then
820 report("")
821 report("destinations to page %i",asked)
822 report("")
823 for k, v in sortedhash(destinations) do
824 local D = v.D
825 if D then
826 local p = reverse[D[1]] or 0
827 if p == asked then
828 report(k)
829 end
830 end
831 end
832 else
833 report("")
834 report("destinations")
835 report("")
836 local list = setmetatableindex("table")
837 for k, v in sortedhash(destinations) do
838 local D = v.D
839 if D then
840 local p = reverse[D[1]]
841 report("tag %s, page %i",k,p)
842 insert(list[p],k)
843 end
844 end
845 for k, v in sortedhash(list) do
846 report("")
847 report("page %i, names : [ % | t ]",k,v)
848 end
849 end
850 end
851 end
852end
853
854function scripts.pdf.outlines(filename)
855
856 local pdffile = loadpdffile(filename)
857 if pdffile then
858
859 local outlines = pdffile.Catalog.Outlines
860 local destinations = pdffile.destinations
861 local pages = pdffile.pages
862
863 local function showdestination(current,depth,title)
864 local action = current.A
865 if type(title) == "table" then
866 title = lpdf.frombytes(title[2],title[3])
867 end
868 if action then
869 local subtype = action.S
870 if subtype == "GoTo" then
871 local destination = action.D
872 local kind = type(destination)
873 if kind == "string" then
874 report("%wtitle %a, name %a",2*depth,title,destination)
875 elseif kind == "table" then
876 local pageref = #destination
877 if pageref then
878 local pagedata = pages[pageref]
879 if pagedata then
880 report("%wtitle %a, page %a",2*depth,title,pagedata.number)
881 end
882 end
883 end
884 end
885 else
886 local destination = current.Dest
887 if destination then
888 if type(destination) == "string" then
889 report("%wtitle %a, name %a",2*depth,title,destination)
890 else
891 local pagedata = destination and destination[1]
892 if pagedata and pagedata.Type == "Page" then
893 report("%wtitle %a, page %a",2*depth,title,pagedata.number)
894 end
895 end
896 end
897 end
898 if title then
899
900 end
901 end
902
903 if outlines then
904
905 local function traverse(current,depth)
906 while current do
907 local title = current.Title
908
909 if title then
910 showdestination(current,depth,title)
911 end
912 local first = current.First
913 if first then
914 local current = first
915 while current do
916 local title = current.Title
917 if title then
918 showdestination(current,depth,title)
919 end
920 traverse(current.First,depth+1)
921 current = current.Next
922 end
923 end
924 current = current.Next
925 end
926 end
927 report("")
928 report("outlines")
929 report("")
930 traverse(outlines,0)
931 report("")
932
933 else
934 report("no outlines in %a",filename)
935 end
936
937 end
938
939end
940
941function scripts.pdf.structure(filename)
942 local pdffile = loadpdffile(filename)
943 if pdffile then
944
945 local pages = pdffile.pages
946 local nofpages = pdffile.nofpages
947
948
949
950
951
952
953
954
955
956 end
957end
958
959local function whatever(filename,asked,what,subtype)
960 local pdffile = loadpdffile(filename)
961 if pdffile then
962
963 local pages = pdffile.pages
964 local nofpages = pdffile.nofpages
965
966 if asked and (asked < 1 or asked > nofpages) then
967 report("")
968 report("no page %i, last page %i",asked,nofpages)
969 report("")
970 return
971 end
972
973 local function banner(pagenumber)
974 report("")
975 report("%s @ page %i",what,pagenumber)
976 report("")
977 end
978
979 local function show(pagenumber)
980 local page = pages[pagenumber]
981 local annots = page.Annots
982 if annots then
983 local done = false
984 for i=1,#annots do
985 local annotation = annots[i]
986 local S = annotation.Subtype
987 if S == subtype then
988 local author = annotation.T or "unknown"
989 local contents = annotation.Contents or "empty"
990 local rect = annotation.Rect()
991 local name = annotation.NM or "unset"
992 if not done then
993 banner(pagenumber)
994 done = true
995 end
996 local x = rect[1]
997 local y = rect[2]
998 local w = rect[3] - rect[1]
999 local h = rect[4] - rect[2]
1000 report("position (%N,%N), dimensions (%N,%N), name %a, author %a, contents %a",
1001 x,y,w,h,name,author,contents)
1002 end
1003 end
1004 end
1005 end
1006
1007 if asked then
1008 show(asked)
1009 else
1010 for pagenumber=1,nofpages do
1011 show(pagenumber)
1012 end
1013 end
1014
1015 end
1016end
1017
1018
1019function scripts.pdf.highlights(filename,asked)
1020 whatever(filename,asked,"highlights","Highlight")
1021end
1022
1023function scripts.pdf.comments(filename,asked)
1024 whatever(filename,asked,"comments","Text")
1025end
1026
1027local template = [[
1028\startTEXpage
1029\externalfigure[%s][page=%i]
1030\stopTEXpage
1031]]
1032
1033function scripts.pdf.split(filename)
1034 local pdffile = loadpdffile(filename)
1035 if pdffile then
1036 local pages = pdffile.nofpages
1037 local name = file.nameonly(filename)
1038 local texname = "mtx-pdf-temp.tex"
1039 for page=1,pages do
1040 local pdfname = file.addsuffix(name.."-"..page,"pdf")
1041 local command = format("context --batch --nostats --silent --once %s --result=%s --purgeall",texname,pdfname)
1042 io.savedata(texname,format(template,filename,page))
1043 os.execute(command)
1044 end
1045 os.remove(texname)
1046 end
1047end
1048
1049
1050
1051
1052
1053
1054local filename = environment.files[1] or ""
1055
1056if filename == "" then
1057 application.help()
1058elseif environment.argument("info") then
1059 scripts.pdf.info(filename)
1060elseif environment.argument("identify") then
1061 scripts.pdf.identify(filename)
1062elseif environment.argument("metadata") then
1063 scripts.pdf.metadata(filename,environment.argument("pretty"))
1064elseif environment.argument("formdata") then
1065 scripts.pdf.formdata(filename,environment.argument("save"))
1066elseif environment.argument("userdata") then
1067 scripts.pdf.userdata(filename,environment.argument("userdata"),environment.argument("format"))
1068elseif environment.argument("fonts") then
1069 scripts.pdf.fonts(filename)
1070elseif environment.argument("object") then
1071 scripts.pdf.object(filename,tonumber(environment.argument("object")))
1072elseif environment.argument("links") then
1073 scripts.pdf.links(filename,tonumber(environment.argument("page")))
1074elseif environment.argument("outlines") then
1075 scripts.pdf.outlines(filename)
1076elseif environment.argument("structure") then
1077 scripts.pdf.structure(filename)
1078elseif environment.argument("highlights") then
1079 scripts.pdf.highlights(filename,tonumber(environment.argument("page")))
1080elseif environment.argument("comments") then
1081 scripts.pdf.comments(filename,tonumber(environment.argument("page")))
1082elseif environment.argument("signature") then
1083 scripts.pdf.signature(filename,environment.argument("save"))
1084elseif environment.argument("sign") then
1085 scripts.pdf.sign(filename)
1086elseif environment.argument("detail") then
1087 scripts.pdf.detail(filename,environment.argument("detail"))
1088elseif environment.argument("verify") then
1089 scripts.pdf.verify(filename)
1090elseif environment.argument("split") then
1091 scripts.pdf.split(filename)
1092elseif environment.argument("exporthelp") then
1093 application.export(environment.argument("exporthelp"),filename)
1094else
1095 application.help()
1096end
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118 |