mtx-pdf.lua /size: 39 Kb    last modification: 2025-02-21 11:03
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-- Looks like we can get (even from programs using the adobe library):
103--
104-- 1 0 obj << /Metadata 3 0 R >> endobj
105-- 3 0 obj << /Subtype /XML /Type /Metadata /Length 9104 >> stream ...
106-- 2 0 obj << /Metadata 4 0 R /Subtype /XML /Type /Metadata >> endobj
107-- 4 0 obj << /Length 9104 >> stream ...
108
109do
110
111    -- This is a goodie. Checking came up in the ctx chat (HHR) in relation
112    -- to conversion and newer (lossless jpeg) file formats (not in pdf) but
113    -- that could be dealt with later (at least get the size and resolution
114    -- info).
115
116    -- todo : svg
117    -- todo : pdf (similar table)
118    -- todo : jbig jbig2 jb2 (if needed)
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     -- if details then
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     -- end
251
252     -- if details then
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    --  end
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                            -- F Ff FT Opt T | AA OC (rest follows)
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-- todo: fromunicode16
551
552local function getunicodes(font)
553    local cid = font.ToUnicode
554    if cid then
555        cid = cid()
556        local counts  = { }
557        local indices = { }
558     -- for s in gmatch(cid,"begincodespacerange%s*(.-)%s*endcodespacerange") do
559     --     for a, b in gmatch(s,"<([^>]+)>%s+<([^>]+)>") do
560     --         print(a,b)
561     --     end
562     -- end
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         -- local freqs    = { }
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                     -- freqs[i] = format("U+%05X  %s  %s",k,counts[k] > 1 and "+" or " ", c)
615                    else
616                        chars[i] = k == 32 and "SPACE" or format("U+%03X",k)
617                     -- freqs[i] = format("U+%05X  %s  --",k,counts[k] > 1 and "+" or " ")
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             -- freqs    = freqs,
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--             for s, f in sortedhash(list) do
672--                 local k = f[1]
673--                 local v = f[2]
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 } -- no need for a dict
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--                 report("%wtitle %a, unknown",2*depth,title)
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 -- can be pdfdoc or unicode
908--                     local title = current("Title")  -- can be pdfdoc or unicode
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        -- When I'm really bored, have a stack of new cd's or it's depressing
949        -- weather I might be willing to waste time on this.
950
951--         local tree = pdffile.Catalog.StructTreeRoot
952--         if tree then
953--             local kids = tree.K
954--         end
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-- scripts.pdf.info("e:/tmp/oeps.pdf")
1050-- scripts.pdf.metadata("e:/tmp/oeps.pdf")
1051-- scripts.pdf.fonts("e:/tmp/oeps.pdf")
1052-- scripts.pdf.linearize("e:/tmp/oeps.pdf")
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-- a variant on an experiment by hartmut
1099
1100--~ function downloadlinks(filename)
1101--~     local document = lpdf.epdf.load(filename)
1102--~     if document then
1103--~         local pages = document.pages
1104--~         for p = 1,#pages do
1105--~             local annotations = pages[p].Annots
1106--~             if annotations then
1107--~                 for a=1,#annotations do
1108--~                     local annotation = annotations[a]
1109--~                     local uri = annotation.Subtype == "Link" and annotation.A and annotation.A.URI
1110--~                     if uri and string.find(uri,"^http") then
1111--~                         os.execute("wget " .. uri)
1112--~                     end
1113--~                 end
1114--~             end
1115--~         end
1116--~     end
1117--~ end
1118