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