diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/grph-inc.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/grph-inc.lua | 1052 |
1 files changed, 605 insertions, 447 deletions
diff --git a/Master/texmf-dist/tex/context/base/grph-inc.lua b/Master/texmf-dist/tex/context/base/grph-inc.lua index 33dd0766d2b..9603419ae2e 100644 --- a/Master/texmf-dist/tex/context/base/grph-inc.lua +++ b/Master/texmf-dist/tex/context/base/grph-inc.lua @@ -6,19 +6,15 @@ if not modules then modules = { } end modules ['grph-inc'] = { license = "see context related readme files" } --- figures -> managers.figures - -- todo: empty filename or only suffix always false (not found) - -- lowercase types -- mps tex tmp svg -- partly qualified -- dimensions --- consult rlx - +-- use metatables -- figures.boxnumber can go as we now can use names - -- avoid push +-- move some to command namespace --[[ The ConTeXt figure inclusion mechanisms are among the oldest code @@ -46,13 +42,18 @@ local texbox = tex.box local contains = table.contains local concat, insert, remove = table.concat, table.insert, table.remove local todimen = string.todimen +local collapsepath = file.collapsepath +local formatters = string.formatters +local longtostring = string.longtostring +local expandfilename = dir.expandname -local P = lpeg.P +local P, lpegmatch = lpeg.P, lpeg.match local settings_to_array = utilities.parsers.settings_to_array local settings_to_hash = utilities.parsers.settings_to_hash local allocate = utilities.storage.allocate local setmetatableindex = table.setmetatableindex +local replacetemplate = utilities.templates.replace local variables = interfaces.variables local codeinjections = backends.codeinjections @@ -68,7 +69,39 @@ local report_inclusion = logs.reporter("graphics","inclusion") local context, img = context, img ---- some extra img functions --- +local f_hash_part = formatters["%s->%s->%s"] +local f_hash_full = formatters["%s->%s->%s->%s->%s->%s->%s"] + +local v_yes = variables.yes +local v_low = variables.low +local v_medium = variables.medium +local v_high = variables.high +local v_global = variables["global"] +local v_local = variables["local"] +local v_default = variables.default + +local maxdimen = 2^30-1 + +function img.check(figure) + if figure then + local width = figure.width + local height = figure.height + if height > width then + if height > maxdimen then + figure.height = maxdimen + figure.width = width * maxdimen/height + report_inclusion("limiting natural dimensions of %a (%s)",figure.filename,"height") + end + elseif width > maxdimen then + figure.width = maxdimen + figure.height = height * maxdimen/width + report_inclusion("limiting natural dimensions of %a (%s)",figure.filename,"width") + end + return figure + end +end + +--- some extra img functions --- can become luat-img.lua local imgkeys = img.keys() @@ -102,7 +135,7 @@ local validtypes = table.tohash(img.types()) function img.checksize(size) if size then size = gsub(size,"box","") - return (validsizes[size] and size) or "crop" + return validsizes[size] and size or "crop" else return "crop" end @@ -116,54 +149,57 @@ end --- we can consider an grph-ini file -figures = figures or { } -local figures = figures - -figures.loaded = allocate() -figures.used = allocate() -figures.found = allocate() -figures.suffixes = allocate() -figures.patterns = allocate() -figures.resources = allocate() - - -figures.boxnumber = figures.boxnumber or 0 -figures.defaultsearch = true -figures.defaultwidth = 0 -figures.defaultheight = 0 -figures.defaultdepth = 0 -figures.nofprocessed = 0 -figures.preferquality = true -- quality over location - -local existers = allocate() figures.existers = existers -local checkers = allocate() figures.checkers = checkers -local includers = allocate() figures.includers = includers -local converters = allocate() figures.converters = converters -local identifiers = allocate() figures.identifiers = identifiers -local programs = allocate() figures.programs = programs +figures = figures or { } +local figures = figures + +figures.boxnumber = figures.boxnumber or 0 +figures.defaultsearch = true +figures.defaultwidth = 0 +figures.defaultheight = 0 +figures.defaultdepth = 0 +figures.nofprocessed = 0 +figures.preferquality = true -- quality over location + +local figures_loaded = allocate() figures.loaded = figures_loaded +local figures_used = allocate() figures.used = figures_used +local figures_found = allocate() figures.found = figures_found +local figures_suffixes = allocate() figures.suffixes = figures_suffixes +local figures_patterns = allocate() figures.patterns = figures_patterns +local figures_resources = allocate() figures.resources = figures_resources + +local existers = allocate() figures.existers = existers +local checkers = allocate() figures.checkers = checkers +local includers = allocate() figures.includers = includers +local converters = allocate() figures.converters = converters +local identifiers = allocate() figures.identifiers = identifiers +local programs = allocate() figures.programs = programs + +local defaultformat = "pdf" +local defaultprefix = "m_k_i_v_" figures.localpaths = allocate { ".", "..", "../.." } figures.cachepaths = allocate { - prefix = "", - path = ".", + prefix = "", + path = ".", subpath = ".", } -figures.paths = allocate(table.copy(figures.localpaths)) +local figure_paths = allocate(table.copy(figures.localpaths)) +figures.paths = figure_paths -figures.order = allocate{ +local figures_order = allocate { "pdf", "mps", "jpg", "png", "jp2", "jbig", "svg", "eps", "tif", "gif", "mov", "buffer", "tex", "cld", "auto", } -local formats = allocate { +local figures_formats = allocate { -- magic and order will move here ["pdf"] = { list = { "pdf" } }, ["mps"] = { patterns = { "mps", "%d+" } }, ["jpg"] = { list = { "jpg", "jpeg" } }, - ["jp2"] = { list = { "jp2" } }, ["png"] = { list = { "png" } }, + ["jp2"] = { list = { "jp2" } }, ["jbig"] = { list = { "jbig", "jbig2", "jb2" } }, ["svg"] = { list = { "svg", "svgz" } }, ["eps"] = { list = { "eps", "ai" } }, @@ -176,7 +212,7 @@ local formats = allocate { ["auto"] = { list = { "auto" } }, } -local magics = allocate { +local figures_magics = allocate { { format = "png", pattern = P("\137PNG\013\010\026\010") }, -- 89 50 4E 47 0D 0A 1A 0A, { format = "jpg", pattern = P("\255\216\255") }, -- FF D8 FF { format = "jp2", pattern = P("\000\000\000\012\106\080\032\032\013\010"), }, -- 00 00 00 0C 6A 50 20 20 0D 0A }, @@ -184,62 +220,92 @@ local magics = allocate { { format = "pdf", pattern = (1 - P("%PDF"))^0 * P("%PDF") }, } -figures.formats = formats -- frozen -figures.magics = magics -- frozen +figures.formats = figures_formats -- frozen +figures.magics = figures_magics -- frozen +figures.order = figures_order -- frozen + +-- We can set the order but only indirectly so that we can check for support. + +function figures.setorder(list) -- can be table or string + if type(list) == "string" then + list = settings_to_array(list) + end + if list and #list > 0 then + figures_order = allocate() + figures.order = figures_order + local done = { } -- just to be sure in case the list is generated + for i=1,#list do + local l = lower(list[i]) + if figures_formats[l] and not done[l] then + figures_order[#figures_order+1] = l + done[l] = true + end + end + report_inclusion("lookup order % a",figures_order) + else + -- invalid list + end +end function figures.guess(filename) local f = io.open(filename,'rb') if f then local str = f:read(100) f:close() - for i=1,#magics do - local pattern = magics[i] - if pattern.pattern:match(str) then - local format = pattern.format - if trace_figures then - report_inclusion("file %q has format %s",filename,format) + if str then + for i=1,#figures_magics do + local pattern = figures_magics[i] + if lpegmatch(pattern.pattern,str) then + local format = pattern.format + if trace_figures then + report_inclusion("file %a has format %a",filename,format) + end + return format end - return format end end end end -function figures.setlookups() -- tobe redone .. just set locals - local fs, fp = allocate(), allocate() - figures.suffixes, figures.patterns = fs, fp - for _, format in next, figures.order do - local data = formats[format] +local function setlookups() -- tobe redone .. just set locals + figures_suffixes = allocate() + figures_patterns = allocate() + for _, format in next, figures_order do + local data = figures_formats[format] local list = data.list if list then for i=1,#list do - fs[list[i]] = format -- hash + figures_suffixes[list[i]] = format -- hash end else - fs[format] = format + figures_suffixes[format] = format end local patterns = data.patterns if patterns then for i=1,#patterns do - fp[#fp+1] = { patterns[i], format } -- array + figures_patterns[#figures_patterns+1] = { patterns[i], format } -- array end end end + figures.suffixes = figures_suffixes + figures.patterns = figures_patterns end -figures.setlookups() +setlookups() + +figures.setlookups = setlookups function figures.registerresource(t) - local n = #figures.resources + 1 - figures.resources[n] = t + local n = #figures_resources + 1 + figures_resources[n] = t return n end local function register(tag,target,what) - local data = formats[target] -- resolver etc + local data = figures_formats[target] -- resolver etc if not data then data = { } - formats[target] = data + figures_formats[target] = data end local d = data[tag] -- list or pattern if d and not contains(d,what) then @@ -247,34 +313,35 @@ local function register(tag,target,what) else data[tag] = { what } end - if not contains(figures.order,target) then - figures.order[#figures.order+1] = target + if not contains(figures_order,target) then + figures_order[#figures_order+1] = target end - figures.setlookups() + setlookups() end function figures.registersuffix (suffix, target) register('list', target,suffix ) end function figures.registerpattern(pattern,target) register('pattern',target,pattern) end -local last_locationset, last_pathlist = last_locationset or nil, last_pathlist or nil +local last_locationset = last_locationset or nil +local last_pathlist = last_pathlist or nil function figures.setpaths(locationset,pathlist) if last_locationset == locationset and last_pathlist == pathlist then -- this function can be called each graphic so we provide this optimization return end - local iv, t, h = interfaces.variables, figures.paths, settings_to_hash(locationset) + local t, h = figure_paths, settings_to_hash(locationset) if last_locationset ~= locationset then -- change == reset (actually, a 'reset' would indeed reset - if h[iv["local"]] then + if h[v_local] then t = table.fastcopy(figures.localpaths or { }) else t = { } end - figures.defaultsearch = h[iv["default"]] + figures.defaultsearch = h[v_default] last_locationset = locationset end - if h[iv["global"]] then + if h[v_global] then local list = settings_to_array(pathlist) for i=1,#list do local s = list[i] @@ -283,18 +350,20 @@ function figures.setpaths(locationset,pathlist) end end end - figures.paths, last_pathlist = t, pathlist + figure_paths = t + last_pathlist = pathlist + figures.paths = figure_paths if trace_figures then - report_inclusion("locations: %s",last_locationset) - report_inclusion("path list: %s",concat(figures.paths, " ")) + report_inclusion("using locations %a",last_locationset) + report_inclusion("using paths % a",figure_paths) end end -- check conversions and handle it here function figures.hash(data) - return data.status.hash or tostring(data.status.private) -- the <img object> --- return data.status.fullname .. "+".. (data.status.page or data.request.page or 1) -- img is still not perfect + local status = data and data.status + return (status and status.hash or tostring(status.private)) or "nohash" -- the <img object> end -- interfacing to tex @@ -353,19 +422,21 @@ local callstack = { } function figures.initialize(request) local figuredata = new() if request then - -- request.width/height are strings and are only used when no natural dimensions - -- can be determined; at some point the handlers might set them to numbers instead - -- local w, h = tonumber(request.width), tonumber(request.height) - request.page = math.max(tonumber(request.page) or 1,1) - request.size = img.checksize(request.size) - request.object = request.object == variables.yes - request["repeat"] = request["repeat"] == variables.yes - request.preview = request.preview == variables.yes - request.cache = request.cache ~= "" and request.cache - request.prefix = request.prefix ~= "" and request.prefix - request.format = request.format ~= "" and request.format - -- request.width = (w and w > 0) or false - -- request.height = (h and h > 0) or false + -- request.width/height are strings and are only used when no natural dimensions + -- can be determined; at some point the handlers might set them to numbers instead + local w = tonumber(request.width) or 0 + local h = tonumber(request.height) or 0 + request.width = w > 0 and w or nil + request.height = h > 0 and h or nil + -- + request.page = math.max(tonumber(request.page) or 1,1) + request.size = img.checksize(request.size) + request.object = request.object == v_yes + request["repeat"] = request["repeat"] == v_yes + request.preview = request.preview == v_yes + request.cache = request.cache ~= "" and request.cache + request.prefix = request.prefix ~= "" and request.prefix + request.format = request.format ~= "" and request.format table.merge(figuredata.request,request) end return figuredata @@ -388,7 +459,7 @@ function figures.current() return callstack[#callstack] or lastfiguredata end -function figures.get(category,tag,default) +local function get(category,tag,default) local value = lastfiguredata and lastfiguredata[category] value = value and value[tag] if not value or value == "" or value == true then @@ -398,14 +469,19 @@ function figures.get(category,tag,default) end end --- +figures.get = get -function figures.tprint(category,tag,default) - context(figures.get(category,tag,default)) +function commands.figurevariable(category,tag,default) + context(get(category,tag,default)) end -local defaultformat = "pdf" -local defaultprefix = "m_k_i_v_" +function commands.figurestatus (tag,default) context(get("status", tag,default)) end +function commands.figurerequest(tag,default) context(get("request",tag,default)) end +function commands.figureused (tag,default) context(get("used", tag,default)) end + +function commands.figurefilepath() context(file.dirname (get("used","fullname"))) end +function commands.figurefilename() context(file.nameonly(get("used","fullname"))) end +function commands.figurefiletype() context(file.extname (get("used","fullname"))) end -- todo: local path or cache path @@ -413,217 +489,249 @@ local function forbiddenname(filename) if not filename or filename == "" then return false end - local expandedfullname = file.collapsepath(filename,true) - local expandedinputname = file.collapsepath(file.addsuffix(environment.jobfilename,environment.jobfilesuffix),true) + local expandedfullname = collapsepath(filename,true) + local expandedinputname = collapsepath(file.addsuffix(environment.jobfilename,environment.jobfilesuffix),true) if expandedfullname == expandedinputname then - report_inclusion("skipping graphic with same name as input filename (%s), enforce suffix",expandedinputname) + report_inclusion("skipping graphic with same name as input filename %a, enforce suffix",expandedinputname) return true end - local expandedoutputname = file.collapsepath(codeinjections.getoutputfilename(),true) + local expandedoutputname = collapsepath(codeinjections.getoutputfilename(),true) if expandedfullname == expandedoutputname then - report_inclusion("skipping graphic with same name as output filename (%s), enforce suffix",expandedoutputname) + report_inclusion("skipping graphic with same name as output filename %a, enforce suffix",expandedoutputname) return true end end local function register(askedname,specification) - if specification then - if forbiddenname(specification.fullname) then - specification = { } - else - local format = specification.format - if format then - local conversion = specification.conversion - local resolution = specification.resolution - if conversion == "" then - conversion = nil - end - if resolution == "" then - resolution = nil - end - local newformat = conversion - if not newformat or newformat == "" then + if not specification then + specification = { } + elseif forbiddenname(specification.fullname) then + specification = { } + else + local format = specification.format + if format then + local conversion = specification.conversion + local resolution = specification.resolution + if conversion == "" then + conversion = nil + end + if resolution == "" then + resolution = nil + end + local newformat = conversion + if not newformat or newformat == "" then + newformat = defaultformat + end + if trace_conversion then + report_inclusion("checking conversion of %a, fullname %a, old format %a, new format %a, conversion %a, resolution %a", + askedname,specification.fullname,format,newformat,conversion or "default",resolution or "default") + end + -- quick hack + local converter = (newformat ~= format or resolution) and converters[format] + if converter then + if converter[newformat] then + converter = converter[newformat] + else newformat = defaultformat - end - if trace_conversion then - report_inclusion("checking conversion of '%s' (%s): old format '%s', new format '%s', conversion '%s', resolution '%s'", - askedname,specification.fullname,format,newformat,conversion or "default",resolution or "default") - end - -- quick hack - -- local converter = (newformat ~= format) and converters[format] - local converter = (newformat ~= format or resolution) and converters[format] - if converter then if converter[newformat] then converter = converter[newformat] else + converter = nil newformat = defaultformat - if converter[newformat] then - converter = converter[newformat] - else - converter = nil - newformat = defaultformat - end end - elseif trace_conversion then - report_inclusion("no converter for '%s' -> '%s'",format,newformat) end - if converter then - local oldname = specification.fullname - local newpath = file.dirname(oldname) - local oldbase = file.basename(oldname) - -- - -- problem: we can have weird filenames, like a.b.c (no suffix) and a.b.c.gif - -- so we cannot safely remove a suffix (unless we do that for known suffixes) - -- - -- local newbase = file.removesuffix(oldbase) -- assumes a known suffix - -- - -- so we now have (also see *): - -- - local newbase = oldbase - -- - local fc = specification.cache or figures.cachepaths.path - if fc and fc ~= "" and fc ~= "." then - newpath = fc - else - newbase = defaultprefix .. newbase - end - if not file.is_writable(newpath) then - if trace_conversion then - report_inclusion("path '%s'is not writable, forcing conversion path '.' ",newpath) - end - newpath = "." - end - local subpath = specification.subpath or figures.cachepaths.subpath - if subpath and subpath ~= "" and subpath ~= "." then - newpath = newpath .. "/" .. subpath - end - local prefix = specification.prefix or figures.cachepaths.prefix - if prefix and prefix ~= "" then - newbase = prefix .. newbase + elseif trace_conversion then + report_inclusion("no converter for %a to %a",format,newformat) + end + if converter then + local oldname = specification.fullname + local newpath = file.dirname(oldname) + local oldbase = file.basename(oldname) + -- + -- problem: we can have weird filenames, like a.b.c (no suffix) and a.b.c.gif + -- so we cannot safely remove a suffix (unless we do that for known suffixes) + -- + -- local newbase = file.removesuffix(oldbase) -- assumes a known suffix + -- + -- so we now have (also see *): + -- + local newbase = oldbase + -- + local fc = specification.cache or figures.cachepaths.path + if fc and fc ~= "" and fc ~= "." then + newpath = fc + else + newbase = defaultprefix .. newbase + end + if not file.is_writable(newpath) then + if trace_conversion then + report_inclusion("path %a is not writable, forcing conversion path %a",newpath,".") end - if resolution and resolution ~= "" then -- the order might change - newbase = newbase .. "_" .. resolution + newpath = "." + end + local subpath = specification.subpath or figures.cachepaths.subpath + if subpath and subpath ~= "" and subpath ~= "." then + newpath = newpath .. "/" .. subpath + end + local prefix = specification.prefix or figures.cachepaths.prefix + if prefix and prefix ~= "" then + newbase = prefix .. newbase + end + if resolution and resolution ~= "" then -- the order might change + newbase = newbase .. "_" .. resolution + end + -- + -- see *, we had: + -- + -- local newbase = file.addsuffix(newbase,newformat) + -- + -- but now have (result of Aditya's web image testing): + -- + -- as a side effect we can now have multiple fetches with different + -- original figures_formats, not that it matters much (apart from older conversions + -- sticking around) + -- + local newbase = newbase .. "." .. newformat + -- + local newname = file.join(newpath,newbase) + dir.makedirs(newpath) + oldname = collapsepath(oldname) + newname = collapsepath(newname) + local oldtime = lfs.attributes(oldname,'modification') or 0 + local newtime = lfs.attributes(newname,'modification') or 0 + if newtime == 0 or oldtime > newtime then + if trace_conversion then + report_inclusion("converting %a (%a) from %a to %a",askedname,oldname,format,newformat) end - -- - -- see *, we had: - -- - -- local newbase = file.addsuffix(newbase,newformat) - -- - -- but now have (result of Aditya's web image testing): - -- - -- as a side effect we can now have multiple fetches with different - -- original formats, not that it matters much (apart from older conversions - -- sticking around) - -- - local newbase = newbase .. "." .. newformat - -- - local newname = file.join(newpath,newbase) - dir.makedirs(newpath) - oldname = file.collapsepath(oldname) - newname = file.collapsepath(newname) - local oldtime = lfs.attributes(oldname,'modification') or 0 - local newtime = lfs.attributes(newname,'modification') or 0 - if newtime == 0 or oldtime > newtime then - if trace_conversion then - report_inclusion("converting '%s' (%s) from '%s' to '%s'",askedname,oldname,format,newformat) - end - converter(oldname,newname,resolution or "") - else - if trace_conversion then - report_inclusion("no need to convert '%s' (%s) from '%s' to '%s'",askedname,oldname,format,newformat) - end + converter(oldname,newname,resolution or "") + else + if trace_conversion then + report_inclusion("no need to convert %a (%a) from %a to %a",askedname,oldname,format,newformat) end - if io.exists(newname) and io.size(newname) > 0 then - specification.foundname = oldname - specification.fullname = newname - specification.prefix = prefix - specification.subpath = subpath - specification.converted = true - format = newformat - if not figures.suffixes[format] then - -- maybe the new format is lowres.png (saves entry in suffixes) - -- so let's do thsi extra check - local suffix = file.suffix(newformat) - if figures.suffixes[suffix] then - if trace_figures then - report_inclusion("using suffix '%s' as format for '%s'",suffix,format) - end - format = suffix + end + if io.exists(newname) and io.size(newname) > 0 then + specification.foundname = oldname + specification.fullname = newname + specification.prefix = prefix + specification.subpath = subpath + specification.converted = true + format = newformat + if not figures_suffixes[format] then + -- maybe the new format is lowres.png (saves entry in suffixes) + -- so let's do thsi extra check + local suffix = file.suffix(newformat) + if figures_suffixes[suffix] then + if trace_figures then + report_inclusion("using suffix %a as format for %a",suffix,format) end + format = suffix end - elseif io.exists(oldname) then - specification.fullname = oldname -- was newname - specification.converted = false end + elseif io.exists(oldname) then + specification.fullname = oldname -- was newname + specification.converted = false end end - local found = figures.suffixes[format] -- validtypes[format] - if not found then - specification.found = false - if trace_figures then - report_inclusion("format not supported: %s",format) - end - else - specification.found = true - if trace_figures then - if validtypes[format] then -- format? - report_inclusion("format natively supported by backend: %s",format) - else - report_inclusion("format supported by output file format: %s",format) - end + end + local found = figures_suffixes[format] -- validtypes[format] + if not found then + specification.found = false + if trace_figures then + report_inclusion("format %a is not supported",format) + end + else + specification.found = true + if trace_figures then + if validtypes[format] then -- format? + report_inclusion("format %a natively supported by backend",format) + else + report_inclusion("format %a supported by output file format",format) end end end - else - specification = { } end specification.foundname = specification.foundname or specification.fullname - figures.found[askedname .. "->" .. (specification.conversion or "default") .. "->" .. (specification.resolution or "default")] = specification + local askedhash = f_hash_part(askedname,specification.conversion or "default",specification.resolution or "default") + figures_found[askedhash] = specification return specification end -local resolve_too = true -- urls +local resolve_too = false -- true + +local internalschemes = { + file = true, +} local function locate(request) -- name, format, cache -- not resolvers.cleanpath(request.name) as it fails on a!b.pdf and b~c.pdf -- todo: more restricted cleanpath local askedname = request.name - local foundname = figures.found[askedname .. "->" .. (request.conversion or "default") .. "->" .. (request.resolution or "default")] + local askedhash = f_hash_part(askedname,request.conversion or "default",request.resolution or "default") + local foundname = figures_found[askedhash] if foundname then return foundname end + -- + local askedcache = request.cache + local askedconversion = request.conversion + local askedresolution = request.resolution + -- + if request.format == "" or request.format == "unknown" then + request.format = nil + end -- protocol check local hashed = url.hashed(askedname) - if hashed then - if hashed.scheme == "file" then - local path = hashed.path - if path and path ~= "" then - askedname = path + if not hashed then + -- go on + elseif internalschemes[hashed.scheme] then + local path = hashed.path + if path and path ~= "" then + askedname = path + end + else + local foundname = resolvers.findbinfile(askedname) + if not foundname or not lfs.isfile(foundname) then -- foundname can be dummy + if trace_figures then + report_inclusion("unknown url %a",askedname) end + -- url not found + return register(askedname) + end + local askedformat = request.format or file.suffix(askedname) or "" + local guessedformat = figures.guess(foundname) + if askedformat ~= guessedformat then + if trace_figures then + report_inclusion("url %a has unknown format",askedname) + end + -- url found, but wrong format + return register(askedname) else - local foundname = resolvers.findbinfile(askedname) - if foundname then - askedname = foundname + if trace_figures then + report_inclusion("url %a is resolved to %a",askedname,foundname) end + return register(askedname, { + askedname = askedname, + fullname = foundname, + format = askedformat, + cache = askedcache, + conversion = askedconversion, + resolution = askedresolution, + }) end end -- we could use the hashed data instead local askedpath= file.is_rootbased_path(askedname) local askedbase = file.basename(askedname) - local askedformat = (request.format ~= "" and request.format ~= "unknown" and request.format) or file.extname(askedname) or "" - local askedcache = request.cache - local askedconversion = request.conversion - local askedresolution = request.resolution + local askedformat = request.format or file.suffix(askedname) or "" if askedformat ~= "" then askedformat = lower(askedformat) if trace_figures then - report_inclusion("strategy: forced format %s",askedformat) + report_inclusion("forcing format %a",askedformat) end - local format = figures.suffixes[askedformat] + local format = figures_suffixes[askedformat] if not format then - local figurepatterns = figures.patterns - for i=1,#figurepatterns do - local pattern = figurepatterns[i] + for i=1,#figures_patterns do + local pattern = figures_patterns[i] if find(askedformat,pattern[1]) then format = pattern[2] break @@ -646,7 +754,7 @@ local function locate(request) -- name, format, cache return register(askedname) end elseif trace_figures then - report_inclusion("strategy: unknown format %s",askedformat) + report_inclusion("unknown format %a",askedformat) end if askedpath then -- path and type given, todo: strip pieces of path @@ -663,9 +771,8 @@ local function locate(request) -- name, format, cache end else -- type given - local figurepaths = figures.paths - for i=1,#figurepaths do - local path = figurepaths[i] + for i=1,#figure_paths do + local path = figure_paths[i] local check = path .. "/" .. askedname -- we pass 'true' as it can be an url as well, as the type -- is given we don't waste much time @@ -697,12 +804,11 @@ local function locate(request) -- name, format, cache end elseif askedpath then if trace_figures then - report_inclusion("strategy: rootbased path") + report_inclusion("using rootbased path") end - local figureorder = figures.order - for i=1,#figureorder do - local format = figureorder[i] - local list = formats[format].list or { format } + for i=1,#figures_order do + local format = figures_order[i] + local list = figures_formats[format].list or { format } for j=1,#list do local suffix = list[j] local check = file.addsuffix(askedname,suffix) @@ -722,27 +828,25 @@ local function locate(request) -- name, format, cache else if figures.preferquality then if trace_figures then - report_inclusion("strategy: unknown format, prefer quality") + report_inclusion("unknown format, quality preferred") end - local figurepaths = figures.paths - local figureorder = figures.order - for j=1,#figureorder do - local format = figureorder[j] - local list = formats[format].list or { format } + for j=1,#figures_order do + local format = figures_order[j] + local list = figures_formats[format].list or { format } for k=1,#list do local suffix = list[k] -- local name = file.replacesuffix(askedbase,suffix) local name = file.replacesuffix(askedname,suffix) - for i=1,#figurepaths do - local path = figurepaths[i] + for i=1,#figure_paths do + local path = figure_paths[i] local check = path .. "/" .. name local isfile = url.hashed(check).scheme == "file" if not isfile then if trace_figures then - report_inclusion("warning: skipping path %s",path) + report_inclusion("warning: skipping path %a",path) end else - local foundname, quitscanning, forcedformat = figures.exists(check,format,true) + local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too) -- true) if foundname then return register(askedname, { askedname = askedname, @@ -759,15 +863,13 @@ local function locate(request) -- name, format, cache end else -- 'location' if trace_figures then - report_inclusion("strategy: unknown format, prefer path") + report_inclusion("unknown format, using path strategy") end - local figurepaths = figures.paths - local figureorder = figures.order - for i=1,#figurepaths do - local path = figurepaths[i] - for j=1,#figureorder do - local format = figureorder[j] - local list = formats[format].list or { format } + for i=1,#figure_paths do + local path = figure_paths[i] + for j=1,#figures_order do + local format = figures_order[j] + local list = figures_formats[format].list or { format } for k=1,#list do local suffix = list[k] local check = path .. "/" .. file.replacesuffix(askedbase,suffix) @@ -788,12 +890,11 @@ local function locate(request) -- name, format, cache end if figures.defaultsearch then if trace_figures then - report_inclusion("strategy: default tex path") + report_inclusion("using default tex path") end - local figureorder = figures.order - for j=1,#figureorder do - local format = figureorder[j] - local list = formats[format].list or { format } + for j=1,#figures_order do + local format = figures_order[j] + local list = figures_formats[format].list or { format } for k=1,#list do local suffix = list[k] local check = resolvers.findfile(file.replacesuffix(askedname,suffix)) @@ -835,7 +936,7 @@ function identifiers.default(data) end function figures.identify(data) - data = data or figures.current() + data = data or callstack[#callstack] or lastfiguredata local list = identifiers.list -- defined at the end for i=1,#list do local identifier = list[i] @@ -852,12 +953,12 @@ function figures.exists(askedname,format,resolve) end function figures.check(data) - data = data or figures.current() + data = data or callstack[#callstack] or lastfiguredata return (checkers[data.status.format] or checkers.generic)(data) end function figures.include(data) - data = data or figures.current() + data = data or callstack[#callstack] or lastfiguredata return (includers[data.status.format] or includers.generic)(data) end @@ -868,8 +969,7 @@ end function figures.done(data) figures.nofprocessed = figures.nofprocessed + 1 - data = data or figures.current() ---~ print(table.serialize(figures.current())) + data = data or callstack[#callstack] or lastfiguredata local dr, du, ds, nr = data.request, data.used, data.status, figures.boxnumber local box = texbox[nr] ds.width = box.width @@ -877,18 +977,17 @@ function figures.done(data) ds.xscale = ds.width /(du.width or 1) ds.yscale = ds.height/(du.height or 1) ds.page = ds.page or du.page or dr.page -- sort of redundant but can be limited ---~ print(table.serialize(figures.current())) return data end function figures.dummy(data) - data = data or figures.current() + data = data or callstack[#callstack] or lastfiguredata local dr, du, nr = data.request, data.used, figures.boxnumber - local box = node.hpack(node.new("hlist")) -- we need to set the dir (luatex 0.60 buglet) - du.width = du.width or figures.defaultwidth - du.height = du.height or figures.defaultheight - du.depth = du.depth or figures.defaultdepth - -- box.dir = "TLT" + local box = node.hpack(node.new("hlist")) -- we need to set the dir (luatex 0.60 buglet) + du.width = du.width or figures.defaultwidth + du.height = du.height or figures.defaultheight + du.depth = du.depth or figures.defaultdepth + -- box.dir = "TLT" box.width = du.width box.height = du.height box.depth = du.depth @@ -908,9 +1007,9 @@ function existers.generic(askedname,resolve) end if trace_figures then if result then - report_inclusion("found: %s -> %s",askedname,result) + report_inclusion("%a resolved to %a",askedname,result) else - report_inclusion("not found: %s",askedname) + report_inclusion("%a cannot be resolved",askedname) end end return result @@ -931,8 +1030,8 @@ function checkers.generic(data) if not resolution or resolution == "" then resolution = "unknown" end - local hash = name .. "->" .. page .. "->" .. size .. "->" .. color .. "->" .. conversion .. "->" .. resolution .. "->" .. mask - local figure = figures.loaded[hash] + local hash = f_hash_full(name,page,size,color,conversion,resolution,mask) + local figure = figures_loaded[hash] if figure == nil then figure = img.new { filename = name, @@ -942,30 +1041,30 @@ function checkers.generic(data) } codeinjections.setfigurecolorspace(data,figure) codeinjections.setfiguremask(data,figure) - figure = (figure and img.scan(figure)) or false + figure = figure and img.check(img.scan(figure)) or false local f, d = codeinjections.setfigurealternative(data,figure) figure, data = f or figure, d or data - figures.loaded[hash] = figure + figures_loaded[hash] = figure if trace_conversion then - report_inclusion("new graphic, hash: %s",hash) + report_inclusion("new graphic, using hash %a",hash) end else if trace_conversion then - report_inclusion("existing graphic, hash: %s",hash) + report_inclusion("existing graphic, using hash %a",hash) end end if figure then - du.width = figure.width - du.height = figure.height - du.pages = figure.pages - du.depth = figure.depth or 0 - du.colordepth = figure.colordepth or 0 + du.width = figure.width + du.height = figure.height + du.pages = figure.pages + du.depth = figure.depth or 0 + du.colordepth = figure.colordepth or 0 du.xresolution = figure.xres or 0 du.yresolution = figure.yres or 0 - du.xsize = figure.xsize or 0 - du.ysize = figure.ysize or 0 - ds.private = figure - ds.hash = hash + du.xsize = figure.xsize or 0 + du.ysize = figure.ysize or 0 + ds.private = figure + ds.hash = hash end return data end @@ -976,19 +1075,19 @@ function includers.generic(data) dr.width = du.width dr.height = du.height local hash = figures.hash(data) - local figure = figures.used[hash] ---~ figures.registerresource { ---~ filename = du.fullname, ---~ width = dr.width, ---~ height = dr.height, ---~ } + local figure = figures_used[hash] + -- figures.registerresource { + -- filename = du.fullname, + -- width = dr.width, + -- height = dr.height, + -- } if figure == nil then figure = ds.private if figure then figure = img.copy(figure) - figure = (figure and img.clone(figure,data.request)) or false + figure = figure and img.clone(figure,data.request) or false end - figures.used[hash] = figure + figures_used[hash] = figure end if figure then local nr = figures.boxnumber @@ -1005,7 +1104,7 @@ end -- -- -- nongeneric -- -- -- -function checkers.nongeneric(data,command) -- todo: macros and context.* +local function checkers_nongeneric(data,command) -- todo: macros and context.* local dr, du, ds = data.request, data.used, data.status local name = du.fullname or "unknown nongeneric" local hash = name @@ -1024,10 +1123,13 @@ function checkers.nongeneric(data,command) -- todo: macros and context.* return data end -function includers.nongeneric(data) +local function includers_nongeneric(data) return data end +checkers.nongeneric = checkers_nongeneric +includers.nongeneric = includers_nongeneric + -- -- -- mov -- -- -- function checkers.mov(data) @@ -1038,7 +1140,7 @@ function checkers.mov(data) dr.width, dr.height = width, height du.width, du.height, du.foundname = width, height, foundname if trace_inclusion then - report_inclusion("including movie '%s': width %s, height %s",foundname,width,height) + report_inclusion("including movie %a, width %p, height %p",foundname,width,height) end -- we need to push the node.write in between ... we could make a shared helper for this context.startfoundexternalfigure(width .. "sp",height .. "sp") @@ -1062,9 +1164,11 @@ includers.mov = includers.nongeneric -- -- -- mps -- -- -- +internalschemes.mprun = true + local function internal(askedname) - local spec, mprun, mpnum = match(lower(askedname),"mprun(:?)(.-)%.(%d+)") - if spec == ":" then + local spec, mprun, mpnum = match(lower(askedname),"mprun([:%.]?)(.-)%.(%d+)") + if spec ~= "" then return mprun, mpnum else return "", mpnum @@ -1083,9 +1187,9 @@ end function checkers.mps(data) local mprun, mpnum = internal(data.used.fullname) if mpnum then - return checkers.nongeneric(data,function() context.docheckfiguremprun(mprun,mpnum) end) + return checkers_nongeneric(data,function() context.docheckfiguremprun(mprun,mpnum) end) else - return checkers.nongeneric(data,function() context.docheckfiguremps(data.used.fullname) end) + return checkers_nongeneric(data,function() context.docheckfiguremps(data.used.fullname) end) end end @@ -1095,11 +1199,11 @@ includers.mps = includers.nongeneric function existers.tex(askedname) askedname = resolvers.findfile(askedname) - return (askedname ~= "" and askedname) or false + return askedname ~= "" and askedname or false end function checkers.tex(data) - return checkers.nongeneric(data,function() context.docheckfiguretex(data.used.fullname) end) + return checkers_nongeneric(data,function() context.docheckfiguretex(data.used.fullname) end) end includers.tex = includers.nongeneric @@ -1113,7 +1217,7 @@ function existers.buffer(askedname) end function checkers.buffer(data) - return checkers.nongeneric(data,function() context.docheckfigurebuffer(file.nameonly(data.used.fullname)) end) + return checkers_nongeneric(data,function() context.docheckfigurebuffer(file.nameonly(data.used.fullname)) end) end includers.buffers = includers.nongeneric @@ -1124,9 +1228,9 @@ function existers.auto(askedname) local name = gsub(askedname, ".auto$", "") local format = figures.guess(name) if format then - report_inclusion("format guess for %q: %s",name,format) + report_inclusion("format guess %a for %a",format,name) else - report_inclusion("format guess for %q is not possible",name) + report_inclusion("format guess for %a is not possible",name) end return format and name, true, format end @@ -1139,7 +1243,7 @@ includers.auto = includers.generic existers.cld = existers.tex function checkers.cld(data) - return checkers.nongeneric(data,function() context.docheckfigurecld(data.used.fullname) end) + return checkers_nongeneric(data,function() context.docheckfigurecld(data.used.fullname) end) end includers.cld = includers.nongeneric @@ -1151,182 +1255,231 @@ local function makeoptions(options) return (to == "table" and concat(options," ")) or (to == "string" and options) or "" end -local function runprogram(template,binary,...) - local command = format(template,binary,...) +-- programs.makeoptions = makeoptions + +local function runprogram(binary,argument,variables) local binary = match(binary,"[%S]+") -- to be sure - if os.which(binary) then + if type(argument) == "table" then + argument = concat(argument," ") -- for old times sake + end + if not os.which(binary) then + report_inclusion("program %a is not installed, not running command: %s",binary,command) + elseif not argument or argument == "" then + report_inclusion("nothing to run, unknown program %a",binary) + else + local command = format([["%s" %s]],binary,replacetemplate(longtostring(argument),variables)) if trace_conversion or trace_programs then - report_inclusion("running: %s",command) + report_inclusion("running command: %s",command) end os.spawn(command) - else - report_inclusion("program '%s' is not installed, not running: %s",binary,command) end end --- -- -- eps -- -- -- +programs.run = runprogram -local epsconverter = { } -converters.eps = epsconverter +-- -- -- eps & pdf -- -- -- +-- +-- \externalfigure[cow.eps] +-- \externalfigure[cow.pdf][conversion=stripped] + +local epsconverter = converters.eps or { } +converters.eps = epsconverter +converters.ps = epsconverter -programs.gs = { +local epstopdf = { resolutions = { - [variables.low] = "screen", - [variables.medium] = "ebook", - [variables.high] = "prepress", + [v_low] = "screen", + [v_medium] = "ebook", + [v_high] = "prepress", }, - options = { - "-dAutoRotatePages=/None", - "-dPDFSETTINGS=/%s", - "-dEPSCrop", - }, - command = (os.type == "windows" and "gswin32c") or "gs" + command = os.type == "windows" and "gswin32c" or "gs", + -- -dProcessDSCComments=false + argument = [[ + -q + -sDEVICE=pdfwrite + -dNOPAUSE + -dNOCACHE + -dBATCH + -dAutoRotatePages=/None + -dPDFSETTINGS=/%presets% + -dEPSCrop + -sOutputFile=%newname% + %oldname% + -c quit + ]], } +programs.epstopdf = epstopdf +programs.gs = epstopdf + function epsconverter.pdf(oldname,newname,resolution) -- the resolution interface might change - local gs = programs.gs - runprogram ( - '%s -q -sDEVICE=pdfwrite -dNOPAUSE -dNOCACHE -dBATCH %s -sOutputFile="%s" "%s" -c quit', - gs.command, - format(makeoptions(gs.options),gs.resolutions[resolution or ""] or "prepress"), - newname, - oldname - ) + local epstopdf = programs.epstopdf -- can be changed + local presets = epstopdf.resolutions[resolution or ""] or epstopdf.resolutions.high + runprogram(epstopdf.command, epstopdf.argument, { + newname = newname, + oldname = oldname, + presets = presets, + } ) end epsconverter.default = epsconverter.pdf +local pdfconverter = converters.pdf or { } +converters.pdf = pdfconverter + +programs.pdftoeps = { + command = "pdftops", + argument = [[-eps "%oldname%" "%newname%]], +} + +pdfconverter.stripped = function(oldname,newname) + local pdftoeps = programs.pdftoeps -- can be changed + local epstopdf = programs.epstopdf -- can be changed + local presets = epstopdf.resolutions[resolution or ""] or epstopdf.resolutions.high + local tmpname = newname .. ".tmp" + runprogram(pdftoeps.command, pdftoeps.argument, { oldname = oldname, newname = tmpname, presets = presets }) + runprogram(epstopdf.command, epstopdf.argument, { oldname = tmpname, newname = newname, presets = presets }) + os.remove(tmpname) +end + +figures.registersuffix("stripped","pdf") + -- -- -- svg -- -- -- -local svgconverter = { } -converters.svg = svgconverter -converters.svgz = svgconverter +local svgconverter = { } +converters.svg = svgconverter +converters.svgz = svgconverter -- inkscape on windows only works with complete paths programs.inkscape = { - options = { - "--export-dpi=600" - }, - command = "inkscape" + command = "inkscape", + pdfargument = [[ + "%oldname%" + --export-dpi=600 + -A + "%newname%" + ]], + pngargument = [[ + "%oldname%" + --export-dpi=600 + --export-png="%newname%" + ]], } function svgconverter.pdf(oldname,newname) - local inkscape = programs.inkscape - local oldname = dir.expandname(oldname) - local newname = dir.expandname(newname) - runprogram ( - '%s "%s" %s -A "%s"', - inkscape.command, oldname, makeoptions(inkscape.options), newname - ) + local inkscape = programs.inkscape -- can be changed + runprogram(inkscape.command, inkscape.pdfargument, { + newname = expandfilename(newname), + oldname = expandfilename(oldname), + } ) end function svgconverter.png(oldname,newname) local inkscape = programs.inkscape - runprogram ( - '%s "%s" --export-png="%s" %s', - inkscape.command, oldname, newname, makeoptions(inkscape.options) - ) + runprogram(inkscape.command, inkscape.pngargument, { + newname = expandfilename(newname), + oldname = expandfilename(oldname), + } ) end svgconverter.default = svgconverter.pdf -- -- -- gif -- -- -- - -local gifconverter = { } -converters.gif = gifconverter - -programs.convert = { - command = "gm convert" -- graphicmagick -} - -function gifconverter.pdf(oldname,newname) - local convert = programs.convert - runprogram ( - "%s %s %s %s", - convert.command, makeoptions(convert.options), oldname, newname - ) -end - -gifconverter.default = gifconverter.pdf - -- -- -- tif -- -- -- --- http://sourceforge.net/projects/gnuwin32/files/tiff/3.8.2-1/tiff-3.8.2-1-bin.zip/download +local gifconverter = converters.gif or { } +local tifconverter = converters.tif or { } +local bmpconverter = converters.bmp or { } -local tifconverter = { } -converters.tif = tifconverter +converters.gif = gifconverter +converters.tif = tifconverter +converters.bmp = bmpconverter programs.convert = { ---~ command = "convert" -- imagemagick - command = "gm convert" -- graphicmagick ---~ command = "tiff2pdf" + command = "gm", -- graphicmagick + argument = [[convert "%oldname%" "%newname%"]], } -function tifconverter.pdf(oldname,newname) +local function converter(oldname,newname) local convert = programs.convert - runprogram ( - "%s %s %s %s", - convert.command, makeoptions(convert.options), oldname, newname ---~ "%s %s -z -o %s %s", ---~ convert.command, makeoptions(convert.options), newname, oldname - ) + runprogram(convert.command, convert.argument, { + newname = newname, + oldname = oldname, + } ) end -tifconverter.default = tifconverter.pdf +tifconverter.pdf = converter +gifconverter.pdf = converter +bmpconverter.pdf = converter + +gifconverter.default = converter +tifconverter.default = converter +bmpconverter.default = converter -- todo: lowres -- -- -- bases -- -- -- -figures.bases = { } -figures.bases.list = { } -- index => { basename, fullname, xmlroot } -figures.bases.used = { } -- [basename] => { basename, fullname, xmlroot } -- pointer to list -figures.bases.found = { } -figures.bases.enabled = false +local bases = allocate() +figures.bases = bases + +local bases_list = nil -- index => { basename, fullname, xmlroot } +local bases_used = nil -- [basename] => { basename, fullname, xmlroot } -- pointer to list +local bases_found = nil +local bases_enabled = false + +local function reset() + bases_list = allocate() + bases_used = allocate() + bases_found = allocate() + bases_enabled = false + bases.list = bases_list + bases.used = bases_used + bases.found = bases_found +end -local bases = figures.bases +reset() function bases.use(basename) if basename == "reset" then - bases.list, bases.used, bases.found, bases.enabled = { }, { }, { }, false + reset() else basename = file.addsuffix(basename,"xml") - if not bases.used[basename] then + if not bases_used[basename] then local t = { basename, nil, nil } - bases.used[basename] = t - bases.list[#bases.list+1] = t - if not bases.enabled then - bases.enabled = true + bases_used[basename] = t + bases_list[#bases_list+1] = t + if not bases_enabled then + bases_enabled = true xml.registerns("rlx","http://www.pragma-ade.com/schemas/rlx") -- we should be able to do this per xml file end if trace_bases then - report_inclusion("registering base '%s'",basename) + report_inclusion("registering base %a",basename) end end end end -function bases.find(basename,askedlabel) +local function bases_find(basename,askedlabel) if trace_bases then - report_inclusion("checking for '%s' in base '%s'",askedlabel,basename) + report_inclusion("checking for %a in base %a",askedlabel,basename) end basename = file.addsuffix(basename,"xml") - local t = bases.found[askedlabel] + local t = bases_found[askedlabel] if t == nil then - local base = bases.used[basename] + local base = bases_used[basename] local page = 0 if base[2] == nil then -- no yet located - local figurepaths = figures.paths - for i=1,#figurepaths do - local path = figurepaths[i] + for i=1,#figure_paths do + local path = figure_paths[i] local xmlfile = path .. "/" .. basename if io.exists(xmlfile) then base[2] = xmlfile base[3] = xml.load(xmlfile) if trace_bases then - report_inclusion("base '%s' loaded",xmlfile) + report_inclusion("base %a loaded",xmlfile) end break end @@ -1343,15 +1496,15 @@ function bases.find(basename,askedlabel) name = xml.text(e,"../*:file"), -- to be checked page = page, } - bases.found[askedlabel] = t + bases_found[askedlabel] = t if trace_bases then - report_inclusion("figure '%s' found in base '%s'",askedlabel,base[2]) + report_inclusion("figure %a found in base %a",askedlabel,base[2]) end return t end end if trace_bases and not t then - report_inclusion("figure '%s' not found in base '%s'",askedlabel,base[2]) + report_inclusion("figure %a not found in base %a",askedlabel,base[2]) end end end @@ -1360,11 +1513,10 @@ end -- we can access sequential or by name -function bases.locate(askedlabel) - local list = bases.list - for i=1,#list do - local entry = list[i] - local t = bases.find(entry[1],askedlabel) +local function bases_locate(askedlabel) + for i=1,#bases_list do + local entry = bases_list[i] + local t = bases_find(entry[1],askedlabel) if t then return t end @@ -1373,9 +1525,9 @@ function bases.locate(askedlabel) end function identifiers.base(data) - if bases.enabled then + if bases_enabled then local dr, du, ds = data.request, data.used, data.status - local fbl = bases.locate(dr.name or dr.label) + local fbl = bases_locate(dr.name or dr.label) if fbl then du.page = fbl.page du.format = fbl.format @@ -1389,6 +1541,9 @@ function identifiers.base(data) return data end +bases.locate = bases_locate +bases.find = bases_find + identifiers.list = { identifiers.base, identifiers.default @@ -1433,7 +1588,7 @@ function figures.applyratio(width,height,w,h) -- width and height are strings an end end --- example of a simple plugin: +-- example of simple plugins: -- -- figures.converters.png = { -- png = function(oldname,newname,resolution) @@ -1443,9 +1598,12 @@ end -- end, -- } +-- local fig = figures.push { name = pdffile } +-- figures.identify() +-- figures.check() +-- local nofpages = fig.used.pages +-- figures.pop() + +-- interfacing --- local fig = figures.push { name = pdffile } --- figures.identify() --- figures.check() --- local nofpages = fig.used.pages --- figures.pop() +commands.setfigurelookuporder = figures.setorder |