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