1if not modules then modules = { } end modules ['grph-inc'] = {
2 version = 1.001,
3 comment = "companion to grph-inc.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
45
46
47
48local tonumber, tostring, next, unpack = tonumber, tostring, next, unpack
49local format, lower, find, match, gsub = string.format, string.lower, string.find, string.match, string.gsub
50local longtostring = string.longtostring
51local contains = table.contains
52local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys
53local concat, insert, remove = table.concat, table.insert, table.remove
54local todimen = string.todimen
55local collapsepath = file.collapsepath
56local formatters = string.formatters
57local odd = math.odd
58local isfile, isdir, modificationtime = lfs.isfile, lfs.isdir, lfs.modification
59
60local P, R, S, Cc, C, Cs, Ct, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.Cc, lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.match
61
62local settings_to_array = utilities.parsers.settings_to_array
63local settings_to_hash = utilities.parsers.settings_to_hash
64local allocate = utilities.storage.allocate
65local setmetatableindex = table.setmetatableindex
66local replacetemplate = utilities.templates.replace
67
68local bpfactor = number.dimenfactors.bp
69
70images = images or { }
71local images = images
72
73local hasscheme = url.hasscheme
74local urlhashed = url.hashed
75
76local resolveprefix = resolvers.resolve
77
78local texgetbox = tex.getbox
79local texsetbox = tex.setbox
80
81local hpack = nodes.hpack
82
83local new_latelua = nodes.pool.latelua
84local new_hlist = nodes.pool.hlist
85
86local context = context
87
88local implement = interfaces.implement
89local variables = interfaces.variables
90
91local codeinjections = backends.codeinjections
92local nodeinjections = backends.nodeinjections
93
94local trace_figures = false trackers.register ("graphics.locating", function(v) trace_figures = v end)
95local trace_bases = false trackers.register ("graphics.bases", function(v) trace_bases = v end)
96local trace_programs = false trackers.register ("graphics.programs", function(v) trace_programs = v end)
97local trace_conversion = false trackers.register ("graphics.conversion", function(v) trace_conversion = v end)
98local trace_inclusion = false trackers.register ("graphics.inclusion", function(v) trace_inclusion = v end)
99local trace_usage = false trackers.register ("graphics.usage", function(v) trace_usage = v end)
100
101local extra_check = false directives.register("graphics.extracheck", function(v) extra_check = v end)
102local auto_transform = true directives.register("graphics.autotransform", function(v) auto_transform = v end)
103
104local report = logs.reporter("graphics")
105local report_inclusion = logs.reporter("graphics","inclusion")
106
107local f_hash_part = formatters["%s->%s->%s->%s"]
108local f_hash_full = formatters["%s->%s->%s->%s->%s->%s->%s->%s"]
109
110local v_yes = variables.yes
111local v_global = variables["global"]
112local v_local = variables["local"]
113local v_default = variables.default
114local v_auto = variables.auto
115
116local maxdimen = tex.magicconstants.maxdimen
117
118local ctx_doscalefigure = context.doscalefigure
119local ctx_relocateexternalfigure = context.relocateexternalfigure
120local ctx_startfoundexternalfigure = context.startfoundexternalfigure
121local ctx_stopfoundexternalfigure = context.stopfoundexternalfigure
122local ctx_dosetfigureobject = context.dosetfigureobject
123local ctx_doboxfigureobject = context.doboxfigureobject
124
125
126
127function checkimage(figure)
128 if figure then
129 local width = figure.width or 0
130 local height = figure.height or 0
131 if width <= 0 or height <= 0 then
132 report_inclusion("image %a has bad dimensions (%p,%p), discarding",figure.filename or "?",width,height)
133 return false, "bad dimensions"
134 end
135
136
137 local changes = false
138 if height > width then
139 if height > maxdimen then
140 figure.height = maxdimen
141 figure.width = width * maxdimen/height
142 changed = true
143 end
144 elseif width > maxdimen then
145 figure.width = maxdimen
146 figure.height = height * maxdimen/width
147 changed = true
148 end
149 if changed then
150 report_inclusion("limiting natural dimensions of %a, old %p * %p, new %p * %p",
151 figure.filename,width,height,figure.width,figure.height)
152 end
153 if width >= maxdimen or height >= maxdimen then
154 report_inclusion("image %a is too large (%p,%p), discarding",
155 figure.filename,width,height)
156 return false, "dimensions too large"
157 end
158 return figure
159 end
160end
161
162
163
164
165local imagekeys = {
166
167 "width", "height", "depth", "bbox",
168 "colordepth", "colorspace",
169 "filename", "filepath", "visiblefilename",
170 "imagetype", "stream",
171 "index", "objnum",
172 "pagebox", "page", "pages",
173 "rotation", "transform",
174 "xsize", "ysize", "xres", "yres",
175}
176
177local imagesizes = {
178 art = true, bleed = true, crop = true,
179 media = true, none = true, trim = true,
180}
181
182local imagetypes = { [0] =
183 "none",
184 "pdf", "png", "jpg", "jp2", "jbig2",
185 "stream", "memstream",
186}
187
188imagetypes = table.swapped(imagetypes,imagetypes)
189
190images.keys = imagekeys
191images.types = imagetypes
192images.sizes = imagesizes
193
194local function createimage(specification)
195 return backends.codeinjections.newimage(specification)
196end
197
198local function copyimage(specification)
199 return backends.codeinjections.copyimage(specification)
200end
201
202local function scanimage(specification)
203 return backends.codeinjections.scanimage(specification)
204end
205
206local function embedimage(specification)
207
208 return backends.codeinjections.embedimage(specification)
209end
210
211local function wrapimage(specification)
212
213 return backends.codeinjections.wrapimage(specification)
214end
215
216images.create = createimage
217images.scan = scanimage
218images.copy = copyimage
219images.wrap = wrapimage
220images.embed = embedimage
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239local function imagetotable(imgtable)
240 if type(imgtable) == "table" then
241 return copy(imgtable)
242 end
243 local result = { }
244 for k=1,#imagekeys do
245 local key = imagekeys[k]
246 result[key] = imgtable[key]
247 end
248 return result
249end
250
251function images.serialize(i,...)
252 return table.serialize(imagetotable(i),...)
253end
254
255function images.print(i,...)
256 return table.print(imagetotable(i),...)
257end
258
259local function checkimagesize(size)
260 if size then
261 size = gsub(size,"box","")
262 return imagesizes[size] and size or "crop"
263 else
264 return "crop"
265 end
266end
267
268images.check = checkimage
269images.checksize = checkimagesize
270images.totable = imagetotable
271
272
273
274
275
276
277
278
279
280figures = figures or { }
281local figures = figures
282
283figures.images = images
284figures.boxnumber = figures.boxnumber or 0
285figures.defaultsearch = true
286figures.defaultwidth = 0
287figures.defaultheight = 0
288figures.defaultdepth = 0
289figures.nofprocessed = 0
290figures.nofmissing = 0
291figures.preferquality = true
292
293local figures_loaded = allocate() figures.loaded = figures_loaded
294local figures_used = allocate() figures.used = figures_used
295local figures_found = allocate() figures.found = figures_found
296local figures_suffixes = allocate() figures.suffixes = figures_suffixes
297local figures_patterns = allocate() figures.patterns = figures_patterns
298local figures_resources = allocate() figures.resources = figures_resources
299
300local existers = allocate() figures.existers = existers
301local checkers = allocate() figures.checkers = checkers
302local includers = allocate() figures.includers = includers
303local remappers = allocate() figures.remappers = remappers
304local converters = allocate() figures.converters = converters
305local identifiers = allocate() figures.identifiers = identifiers
306local programs = allocate() figures.programs = programs
307
308local defaultformat = "pdf"
309local defaultprefix = "m_k_i_v_"
310
311figures.localpaths = allocate {
312 ".", "..", "../.."
313}
314
315figures.cachepaths = allocate {
316 prefix = "",
317 path = ".",
318 subpath = ".",
319}
320
321local figure_paths = allocate(table.copy(figures.localpaths))
322figures.paths = figure_paths
323
324local figures_order = allocate {
325 "pdf", "mps", "jpg", "png", "jp2", "jbig", "svg", "eps", "tif", "gif", "mov", "buffer", "tex", "cld", "auto",
326}
327
328local figures_formats = allocate {
329 ["pdf"] = { list = { "pdf" } },
330 ["mps"] = { patterns = { "^mps$", "^%d+$" } },
331 ["jpg"] = { list = { "jpg", "jpeg" } },
332 ["png"] = { list = { "png" } },
333 ["jp2"] = { list = { "jp2" } },
334 ["jbig"] = { list = { "jbig", "jbig2", "jb2" } },
335 ["svg"] = { list = { "svg", "svgz" } },
336 ["eps"] = { list = { "eps", "ai" } },
337 ["gif"] = { list = { "gif" } },
338 ["tif"] = { list = { "tif", "tiff" } },
339 ["mov"] = { list = { "mov", "flv", "mp4" } },
340 ["buffer"] = { list = { "tmp", "buffer", "buf" } },
341 ["tex"] = { list = { "tex" } },
342 ["cld"] = { list = { "cld" } },
343 ["auto"] = { list = { "auto" } },
344}
345
346local figures_magics = allocate {
347 { format = "png", pattern = P("\137PNG\013\010\026\010") },
348 { format = "jpg", pattern = P("\255\216\255") },
349 { format = "jp2", pattern = P("\000\000\000\012\106\080\032\032\013\010"), },
350 { format = "gif", pattern = P("GIF") },
351 { format = "pdf", pattern = (1 - P("%PDF"))^0 * P("%PDF") },
352}
353
354local figures_native = allocate {
355 pdf = true,
356 jpg = true,
357 jp2 = true,
358 png = true,
359}
360
361figures.formats = figures_formats
362figures.magics = figures_magics
363figures.order = figures_order
364
365
366
367local okay = P("m_k_i_v_")
368
369local pattern = (R("az","AZ") * P(":"))^-1 * (
370 (okay + R("az","09") + S("_/") - P("_")^2)^1 * (P(".") * R("az")^1)^0 * P(-1) +
371 (okay + R("az","09") + S("-/") - P("-")^2)^1 * (P(".") * R("az")^1)^0 * P(-1) +
372 (okay + R("AZ","09") + S("_/") - P("_")^2)^1 * (P(".") * R("AZ")^1)^0 * P(-1) +
373 (okay + R("AZ","09") + S("-/") - P("-")^2)^1 * (P(".") * R("AZ")^1)^0 * P(-1)
374) * Cc(false) + Cc(true)
375
376function figures.badname(name)
377 if not name then
378
379 elseif not hasscheme(name) then
380 return lpegmatch(pattern,name)
381 else
382 return lpegmatch(pattern,file.basename(name))
383 end
384end
385
386logs.registerfinalactions(function()
387 local done = false
388 if trace_usage and figures.nofprocessed > 0 then
389 logs.startfilelogging(report,"names")
390 for _, data in sortedhash(figures_found) do
391 if done then
392 report()
393 else
394 done = true
395 end
396 report("asked : %s",data.askedname)
397 if data.found then
398 report("format : %s",data.format)
399 report("found : %s",data.foundname)
400 report("used : %s",data.fullname)
401 if data.badname then
402 report("comment : %s","bad name")
403 elseif data.comment then
404 report("comment : %s",data.comment)
405 end
406 else
407 report("comment : %s","not found")
408 end
409 end
410 logs.stopfilelogging()
411 end
412 if figures.nofmissing > 0 and logs.loggingerrors() then
413 logs.starterrorlogging(report,"missing figures")
414 for _, data in sortedhash(figures_found) do
415 report("%w%s",6,data.askedname)
416 end
417 logs.stoperrorlogging()
418 end
419end)
420
421
422
423function figures.setorder(list)
424 if type(list) == "string" then
425 list = settings_to_array(list)
426 end
427 if list and #list > 0 then
428 figures_order = allocate()
429 figures.order = figures_order
430 local done = { }
431 for i=1,#list do
432 local l = lower(list[i])
433 if figures_formats[l] and not done[l] then
434 figures_order[#figures_order+1] = l
435 done[l] = true
436 end
437 end
438 report_inclusion("lookup order % a",figures_order)
439 else
440
441 end
442end
443
444local function guessfromstring(str)
445 if str then
446 for i=1,#figures_magics do
447 local pattern = figures_magics[i]
448 if lpegmatch(pattern.pattern,str) then
449 local format = pattern.format
450 if trace_figures then
451 report_inclusion("file %a has format %a",filename,format)
452 end
453 return format
454 end
455 end
456 end
457end
458
459figures.guessfromstring = guessfromstring
460
461function figures.guess(filename)
462 local f = io.open(filename,'rb')
463 if f then
464 local str = f:read(100)
465 f:close()
466 if str then
467 return guessfromstring(str)
468 end
469 end
470end
471
472local function setlookups()
473 figures_suffixes = allocate()
474 figures_patterns = allocate()
475 for _, format in next, figures_order do
476 local data = figures_formats[format]
477 local list = data.list
478 if list then
479 for i=1,#list do
480 figures_suffixes[list[i]] = format
481 end
482 else
483 figures_suffixes[format] = format
484 end
485 local patterns = data.patterns
486 if patterns then
487 for i=1,#patterns do
488 figures_patterns[#figures_patterns+1] = { patterns[i], format }
489 end
490 end
491 end
492 figures.suffixes = figures_suffixes
493 figures.patterns = figures_patterns
494end
495
496setlookups()
497
498figures.setlookups = setlookups
499
500function figures.registerresource(t)
501 local n = #figures_resources + 1
502 figures_resources[n] = t
503 return n
504end
505
506local function register(tag,what,target)
507 local data = figures_formats[target]
508 if not data then
509 data = { }
510 figures_formats[target] = data
511 end
512 local d = data[tag]
513 if d and not contains(d,what) then
514 d[#d+1] = what
515 else
516 data[tag] = { what }
517 end
518 if not contains(figures_order,target) then
519 figures_order[#figures_order+1] = target
520 end
521 setlookups()
522end
523
524function figures.registersuffix (suffix, target) register('list',suffix,target) end
525function figures.registerpattern(pattern,target) register('pattern',pattern,target) end
526
527implement { name = "registerfiguresuffix", actions = register, arguments = { "'list'", "string", "string" } }
528implement { name = "registerfigurepattern", actions = register, arguments = { "'pattern'", "string", "string" } }
529
530local last_locationset = last_locationset or nil
531local last_pathlist = last_pathlist or nil
532
533function figures.setpaths(locationset,pathlist)
534 if last_locationset == locationset and last_pathlist == pathlist then
535
536 return
537 end
538 local t, h = figure_paths, settings_to_hash(locationset)
539 if last_locationset ~= locationset then
540
541 if h[v_local] then
542 t = table.fastcopy(figures.localpaths or { })
543 else
544 t = { }
545 end
546 figures.defaultsearch = h[v_default]
547 last_locationset = locationset
548 end
549 if h[v_global] then
550 local list = settings_to_array(pathlist)
551 for i=1,#list do
552 local s = list[i]
553 if not contains(t,s) then
554 t[#t+1] = s
555 end
556 end
557 end
558
559 if environment.arguments.path then
560 table.insert(t,1,environment.arguments.path)
561 end
562
563 figure_paths = t
564 last_pathlist = pathlist
565 figures.paths = figure_paths
566 if trace_figures then
567 report_inclusion("using locations %a",last_locationset)
568 report_inclusion("using paths % a",figure_paths)
569 end
570end
571
572implement { name = "setfigurepaths", actions = figures.setpaths, arguments = "2 strings" }
573
574
575
576function figures.hash(data)
577 local status = data and data.status
578 return (status and status.hash or tostring(status.private)) or "nohash"
579end
580
581
582
583local function new()
584 local request = {
585 name = false,
586 label = false,
587 format = false,
588 page = false,
589 width = false,
590 height = false,
591 preview = false,
592 ["repeat"] = false,
593 controls = false,
594 display = false,
595 mask = false,
596 crop = false,
597 conversion = false,
598 resolution = false,
599 color = false,
600 arguments = false,
601 cache = false,
602 prefix = false,
603 size = false,
604 }
605 local used = {
606 fullname = false,
607 format = false,
608 name = false,
609 path = false,
610 suffix = false,
611 width = false,
612 height = false,
613 }
614 local status = {
615 status = 0,
616 converted = false,
617 cached = false,
618 fullname = false,
619 format = false,
620 }
621
622
623
624
625 return {
626 request = request,
627 used = used,
628 status = status,
629 }
630end
631
632
633
634local lastfiguredata = nil
635local callstack = { }
636
637function figures.initialize(request)
638 local figuredata = new()
639 if request then
640
641
642 local w = tonumber(request.width) or 0
643 local h = tonumber(request.height) or 0
644 local p = tonumber(request.page) or 0
645 request.width = w > 0 and w or nil
646 request.height = h > 0 and h or nil
647
648 request.page = p > 0 and p or 1
649 request.keepopen = p > 0
650 request.size = checkimagesize(request.size)
651 request.object = request.object == v_yes
652 request["repeat"] = request["repeat"] == v_yes
653 request.preview = request.preview == v_yes
654 request.cache = request.cache ~= "" and request.cache
655 request.prefix = request.prefix ~= "" and request.prefix
656 request.format = request.format ~= "" and request.format
657 request.compact = request.compact == v_yes
658 table.merge(figuredata.request,request)
659 end
660 return figuredata
661end
662
663function figures.push(request)
664 statistics.starttiming(figures)
665 local figuredata = figures.initialize(request)
666 insert(callstack,figuredata)
667 lastfiguredata = figuredata
668 return figuredata
669end
670
671function figures.pop()
672 remove(callstack)
673 lastfiguredata = callstack[#callstack] or lastfiguredata
674 statistics.stoptiming(figures)
675end
676
677function figures.current()
678 return callstack[#callstack] or lastfiguredata
679end
680
681local function get(category,tag,default)
682 local value = lastfiguredata and lastfiguredata[category]
683 value = value and value[tag]
684 if not value or value == "" or value == true then
685 return default or ""
686 else
687 return value
688 end
689end
690
691local function setdimensions(box)
692 local status = lastfiguredata and lastfiguredata.status
693 local used = lastfiguredata and lastfiguredata.used
694 if status and used then
695 local b = texgetbox(box)
696 local w = b.width
697 local h = b.height + b.depth
698 status.width = w
699 status.height = h
700 used.width = w
701 used.height = h
702 status.status = 10
703 end
704end
705
706figures.get = get
707figures.set = setdimensions
708
709implement { name = "figurestatus", actions = { get, context }, arguments = { "'status'", "string", "string" } }
710implement { name = "figurerequest", actions = { get, context }, arguments = { "'request'", "string", "string" } }
711implement { name = "figureused", actions = { get, context }, arguments = { "'used'", "string", "string" } }
712
713implement { name = "figurefilepath", public = true, actions = { get, file.dirname, context }, arguments = { "'used'", "'fullname'" } }
714implement { name = "figurefilename", public = true, actions = { get, file.nameonly, context }, arguments = { "'used'", "'fullname'" } }
715implement { name = "figurefiletype", public = true, actions = { get, file.extname, context }, arguments = { "'used'", "'fullname'" } }
716
717implement { name = "figuresetdimensions", actions = setdimensions, arguments = "integer" }
718
719
720
721local function forbiddenname(filename)
722 if not filename or filename == "" then
723 return false
724 end
725 local expandedfullname = collapsepath(filename,true)
726 local expandedinputname = collapsepath(file.addsuffix(environment.jobfilename,environment.jobfilesuffix),true)
727 if expandedfullname == expandedinputname then
728 report_inclusion("skipping graphic with same name as input filename %a, enforce suffix",expandedinputname)
729 return true
730 end
731 local expandedoutputname = collapsepath(codeinjections.getoutputfilename(),true)
732 if expandedfullname == expandedoutputname then
733 report_inclusion("skipping graphic with same name as output filename %a, enforce suffix",expandedoutputname)
734 return true
735 end
736end
737
738local function rejected(specification)
739 if extra_check then
740 local fullname = specification.fullname
741 if fullname and figures_native[file.suffix(fullname)] and not figures.guess(fullname) then
742 specification.comment = "probably a bad file"
743 specification.found = false
744 specification.error = true
745 report_inclusion("file %a looks bad",fullname)
746 return true
747 end
748 end
749end
750
751local function wipe(str)
752 if str == "" or str == "default" or str == "unknown" then
753 return nil
754 else
755 return str
756 end
757end
758
759local function register(askedname,specification)
760 if not specification then
761 specification = { askedname = askedname, comment = "invalid specification" }
762 elseif forbiddenname(specification.fullname) then
763 specification = { askedname = askedname, comment = "forbidden name" }
764 elseif specification.internal then
765
766 specification.found = true
767 if trace_figures then
768 report_inclusion("format %a internally supported by engine",specification.format)
769 end
770 elseif not rejected(specification) then
771 local format = specification.format
772 if format then
773 local conversion = wipe(specification.conversion)
774 local resolution = wipe(specification.resolution)
775 local arguments = wipe(specification.arguments)
776 local crop = wipe(specification.crop)
777 local newformat = conversion
778 if not newformat or newformat == "" then
779 newformat = defaultformat
780 end
781 if trace_conversion then
782 report_inclusion("checking conversion of %a, fullname %a, old format %a, new format %a, conversion %a, resolution %a, crop %a, arguments %a",
783 askedname,
784 specification.fullname,
785 format,
786 newformat,
787 conversion or "default",
788 resolution or "default",
789 crop or "default",
790 arguments or ""
791 )
792 end
793
794 local remapper = remappers[format]
795 if remapper then
796 remapper = remapper[conversion]
797 if remapper then
798 specification = remapper(specification) or specification
799 format = specification.format
800 newformat = format
801 conversion = nil
802 end
803 end
804
805 local converter = (not remapper) and (newformat ~= format or resolution or arguments) and converters[format]
806 if converter then
807 local okay = converter[newformat]
808 if okay then
809 converter = okay
810 else
811 newformat = defaultformat
812 converter = converter[newformat]
813 end
814 elseif trace_conversion then
815 report_inclusion("no converter for %a to %a",format,newformat)
816 end
817 if converter then
818
819
820
821
822 local oldname = specification.fullname
823 local newpath = file.dirname(oldname)
824 local oldbase = file.basename(oldname)
825 local runpath = environment.arguments.runpath
826 if runpath and runpath ~= "" and newpath == environment.arguments.path then
827 newpath = runpath
828 end
829
830
831
832
833
834
835
836
837 local newbase = oldbase
838
839 local fc = specification.cache or figures.cachepaths.path
840 if fc and fc ~= "" and fc ~= "." then
841 newpath = gsub(fc,"%*",newpath)
842 else
843 newbase = defaultprefix .. newbase
844 end
845 local subpath = specification.subpath or figures.cachepaths.subpath
846 if subpath and subpath ~= "" and subpath ~= "." then
847 newpath = newpath .. "/" .. subpath
848 end
849 if not isdir(newpath) then
850 dir.makedirs(newpath)
851 if not file.is_writable(newpath) then
852 if trace_conversion then
853 report_inclusion("path %a is not writable, forcing conversion path %a",newpath,".")
854 end
855 newpath = "."
856 end
857 end
858 local prefix = specification.prefix or figures.cachepaths.prefix
859 if prefix and prefix ~= "" then
860 newbase = prefix .. newbase
861 end
862 local hash = ""
863 if resolution then
864 hash = hash .. "[r:" .. resolution .. "]"
865 end
866 if arguments then
867 hash = hash .. "[a:" .. arguments .. "]"
868 end
869 if crop then
870 hash = hash .. "[c:" .. crop .. "]"
871 end
872 newbase = gsub(newbase,"%.","_")
873 if hash ~= "" then
874 newbase = newbase .. "_" .. md5.hex(hash)
875 end
876
877
878
879
880
881
882
883
884
885
886
887 local newbase = newbase .. "." .. newformat
888 local newname = file.join(newpath,newbase)
889 oldname = collapsepath(oldname)
890 newname = collapsepath(newname)
891 local oldtime = modificationtime(oldname) or 0
892 local newtime = modificationtime(newname) or 0
893 if newtime == 0 or oldtime > newtime then
894 if trace_conversion then
895 report_inclusion("converting %a (%a) from %a to %a",askedname,oldname,format,newformat)
896 end
897 converter(oldname,newname,resolution or "", arguments or "",specification)
898 else
899 if trace_conversion then
900 report_inclusion("no need to convert %a (%a) from %a to %a",askedname,oldname,format,newformat)
901 end
902 end
903 if io.exists(newname) and io.size(newname) > 0 then
904 specification.foundname = oldname
905 specification.fullname = newname
906 specification.prefix = prefix
907 specification.subpath = subpath
908 specification.converted = true
909 format = newformat
910 if not figures_suffixes[format] then
911
912
913 local suffix = file.suffix(newformat)
914 if figures_suffixes[suffix] then
915 if trace_figures then
916 report_inclusion("using suffix %a as format for %a",suffix,format)
917 end
918 format = suffix
919 end
920 end
921 specification.format = format
922 elseif io.exists(oldname) then
923 report_inclusion("file %a is bugged",oldname)
924 if format and imagetypes[format] then
925 specification.fullname = oldname
926 end
927 specification.converted = false
928 specification.bugged = true
929 end
930 end
931 end
932 if format then
933 local found = figures_suffixes[format]
934 if not found then
935 specification.found = false
936 if trace_figures then
937 report_inclusion("format %a is not supported",format)
938 end
939 elseif imagetypes[format] then
940 specification.found = true
941 if trace_figures then
942 report_inclusion("format %a natively supported by backend",format)
943 end
944 else
945 specification.found = true
946 if trace_figures then
947 report_inclusion("format %a supported by output file format",format)
948 end
949 end
950 else
951 specification.askedname = askedname
952 specification.found = false
953 end
954 end
955 if specification.found then
956 specification.foundname = specification.foundname or specification.fullname
957 else
958 specification.foundname = nil
959 end
960 specification.badname = figures.badname(askedname)
961 local askedhash = f_hash_part(
962 askedname,
963 specification.conversion or "default",
964 specification.resolution or "default",
965 specification.arguments or ""
966 )
967 figures_found[askedhash] = specification
968 if not specification.found then
969 figures.nofmissing = figures.nofmissing + 1
970 end
971 return specification
972end
973
974local resolve_too = false
975
976local internalschemes = {
977 file = true,
978 tree = true,
979 dirfile = true,
980 dirtree = true,
981}
982
983local function locate(request)
984
985
986 local askedname = request.name or ""
987 local askedcache = request.cache
988 local askedconversion = request.conversion
989 local askedresolution = request.resolution
990 local askedarguments = request.arguments
991 local askedcrop = request.crop
992 local askedhash = f_hash_part(
993 askedname,
994 askedconversion or "default",
995 askedresolution or "default",
996 askedcrop or "default",
997 askedarguments or ""
998 )
999 local foundname = figures_found[askedhash]
1000 if foundname then
1001 return foundname
1002 end
1003
1004
1005 local askedformat = request.format
1006 if not askedformat or askedformat == "" or askedformat == "unknown" then
1007 askedformat = file.suffix(askedname) or ""
1008 elseif askedformat == v_auto then
1009 if trace_figures then
1010 report_inclusion("ignoring suffix of %a",askedname)
1011 end
1012 askedformat = ""
1013 askedname = file.removesuffix(askedname)
1014 end
1015
1016 local hashed = urlhashed(askedname)
1017 if not hashed then
1018
1019 elseif internalschemes[hashed.scheme] then
1020 local path = hashed.path
1021 if path and path ~= "" then
1022 askedname = path
1023 end
1024 else
1025 local foundname = resolvers.findbinfile(askedname)
1026 if not foundname or not isfile(foundname) then
1027 if trace_figures then
1028 report_inclusion("unknown url %a",askedname)
1029 end
1030
1031 return register(askedname)
1032 end
1033 local guessedformat = figures.guess(foundname)
1034 if askedformat ~= guessedformat then
1035 if trace_figures then
1036 report_inclusion("url %a has unknown format",askedname)
1037 end
1038
1039 return register(askedname)
1040 else
1041 if trace_figures then
1042 report_inclusion("url %a is resolved to %a",askedname,foundname)
1043 end
1044 return register(askedname, {
1045 askedname = askedname,
1046 fullname = foundname,
1047 format = askedformat,
1048 cache = askedcache,
1049 conversion = askedconversion,
1050 resolution = askedresolution,
1051 crop = askedcrop,
1052 arguments = askedarguments,
1053 })
1054 end
1055 end
1056
1057 local askedpath = file.is_rootbased_path(askedname)
1058 local askedbase = file.basename(askedname)
1059 if askedformat ~= "" then
1060 askedformat = lower(askedformat)
1061 if trace_figures then
1062 report_inclusion("forcing format %a",askedformat)
1063 end
1064 local format = figures_suffixes[askedformat]
1065 if not format then
1066 for i=1,#figures_patterns do
1067 local pattern = figures_patterns[i]
1068 if find(askedformat,pattern[1]) then
1069 format = pattern[2]
1070 if trace_figures then
1071 report_inclusion("asked format %a matches %a",askedformat,pattern[1])
1072 end
1073 break
1074 end
1075 end
1076 end
1077 if format then
1078 local foundname, quitscanning, forcedformat, internal = figures.exists(askedname,format,resolve_too)
1079 if foundname then
1080 return register(askedname, {
1081 askedname = askedname,
1082 fullname = foundname,
1083 format = forcedformat or format,
1084 cache = askedcache,
1085
1086 conversion = askedconversion,
1087 resolution = askedresolution,
1088 arguments = askedarguments,
1089 crop = askedcrop,
1090 internal = internal,
1091 })
1092 elseif quitscanning then
1093 return register(askedname)
1094 end
1095 askedformat = format
1096 elseif trace_figures then
1097 report_inclusion("unknown format %a",askedformat)
1098 end
1099 if askedpath then
1100
1101 local foundname, quitscanning, forcedformat = figures.exists(askedname,askedformat,resolve_too)
1102 if foundname then
1103 return register(askedname, {
1104 askedname = askedname,
1105 fullname = foundname,
1106 format = forcedformat or askedformat,
1107 cache = askedcache,
1108 conversion = askedconversion,
1109 resolution = askedresolution,
1110 crop = askedcrop,
1111 arguments = askedarguments,
1112 })
1113 end
1114 else
1115
1116 for i=1,#figure_paths do
1117 local path = resolveprefix(figure_paths[i])
1118 local check = path .. "/" .. askedname
1119
1120
1121 local foundname, quitscanning, forcedformat = figures.exists(check,askedformat,resolve_too)
1122 if foundname then
1123 return register(check, {
1124 askedname = askedname,
1125 fullname = foundname,
1126 format = askedformat,
1127 cache = askedcache,
1128 conversion = askedconversion,
1129 resolution = askedresolution,
1130 crop = askedcrop,
1131 arguments = askedarguments,
1132 })
1133 end
1134 end
1135 if figures.defaultsearch then
1136 local check = resolvers.findfile(askedname)
1137 if check and check ~= "" then
1138 return register(askedname, {
1139 askedname = askedname,
1140 fullname = check,
1141 format = askedformat,
1142 cache = askedcache,
1143 conversion = askedconversion,
1144 resolution = askedresolution,
1145 crop = askedcrop,
1146 arguments = askedarguments,
1147 })
1148 end
1149 end
1150 end
1151 elseif askedpath then
1152 if trace_figures then
1153 report_inclusion("using rootbased path")
1154 end
1155 for i=1,#figures_order do
1156 local format = figures_order[i]
1157 local list = figures_formats[format].list or { format }
1158 for j=1,#list do
1159 local suffix = list[j]
1160 local check = file.addsuffix(askedname,suffix)
1161 local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too)
1162 if foundname then
1163 return register(askedname, {
1164 askedname = askedname,
1165 fullname = foundname,
1166 format = forcedformat or format,
1167 cache = askedcache,
1168 conversion = askedconversion,
1169 resolution = askedresolution,
1170 crop = askedcrop,
1171 arguments = askedarguments,
1172 })
1173 end
1174 end
1175 end
1176 else
1177 if figures.preferquality then
1178 if trace_figures then
1179 report_inclusion("unknown format, quality preferred")
1180 end
1181 for j=1,#figures_order do
1182 local format = figures_order[j]
1183 local list = figures_formats[format].list or { format }
1184 for k=1,#list do
1185 local suffix = list[k]
1186
1187 local name = file.replacesuffix(askedname,suffix)
1188 for i=1,#figure_paths do
1189 local path = resolveprefix(figure_paths[i])
1190 local check = path .. "/" .. name
1191 local isfile = internalschemes[urlhashed(check).scheme]
1192 if not isfile then
1193 if trace_figures then
1194 report_inclusion("warning: skipping path %a",path)
1195 end
1196 else
1197 local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too)
1198 if foundname then
1199 return register(askedname, {
1200 askedname = askedname,
1201 fullname = foundname,
1202 format = forcedformat or format,
1203 cache = askedcache,
1204 conversion = askedconversion,
1205 resolution = askedresolution,
1206 crop = askedcrop,
1207 arguments = askedarguments
1208 })
1209 end
1210 end
1211 end
1212 end
1213 end
1214 else
1215 if trace_figures then
1216 report_inclusion("unknown format, using path strategy")
1217 end
1218 for i=1,#figure_paths do
1219 local path = resolveprefix(figure_paths[i])
1220 for j=1,#figures_order do
1221 local format = figures_order[j]
1222 local list = figures_formats[format].list or { format }
1223 for k=1,#list do
1224 local suffix = list[k]
1225 local check = path .. "/" .. file.replacesuffix(askedbase,suffix)
1226 local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too)
1227 if foundname then
1228 return register(askedname, {
1229 askedname = askedname,
1230 fullname = foudname,
1231 format = forcedformat or format,
1232 cache = askedcache,
1233 conversion = askedconversion,
1234 resolution = askedresolution,
1235 crop = askedcrop,
1236 arguments = askedarguments,
1237 })
1238 end
1239 end
1240 end
1241 end
1242 end
1243 if figures.defaultsearch then
1244 if trace_figures then
1245 report_inclusion("using default tex path")
1246 end
1247 for j=1,#figures_order do
1248 local format = figures_order[j]
1249 local list = figures_formats[format].list or { format }
1250 for k=1,#list do
1251 local suffix = list[k]
1252 local check = resolvers.findfile(file.replacesuffix(askedname,suffix))
1253 if check and check ~= "" then
1254 return register(askedname, {
1255 askedname = askedname,
1256 fullname = check,
1257 format = format,
1258 cache = askedcache,
1259 conversion = askedconversion,
1260 resolution = askedresolution,
1261 crop = askedcrop,
1262 arguments = askedarguments,
1263 })
1264 end
1265 end
1266 end
1267 end
1268 end
1269 return register(askedname, {
1270 conversion = askedconversion,
1271 resolution = askedresolution,
1272 crop = askedcrop,
1273 arguments = askedarguments,
1274 })
1275end
1276
1277
1278
1279function identifiers.default(data)
1280 local dr, du, ds = data.request, data.used, data.status
1281 local l = locate(dr)
1282 local foundname = l.foundname
1283 local fullname = l.fullname or foundname
1284 if fullname then
1285 du.format = l.format or false
1286 du.fullname = fullname
1287 ds.fullname = foundname
1288 ds.format = l.format
1289 ds.status = (l.bugged and 0) or (l.found and 10) or 0
1290 end
1291 return data
1292end
1293
1294function figures.identify(data)
1295 data = data or callstack[#callstack] or lastfiguredata
1296 if data then
1297 local list = identifiers.list
1298 for i=1,#list do
1299 local identifier = list[i]
1300 local data = identifier(data)
1301
1302 if data and (not data.status and data.status.status > 0) then
1303 break
1304 end
1305 end
1306 end
1307 return data
1308end
1309
1310function figures.exists(askedname,format,resolve)
1311 return (existers[format] or existers.generic)(askedname,resolve)
1312end
1313
1314function figures.check(data)
1315 data = data or callstack[#callstack] or lastfiguredata
1316 return (checkers[data.status.format] or checkers.generic)(data)
1317end
1318
1319local used_images = { }
1320
1321statistics.register("used graphics",function()
1322 if trace_usage then
1323 local filename = file.nameonly(environment.jobname) .. "-figures-usage.lua"
1324 if next(figures_found) then
1325 local found = { }
1326 for _, data in sortedhash(figures_found) do
1327 found[#found+1] = data
1328 for k, v in next, data do
1329 if v == false or v == "" then
1330 data[k] = nil
1331 end
1332 end
1333 end
1334 for i=1,#used_images do
1335 local u = used_images[i]
1336 local s = u.status
1337 if s then
1338 s.status = nil
1339 if s.error then
1340 u.used = { }
1341 end
1342 end
1343 for _, t in next, u do
1344 for k, v in next, t do
1345 if v == false or v == "" or k == "private" then
1346 t[k] = nil
1347 end
1348 end
1349 end
1350 end
1351 table.save(filename,{
1352 found = found,
1353 used = used_images,
1354 } )
1355 return format("log saved in '%s'",filename)
1356 else
1357 os.remove(filename)
1358 end
1359 end
1360end)
1361
1362function figures.include(data)
1363 data = data or callstack[#callstack] or lastfiguredata
1364 if trace_usage then
1365 used_images[#used_images+1] = data
1366 end
1367 return (includers[data.status.format] or includers.generic)(data)
1368end
1369
1370function figures.scale(data)
1371 data = data or callstack[#callstack] or lastfiguredata
1372 ctx_doscalefigure()
1373 return data
1374end
1375
1376function figures.done(data)
1377 figures.nofprocessed = figures.nofprocessed + 1
1378 data = data or callstack[#callstack] or lastfiguredata
1379 local dr, du, ds, nr = data.request, data.used, data.status, figures.boxnumber
1380 local box = texgetbox(nr)
1381 ds.width = box.width
1382 ds.height = box.height
1383
1384
1385
1386
1387 if du.width and du.height and du.width > 0 and du.height > 0 then
1388 ds.xscale = ds.width /du.width
1389 ds.yscale = ds.height/du.height
1390 elseif du.xsize and du.ysize and du.xsize > 0 and du.ysize > 0 then
1391 ds.xscale = ds.width /du.xsize
1392 ds.yscale = ds.height/du.ysize
1393 else
1394 ds.xscale = 1
1395 ds.yscale = 1
1396 end
1397
1398 ds.page = ds.page or du.page or dr.page
1399 return data
1400end
1401
1402function figures.dummy(data)
1403 data = data or callstack[#callstack] or lastfiguredata
1404 local dr, du, nr = data.request, data.used, figures.boxnumber
1405 local box = hpack(new_hlist())
1406 du.width = du.width or figures.defaultwidth
1407 du.height = du.height or figures.defaultheight
1408 du.depth = du.depth or figures.defaultdepth
1409 box.width = du.width
1410 box.height = du.height
1411 box.depth = du.depth
1412 texsetbox(nr,box)
1413end
1414
1415
1416
1417function existers.generic(askedname,resolve)
1418
1419 local result
1420 if hasscheme(askedname) then
1421 result = resolvers.findbinfile(askedname)
1422 elseif isfile(askedname) then
1423 result = askedname
1424 elseif resolve then
1425 result = resolvers.findbinfile(askedname)
1426 end
1427 if not result or result == "" then
1428 result = false
1429 end
1430 if trace_figures then
1431 if result then
1432 report_inclusion("%a resolved to %a",askedname,result)
1433 else
1434 report_inclusion("%a cannot be resolved",askedname)
1435 end
1436 end
1437 return result
1438end
1439
1440
1441
1442
1443local transforms = setmetatableindex (
1444 {
1445 ["orientation-1"] = 0, ["R0"] = 0,
1446 ["orientation-2"] = 4, ["R0MH"] = 4,
1447 ["orientation-3"] = 2, ["R180"] = 2,
1448 ["orientation-4"] = 6, ["R0MV"] = 6,
1449 ["orientation-5"] = 5, ["R270MH"] = 5,
1450 ["orientation-6"] = 3, ["R90"] = 3,
1451 ["orientation-7"] = 7, ["R90MH"] = 7,
1452 ["orientation-8"] = 1, ["R270"] = 1,
1453 },
1454 function(t,k)
1455 local v = tonumber(k) or 0
1456 if v < 0 or v > 7 then
1457 v = 0
1458 end
1459 t[k] = v
1460 return v
1461 end
1462)
1463
1464local function checktransform(figure,forced)
1465 if auto_transform then
1466
1467 local orientation = (forced ~= "" and forced ~= v_auto and forced) or figure.orientation or 0
1468 local transform = transforms["orientation-"..orientation]
1469 figure.transform = transform
1470 if odd(transform) then
1471 return figure.height, figure.width
1472 else
1473 return figure.width, figure.height
1474 end
1475 end
1476end
1477
1478local pagecount = { }
1479
1480function checkers.generic(data)
1481 local dr, du, ds = data.request, data.used, data.status
1482 local name = du.fullname or "unknown generic"
1483 local page = du.page or dr.page
1484 local size = dr.size or "crop"
1485 local color = dr.color or "natural"
1486 local mask = dr.mask or "none"
1487 local crop = dr.crop or "none"
1488 local conversion = dr.conversion
1489 local resolution = dr.resolution
1490 local arguments = dr.arguments
1491 local scanimage = dr.scanimage or scanimage
1492 local userpassword = dr.userpassword
1493 local ownerpassword = dr.ownerpassword
1494 if not conversion or conversion == "" then
1495 conversion = "default"
1496 end
1497 if not resolution or resolution == "" then
1498 resolution = "default"
1499 end
1500 if not arguments or arguments == "" then
1501 arguments = "default"
1502 end
1503 local hash = f_hash_full(
1504 name,
1505 page,
1506 size,
1507 color,
1508 mask,
1509 crop,
1510 conversion,
1511 resolution,
1512 arguments
1513 )
1514
1515 local figure = figures_loaded[hash]
1516 if figure == nil then
1517 figure = createimage {
1518 filename = name,
1519 page = page,
1520 pagebox = dr.size,
1521 keepopen = dr.keepopen or false,
1522 userpassword = userpassword,
1523 ownerpassword = ownerpassword,
1524
1525 }
1526 codeinjections.setfigurecolorspace(data,figure)
1527 codeinjections.setfiguremask(data,figure)
1528 if figure then
1529
1530 if page and page > 1 then
1531 local f = scanimage {
1532 filename = name,
1533 userpassword = userpassword,
1534 ownerpassword = ownerpassword,
1535 }
1536 if f and f.page and f.pages < page then
1537 report_inclusion("no page %i in %a, using page 1",page,name)
1538 page = 1
1539 figure.page = page
1540 end
1541 end
1542
1543 local f, comment = checkimage(scanimage(figure))
1544 if not f then
1545 ds.comment = comment
1546 ds.found = false
1547 ds.error = true
1548 end
1549 if figure.attr and not f.attr then
1550
1551 f.attr = figure.attr
1552 end
1553 if dr.cmyk == v_yes then
1554 f.enforcecmyk = true
1555 elseif dr.cmyk == v_auto and attributes.colors.model == "cmyk" then
1556 f.enforcecmyk = true
1557 end
1558 figure = f
1559 end
1560 local f, d = codeinjections.setfigurealternative(data,figure)
1561 figure = f or figure
1562 data = d or data
1563 figures_loaded[hash] = figure
1564 if trace_conversion then
1565 report_inclusion("new graphic, using hash %a",hash)
1566 end
1567 else
1568 if trace_conversion then
1569 report_inclusion("existing graphic, using hash %a",hash)
1570 end
1571 end
1572 if figure then
1573 local width, height = checktransform(figure,dr.transform)
1574
1575 du.width = width
1576 du.height = height
1577 du.pages = figure.pages
1578 du.depth = figure.depth or 0
1579 du.colordepth = figure.colordepth or 0
1580 du.xresolution = figure.xres or 0
1581 du.yresolution = figure.yres or 0
1582 du.xsize = figure.xsize or 0
1583 du.ysize = figure.ysize or 0
1584 du.rotation = figure.rotation or 0
1585 du.orientation = figure.orientation or 0
1586 ds.private = figure
1587 ds.hash = hash
1588 end
1589 return data
1590end
1591
1592local nofimages = 0
1593local pofimages = { }
1594
1595function figures.getrealpage(index)
1596 return pofimages[index] or 0
1597end
1598
1599local function updatepage(specification)
1600 local n = specification.n
1601 pofimages[n] = pofimages[n] or tex.count.realpageno
1602end
1603
1604function includers.generic(data)
1605 local dr, du, ds = data.request, data.used, data.status
1606
1607 dr.width = du.width
1608 dr.height = du.height
1609 local hash = figures.hash(data)
1610 local figure = figures_used[hash]
1611
1612
1613
1614
1615
1616 if figure == nil then
1617 figure = ds.private
1618 if figure then
1619 figure = (dr.copyimage or copyimage)(figure)
1620 if figure then
1621 figure.width = dr.width or figure.width
1622 figure.height = dr.height or figure.height
1623 end
1624 end
1625 figures_used[hash] = figure
1626 end
1627 if figure then
1628 local nr = figures.boxnumber
1629 nofimages = nofimages + 1
1630 ds.pageindex = nofimages
1631 local image = wrapimage(figure)
1632 local pager = new_latelua { action = updatepage, n = nofimages }
1633 image.next = pager
1634 pager.prev = image
1635 local box = hpack(image)
1636 box.width = figure.width
1637 box.height = figure.height
1638 box.depth = 0
1639 texsetbox(nr,box)
1640 ds.objectnumber = figure.objnum
1641
1642 ctx_relocateexternalfigure()
1643 end
1644 return data
1645end
1646
1647
1648
1649local function checkers_nongeneric(data,command)
1650 local dr, du, ds = data.request, data.used, data.status
1651 local name = du.fullname or "unknown nongeneric"
1652 local hash = name
1653 if dr.object then
1654
1655 if not objects.data["FIG"][hash] then
1656 if type(command) == "function" then
1657 command()
1658 end
1659 ctx_dosetfigureobject("FIG",hash)
1660 end
1661 ctx_doboxfigureobject("FIG",hash)
1662 elseif type(command) == "function" then
1663 command()
1664 end
1665 return data
1666end
1667
1668local function includers_nongeneric(data)
1669 return data
1670end
1671
1672checkers.nongeneric = checkers_nongeneric
1673includers.nongeneric = includers_nongeneric
1674
1675
1676
1677function checkers.mov(data)
1678 local dr, du, ds = data.request, data.used, data.status
1679 local width = todimen(dr.width or figures.defaultwidth)
1680 local height = todimen(dr.height or figures.defaultheight)
1681 local foundname = du.fullname
1682 dr.width, dr.height = width, height
1683 du.width, du.height, du.foundname = width, height, foundname
1684 if trace_inclusion then
1685 report_inclusion("including movie %a, width %p, height %p",foundname,width,height)
1686 end
1687
1688 ctx_startfoundexternalfigure(width .. "sp",height .. "sp")
1689 context(function()
1690 nodeinjections.insertmovie {
1691 width = width,
1692 height = height,
1693 factor = bpfactor,
1694 ["repeat"] = dr["repeat"],
1695 controls = dr.controls,
1696 preview = dr.preview,
1697 label = dr.label,
1698 foundname = foundname,
1699 }
1700 end)
1701 ctx_stopfoundexternalfigure()
1702 return data
1703end
1704
1705includers.mov = includers.nongeneric
1706
1707
1708
1709internalschemes.mprun = true
1710
1711
1712
1713local ctx_docheckfiguremprun = context.docheckfiguremprun
1714local ctx_docheckfiguremps = context.docheckfiguremps
1715
1716local function internal(askedname)
1717 local spec, mprun, mpnum = match(lower(askedname),"mprun([:%.]?)(.-)%.(%d+)")
1718
1719 if spec ~= "" then
1720 return mprun, mpnum
1721 else
1722 return "", mpnum
1723 end
1724end
1725
1726function existers.mps(askedname)
1727 local mprun, mpnum = internal(askedname)
1728 if mpnum then
1729 return askedname, true, "mps", true
1730 else
1731 return existers.generic(askedname)
1732 end
1733end
1734
1735function checkers.mps(data)
1736 local mprun, mpnum = internal(data.used.fullname)
1737 if mpnum then
1738 return checkers_nongeneric(data,function() ctx_docheckfiguremprun(mprun,mpnum) end)
1739 else
1740 return checkers_nongeneric(data,function() ctx_docheckfiguremps(data.used.fullname) end)
1741 end
1742end
1743
1744includers.mps = includers.nongeneric
1745
1746
1747
1748local ctx_docheckfiguretex = context.docheckfiguretex
1749
1750function existers.tex(askedname)
1751 askedname = resolvers.findfile(askedname)
1752 return askedname ~= "" and askedname or false, true, "tex", true
1753end
1754
1755function checkers.tex(data)
1756 return checkers_nongeneric(data,function() ctx_docheckfiguretex(data.used.fullname) end)
1757end
1758
1759includers.tex = includers.nongeneric
1760
1761
1762
1763local ctx_docheckfigurebuffer = context.docheckfigurebuffer
1764
1765function existers.buffer(askedname)
1766 local name = file.nameonly(askedname)
1767 local okay = buffers.exists(name)
1768 return okay and name, true, "buffer", true
1769end
1770
1771function checkers.buffer(data)
1772 return checkers_nongeneric(data,function() ctx_docheckfigurebuffer(file.nameonly(data.used.fullname)) end)
1773end
1774
1775includers.buffers = includers.nongeneric
1776
1777
1778
1779function existers.auto(askedname)
1780 local name = gsub(askedname, ".auto$", "")
1781 local format = figures.guess(name)
1782
1783
1784
1785
1786
1787 return format and name, true, format
1788end
1789
1790checkers.auto = checkers.generic
1791includers.auto = includers.generic
1792
1793
1794
1795local ctx_docheckfigurecld = context.docheckfigurecld
1796
1797function existers.cld(askedname)
1798 askedname = resolvers.findfile(askedname)
1799 return askedname ~= "" and askedname or false, true, "cld", true
1800end
1801
1802function checkers.cld(data)
1803 return checkers_nongeneric(data,function() ctx_docheckfigurecld(data.used.fullname) end)
1804end
1805
1806includers.cld = includers.nongeneric
1807
1808
1809
1810setmetatableindex(converters,"table")
1811
1812
1813
1814
1815function programs.makeoptions(options)
1816 local to = type(options)
1817 return (to == "table" and concat(options," ")) or (to == "string" and options) or ""
1818end
1819
1820function programs.run(binary,argument,variables)
1821 local found = nil
1822 if type(binary) == "table" then
1823 for i=1,#binary do
1824 local b = binary[i]
1825 found = os.which(b)
1826 if found then
1827 binary = b
1828 break
1829 end
1830 end
1831 if not found then
1832 binary = concat(binary, " | ")
1833 end
1834 elseif binary then
1835 found = os.which(match(binary,"[%S]+"))
1836 end
1837 if type(argument) == "table" then
1838 argument = concat(argument," ")
1839 end
1840 if not found then
1841 report_inclusion("program %a is not installed",binary or "?")
1842 elseif not argument or argument == "" then
1843 report_inclusion("nothing to run, no arguments for program %a",binary)
1844 else
1845
1846
1847 local command = format([[%s %s]],binary,replacetemplate(longtostring(argument),variables))
1848 if trace_conversion or trace_programs then
1849 report_inclusion("running command: %s",command)
1850 end
1851 os.execute(command)
1852 end
1853end
1854
1855
1856
1857
1858
1859local bases = allocate()
1860figures.bases = bases
1861
1862local bases_list = nil
1863local bases_used = nil
1864local bases_found = nil
1865local bases_enabled = false
1866
1867local function reset()
1868 bases_list = allocate()
1869 bases_used = allocate()
1870 bases_found = allocate()
1871 bases_enabled = false
1872 bases.list = bases_list
1873 bases.used = bases_used
1874 bases.found = bases_found
1875end
1876
1877reset()
1878
1879function bases.use(basename)
1880 if basename == "reset" then
1881 reset()
1882 else
1883 basename = file.addsuffix(basename,"xml")
1884 if not bases_used[basename] then
1885 local t = { basename, nil, nil }
1886 bases_used[basename] = t
1887 bases_list[#bases_list+1] = t
1888 if not bases_enabled then
1889 bases_enabled = true
1890 xml.registerns("rlx","http://www.pragma-ade.com/schemas/rlx")
1891 end
1892 if trace_bases then
1893 report_inclusion("registering base %a",basename)
1894 end
1895 end
1896 end
1897end
1898
1899implement { name = "usefigurebase", actions = bases.use, arguments = "string" }
1900
1901local function bases_find(basename,askedlabel)
1902 if trace_bases then
1903 report_inclusion("checking for %a in base %a",askedlabel,basename)
1904 end
1905 basename = file.addsuffix(basename,"xml")
1906 local t = bases_found[askedlabel]
1907 if t == nil then
1908 local base = bases_used[basename]
1909 local page = 0
1910 if base[2] == nil then
1911
1912 for i=1,#figure_paths do
1913 local path = resolveprefix(figure_paths[i])
1914 local xmlfile = path .. "/" .. basename
1915 if io.exists(xmlfile) then
1916 base[2] = xmlfile
1917 base[3] = xml.load(xmlfile)
1918 if trace_bases then
1919 report_inclusion("base %a loaded",xmlfile)
1920 end
1921 break
1922 end
1923 end
1924 end
1925 t = false
1926 if base[2] and base[3] then
1927 for e in xml.collected(base[3],"/(*:library|figurelibrary)/*:figure/*:label") do
1928 page = page + 1
1929 if xml.text(e) == askedlabel then
1930 t = {
1931 base = file.replacesuffix(base[2],"pdf"),
1932 format = "pdf",
1933 name = xml.text(e,"../*:file"),
1934 page = page,
1935 }
1936 bases_found[askedlabel] = t
1937 if trace_bases then
1938 report_inclusion("figure %a found in base %a",askedlabel,base[2])
1939 end
1940 return t
1941 end
1942 end
1943 if trace_bases and not t then
1944 report_inclusion("figure %a not found in base %a",askedlabel,base[2])
1945 end
1946 end
1947 end
1948 return t
1949end
1950
1951
1952
1953local function bases_locate(askedlabel)
1954 for i=1,#bases_list do
1955 local entry = bases_list[i]
1956 local t = bases_find(entry[1],askedlabel,1,true)
1957 if t then
1958 return t
1959 end
1960 end
1961 return false
1962end
1963
1964function identifiers.base(data)
1965 if bases_enabled then
1966 local dr, du, ds = data.request, data.used, data.status
1967 local fbl = bases_locate(dr.name or dr.label)
1968 if fbl then
1969 du.page = fbl.page
1970 du.format = fbl.format
1971 du.fullname = fbl.base
1972 ds.fullname = fbl.name
1973 ds.format = fbl.format
1974 ds.page = fbl.page
1975 ds.status = 10
1976 end
1977 end
1978 return data
1979end
1980
1981bases.locate = bases_locate
1982bases.find = bases_find
1983
1984identifiers.list = {
1985 identifiers.base,
1986 identifiers.default
1987}
1988
1989
1990
1991statistics.register("graphics processing time", function()
1992 local nofprocessed = figures.nofprocessed
1993 if nofprocessed > 0 then
1994 local nofnames, nofbadnames = 0, 0
1995 for hash, data in next, figures_found do
1996 nofnames = nofnames + 1
1997 if data.badname then
1998 nofbadnames = nofbadnames + 1
1999 end
2000 end
2001 return format("%s seconds including tex, %s processed images, %s unique asked, %s bad names",
2002 statistics.elapsedtime(figures),nofprocessed,nofnames,nofbadnames)
2003 else
2004 return nil
2005 end
2006end)
2007
2008
2009
2010function figures.applyratio(width,height,w,h)
2011 if not width or width == "" then
2012 if not height or height == "" then
2013 return figures.defaultwidth, figures.defaultheight
2014 else
2015 height = todimen(height)
2016 if w and h then
2017 return height * w/h, height
2018 else
2019 return figures.defaultwidth, height
2020 end
2021 end
2022 else
2023 width = todimen(width)
2024 if not height or height == "" then
2025 if w and h then
2026 return width, width * h/w
2027 else
2028 return width, figures.defaultheight
2029 end
2030 else
2031 return width, todimen(height)
2032 end
2033 end
2034end
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058function figures.getinfo(name,page)
2059 if type(name) == "string" then
2060 name = { name = name, page = page }
2061 end
2062 if name.name then
2063 local data = figures.push(name)
2064 data = figures.identify(data)
2065 if data.status and data.status.status > 0 then
2066 data = figures.check(data)
2067 end
2068 figures.pop()
2069 return data
2070 end
2071end
2072
2073function figures.getpdfinfo(name,page,metadata)
2074
2075
2076 if type(name) ~= "table" then
2077 name = { name = name, page = page, metadata = metadata }
2078 end
2079 return codeinjections.getinfo(name)
2080end
2081
2082
2083
2084implement {
2085 name = "figure_push",
2086 scope = "private",
2087 actions = figures.push,
2088 arguments = {
2089 {
2090 { "name" },
2091 { "label" },
2092 { "page" },
2093 { "file" },
2094 { "size" },
2095 { "object" },
2096 { "prefix" },
2097 { "cache" },
2098 { "format" },
2099 { "preset" },
2100 { "controls" },
2101 { "resources" },
2102 { "preview" },
2103 { "display" },
2104 { "mask" },
2105 { "crop" },
2106 { "conversion" },
2107 { "resolution" },
2108 { "color" },
2109 { "cmyk" },
2110 { "arguments" },
2111 { "repeat" },
2112 { "transform" },
2113 { "compact" },
2114 { "width", "dimen" },
2115 { "height", "dimen" },
2116 { "userpassword" },
2117 { "ownerpassword" },
2118 }
2119 }
2120}
2121
2122
2123
2124implement { name = "figure_pop", scope = "private", actions = figures.pop }
2125implement { name = "figure_done", scope = "private", actions = figures.done }
2126implement { name = "figure_dummy", scope = "private", actions = figures.dummy }
2127implement { name = "figure_identify", scope = "private", actions = figures.identify }
2128implement { name = "figure_scale", scope = "private", actions = figures.scale }
2129implement { name = "figure_check", scope = "private", actions = figures.check }
2130implement { name = "figure_include", scope = "private", actions = figures.include }
2131
2132implement {
2133 name = "setfigurelookuporder",
2134 actions = figures.setorder,
2135 arguments = "string"
2136}
2137
2138implement {
2139 name = "figure_reset",
2140 scope = "private",
2141 arguments = { "integer", "dimen", "dimen" },
2142 actions = function(box,width,height)
2143 figures.boxnumber = box
2144 figures.defaultwidth = width
2145 figures.defaultheight = height
2146 end
2147}
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163local registered = { }
2164
2165local ctx_doexternalfigurerepeat = context.doexternalfigurerepeat
2166
2167implement {
2168 name = "figure_register_page",
2169 arguments = "3 strings",
2170 actions = function(a,b,c)
2171 registered[#registered+1] = { a, b, c }
2172 context(#registered)
2173 end
2174}
2175
2176implement {
2177 name = "figure_nof_registered_pages",
2178 actions = function()
2179 context(#registered)
2180 end
2181}
2182
2183implement {
2184 name = "figure_flush_registered_pages",
2185 arguments = "string",
2186 actions = function(n)
2187 local f = registered[tonumber(n)]
2188 if f then
2189 ctx_doexternalfigurerepeat(f[1],f[2],f[3],n)
2190 end
2191 end
2192}
2193 |