if not modules then modules = { } end modules ['grph-inc'] = { version = 1.001, comment = "companion to grph-inc.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", 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 --[[ The ConTeXt figure inclusion mechanisms are among the oldest code in ConTeXt and evolved into a complex whole. One reason is that we deal with backend in an abstract way. What complicates matters is that we deal with internal graphics as well: TeX code, MetaPost code, etc. Later on figure databases were introduced, which resulted in a plug in model for locating images. On top of that runs a conversion mechanism (with caching) and resource logging. Porting that to Lua is not that trivial because quite some status information is kept between al these stages. Of course, image reuse also has some price, and so I decided to implement the graphics inclusion in several layers: detection, loading, inclusion, etc. Object sharing and scaling can happen at each stage, depending on the way the resource is dealt with. The TeX-Lua mix is suboptimal. This has to do with the fact that we cannot run TeX code from within Lua. Some more functionality will move to Lua. ]]-- local format, lower, find, match, gsub, gmatch = string.format, string.lower, string.find, string.match, string.gsub, string.gmatch local texbox = tex.box local contains = table.contains local concat, insert, remove = table.concat, table.insert, table.remove local todimen = string.todimen local P = lpeg.P 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 variables = interfaces.variables local codeinjections = backends.codeinjections local nodeinjections = backends.nodeinjections local trace_figures = false trackers.register("graphics.locating", function(v) trace_figures = v end) local trace_bases = false trackers.register("graphics.bases", function(v) trace_bases = v end) local trace_programs = false trackers.register("graphics.programs", function(v) trace_programs = v end) local trace_conversion = false trackers.register("graphics.conversion", function(v) trace_conversion = v end) local trace_inclusion = false trackers.register("graphics.inclusion", function(v) trace_inclusion = v end) local report_inclusion = logs.reporter("graphics","inclusion") local context, img = context, img --- some extra img functions --- can become luat-img.lua local imgkeys = img.keys() function img.totable(imgtable) local result = { } for k=1,#imgkeys do local key = imgkeys[k] result[key] = imgtable[key] end return result end function img.serialize(i,...) return table.serialize(img.totable(i),...) end function img.print(i,...) return table.print(img.totable(i),...) end function img.clone(i,data) i.width = data.width or i.width i.height = data.height or i.height -- attr etc return i end local validsizes = table.tohash(img.boxes()) local validtypes = table.tohash(img.types()) function img.checksize(size) if size then size = gsub(size,"box","") return (validsizes[size] and size) or "crop" else return "crop" end end local indexed = { } function img.ofindex(n) return indexed[n] 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.localpaths = allocate { ".", "..", "../.." } figures.cachepaths = allocate { prefix = "", path = ".", subpath = ".", } figures.paths = allocate(table.copy(figures.localpaths)) local lookuporder = allocate { "pdf", "mps", "jpg", "png", "jp2", "jbig", "svg", "eps", "tif", "gif", "mov", "buffer", "tex", "cld", "auto", } local formats = allocate { -- magic and order will move here ["pdf"] = { list = { "pdf" } }, ["mps"] = { patterns = { "mps", "%d+" } }, ["jpg"] = { list = { "jpg", "jpeg" } }, ["png"] = { list = { "png" } }, ["jp2"] = { list = { "jp2" } }, ["jbig"] = { list = { "jbig", "jbig2", "jb2" } }, ["svg"] = { list = { "svg", "svgz" } }, ["eps"] = { list = { "eps", "ai" } }, ["gif"] = { list = { "gif" } }, ["tif"] = { list = { "tif", "tiff" } }, ["mov"] = { list = { "mov", "flv", "mp4" } }, -- "avi" is not supported ["buffer"] = { list = { "tmp", "buffer", "buf" } }, ["tex"] = { list = { "tex" } }, ["cld"] = { list = { "cld" } }, ["auto"] = { list = { "auto" } }, } local 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 }, { format = "gif", pattern = P("GIF") }, { format = "pdf", pattern = (1 - P("%PDF"))^0 * P("%PDF") }, } figures.formats = formats -- frozen figures.magics = magics -- 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 lookuporder = allocate() figures.order = lookuporder local done = { } -- just to be sure in case the list is generated for i=1,#list do local l = lower(list[i]) if formats[l] and not done[l] then lookuporder[#lookuporder+1] = l done[l] = true end end report_inclusion("lookup order: %s",concat(lookuporder," ")) 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() if str then 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) end return format end 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, lookuporder do local data = formats[format] local list = data.list if list then for i=1,#list do fs[list[i]] = format -- hash end else fs[format] = format end local patterns = data.patterns if patterns then for i=1,#patterns do fp[#fp+1] = { patterns[i], format } -- array end end end end figures.setlookups() function figures.registerresource(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 if not data then data = { } formats[target] = data end local d = data[tag] -- list or pattern if d and not contains(d,what) then d[#d+1] = what -- suffix or patternspec else data[tag] = { what } end if not contains(lookuporder,target) then lookuporder[#lookuporder+1] = target end figures.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 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) if last_locationset ~= locationset then -- change == reset (actually, a 'reset' would indeed reset if h[iv["local"]] then t = table.fastcopy(figures.localpaths or { }) else t = { } end figures.defaultsearch = h[iv["default"]] last_locationset = locationset end if h[iv["global"]] then local list = settings_to_array(pathlist) for i=1,#list do local s = list[i] if not contains(t,s) then t[#t+1] = s end end end figures.paths, last_pathlist = t, pathlist if trace_figures then report_inclusion("locations: %s",last_locationset) report_inclusion("path list: %s",concat(figures.paths, " ")) end end -- check conversions and handle it here function figures.hash(data) return data.status.hash or tostring(data.status.private) -- the -- return data.status.fullname .. "+".. (data.status.page or data.request.page or 1) -- img is still not perfect end -- interfacing to tex local function new() -- we could use metatables status -> used -> request but it needs testing local request = { name = false, label = false, format = false, page = false, width = false, height = false, preview = false, ["repeat"] = false, controls = false, display = false, mask = false, conversion = false, resolution = false, cache = false, prefix = false, size = false, } local used = { fullname = false, format = false, name = false, path = false, suffix = false, width = false, height = false, } local status = { status = 0, converted = false, cached = false, fullname = false, format = false, } -- this needs checking because we might check for nil, the test case -- is getfiguredimensions which then should return ~= 0 -- setmetatableindex(status, used) -- setmetatableindex(used, request) return { request = request, used = used, status = status, } end -- use table.insert|remove local lastfiguredata = nil -- will be topofstack or last so no { } (else problems with getfiguredimensions) 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 table.merge(figuredata.request,request) end return figuredata end function figures.push(request) statistics.starttiming(figures) local figuredata = figures.initialize(request) insert(callstack,figuredata) lastfiguredata = figuredata return figuredata end function figures.pop() lastfiguredata = remove(callstack) or lastfiguredata statistics.stoptiming(figures) end function figures.current() return callstack[#callstack] or lastfiguredata end function figures.get(category,tag,default) local value = lastfiguredata and lastfiguredata[category] value = value and value[tag] if not value or value == "" or value == true then return default or "" else return value end end -- function figures.tprint(category,tag,default) context(figures.get(category,tag,default)) end local defaultformat = "pdf" local defaultprefix = "m_k_i_v_" -- todo: local path or cache path 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) if expandedfullname == expandedinputname then report_inclusion("skipping graphic with same name as input filename (%s), enforce suffix",expandedinputname) return true end local expandedoutputname = file.collapsepath(codeinjections.getoutputfilename(),true) if expandedfullname == expandedoutputname then report_inclusion("skipping graphic with same name as output filename (%s), 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 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 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 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 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 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 end elseif io.exists(oldname) then specification.fullname = oldname -- was newname specification.converted = false end 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 end end else specification = { } end specification.foundname = specification.foundname or specification.fullname figures.found[askedname .. "->" .. (specification.conversion or "default") .. "->" .. (specification.resolution or "default")] = specification return specification end local resolve_too = true -- urls 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")] if foundname then return foundname 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 end else local foundname = resolvers.findbinfile(askedname) if foundname then askedname = foundname end 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.suffix(askedname) or "" local askedcache = request.cache local askedconversion = request.conversion local askedresolution = request.resolution if askedformat ~= "" then askedformat = lower(askedformat) if trace_figures then report_inclusion("strategy: forced format %s",askedformat) end local format = figures.suffixes[askedformat] if not format then local figurepatterns = figures.patterns for i=1,#figurepatterns do local pattern = figurepatterns[i] if find(askedformat,pattern[1]) then format = pattern[2] break end end end if format then local foundname, quitscanning, forcedformat = figures.exists(askedname,format,resolve_too) -- not askedformat if foundname then return register(askedname, { askedname = askedname, fullname = foundname, -- askedname, format = forcedformat or format, cache = askedcache, -- foundname = foundname, -- no conversion = askedconversion, resolution = askedresolution, }) elseif quitscanning then return register(askedname) end elseif trace_figures then report_inclusion("strategy: unknown format %s",askedformat) end if askedpath then -- path and type given, todo: strip pieces of path local foundname, quitscanning, forcedformat = figures.exists(askedname,askedformat,resolve_too) if foundname then return register(askedname, { askedname = askedname, fullname = foundname, -- askedname, format = forcedformat or askedformat, cache = askedcache, conversion = askedconversion, resolution = askedresolution, }) end else -- type given local figurepaths = figures.paths for i=1,#figurepaths do local path = figurepaths[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 local foundname, quitscanning, forcedformat = figures.exists(check,askedformat,resolve_too) if foundname then return register(check, { askedname = askedname, fullname = check, format = askedformat, cache = askedcache, conversion = askedconversion, resolution = askedresolution, }) end end if figures.defaultsearch then local check = resolvers.findfile(askedname) if check and check ~= "" then return register(askedname, { askedname = askedname, fullname = check, format = askedformat, cache = askedcache, conversion = askedconversion, resolution = askedresolution, }) end end end elseif askedpath then if trace_figures then report_inclusion("strategy: rootbased path") end for i=1,#lookuporder do local format = lookuporder[i] local list = formats[format].list or { format } for j=1,#list do local suffix = list[j] local check = file.addsuffix(askedname,suffix) local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too) if foundname then return register(askedname, { askedname = askedname, fullname = foundname, -- check, format = forcedformat or format, cache = askedcache, conversion = askedconversion, resolution = askedresolution, }) end end end else if figures.preferquality then if trace_figures then report_inclusion("strategy: unknown format, prefer quality") end local figurepaths = figures.paths for j=1,#lookuporder do local format = lookuporder[j] local list = 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] 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) end else local foundname, quitscanning, forcedformat = figures.exists(check,format,true) if foundname then return register(askedname, { askedname = askedname, fullname = foundname, -- check format = forcedformat or format, cache = askedcache, conversion = askedconversion, resolution = askedresolution, }) end end end end end else -- 'location' if trace_figures then report_inclusion("strategy: unknown format, prefer path") end local figurepaths = figures.paths for i=1,#figurepaths do local path = figurepaths[i] for j=1,#lookuporder do local format = lookuporder[j] local list = formats[format].list or { format } for k=1,#list do local suffix = list[k] local check = path .. "/" .. file.replacesuffix(askedbase,suffix) local foundname, quitscanning, forcedformat = figures.exists(check,format,resolve_too) if foundname then return register(askedname, { askedname = askedname, fullname = foudname, -- check, format = forcedformat or format, cache = askedcache, conversion = askedconversion, resolution = askedresolution, }) end end end end end if figures.defaultsearch then if trace_figures then report_inclusion("strategy: default tex path") end for j=1,#lookuporder do local format = lookuporder[j] local list = formats[format].list or { format } for k=1,#list do local suffix = list[k] local check = resolvers.findfile(file.replacesuffix(askedname,suffix)) if check and check ~= "" then return register(askedname, { askedname = askedname, fullname = check, format = format, cache = askedcache, conversion = askedconversion, resolution = askedresolution, }) end end end end end return register(askedname, { -- these two are needed for hashing 'found' conversion = askedconversion, resolution = askedresolution, }) end -- -- -- plugins -- -- -- function identifiers.default(data) local dr, du, ds = data.request, data.used, data.status local l = locate(dr) local foundname = l.foundname local fullname = l.fullname or foundname if fullname then du.format = l.format or false du.fullname = fullname -- can be cached ds.fullname = foundname -- original ds.format = l.format ds.status = (l.found and 10) or 0 end return data end function figures.identify(data) data = data or figures.current() local list = identifiers.list -- defined at the end for i=1,#list do local identifier = list[i] data = identifier(data) if data.status.status > 0 then break end end return data end function figures.exists(askedname,format,resolve) return (existers[format] or existers.generic)(askedname,resolve) end function figures.check(data) data = data or figures.current() return (checkers[data.status.format] or checkers.generic)(data) end function figures.include(data) data = data or figures.current() return (includers[data.status.format] or includers.generic)(data) end function figures.scale(data) -- will become lua code context.doscalefigure() return data end function figures.done(data) figures.nofprocessed = figures.nofprocessed + 1 data = data or figures.current() --~ print(table.serialize(figures.current())) local dr, du, ds, nr = data.request, data.used, data.status, figures.boxnumber local box = texbox[nr] ds.width = box.width ds.height = box.height 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() 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" box.width = du.width box.height = du.height box.depth = du.depth texbox[nr] = box -- hm, should be global (to be checked for consistency) end -- -- -- generic -- -- -- function existers.generic(askedname,resolve) -- not findbinfile local result if lfs.isfile(askedname) then result = askedname elseif resolve then result = resolvers.findbinfile(askedname) or "" if result == "" then result = false end end if trace_figures then if result then report_inclusion("found: %s -> %s",askedname,result) else report_inclusion("not found: %s",askedname) end end return result end function checkers.generic(data) local dr, du, ds = data.request, data.used, data.status local name = du.fullname or "unknown generic" local page = du.page or dr.page local size = dr.size or "crop" local color = dr.color or "natural" local mask = dr.mask or "none" local conversion = dr.conversion local resolution = dr.resolution if not conversion or conversion == "" then conversion = "unknown" end if not resolution or resolution == "" then resolution = "unknown" end local hash = name .. "->" .. page .. "->" .. size .. "->" .. color .. "->" .. conversion .. "->" .. resolution .. "->" .. mask local figure = figures.loaded[hash] if figure == nil then figure = img.new { filename = name, page = page, pagebox = dr.size, -- visiblefilename = "", -- this prohibits the full filename ending up in the file } codeinjections.setfigurecolorspace(data,figure) codeinjections.setfiguremask(data,figure) figure = (figure and img.scan(figure)) or false local f, d = codeinjections.setfigurealternative(data,figure) figure, data = f or figure, d or data figures.loaded[hash] = figure if trace_conversion then report_inclusion("new graphic, hash: %s",hash) end else if trace_conversion then report_inclusion("existing graphic, hash: %s",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.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 end return data end function includers.generic(data) local dr, du, ds = data.request, data.used, data.status -- here we set the 'natural dimensions' 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, --~ } if figure == nil then figure = ds.private if figure then figure = img.copy(figure) figure = (figure and img.clone(figure,data.request)) or false end figures.used[hash] = figure end if figure then local nr = figures.boxnumber -- it looks like we have a leak in attributes here .. todo local box = node.hpack(img.node(figure)) -- img.node(figure) not longer valid indexed[figure.index] = figure box.width, box.height, box.depth = figure.width, figure.height, 0 -- new, hm, tricky, we need to do that in tex (yet) texbox[nr] = box ds.objectnumber = figure.objnum context.relocateexternalfigure() end return data end -- -- -- nongeneric -- -- -- 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 if dr.object then -- hm, bugged ... waiting for an xform interface if not job.objects.get("FIG::"..hash) then if type(command) == "function" then command() end context.dosetfigureobject(hash) end context.doboxfigureobject(hash) elseif type(command) == "function" then command() end return data end function includers.nongeneric(data) return data end -- -- -- mov -- -- -- function checkers.mov(data) local dr, du, ds = data.request, data.used, data.status local width = todimen(dr.width or figures.defaultwidth) local height = todimen(dr.height or figures.defaultheight) local foundname = du.fullname 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) end -- we need to push the node.write in between ... we could make a shared helper for this context.startfoundexternalfigure(width .. "sp",height .. "sp") context(function() nodeinjections.insertmovie { width = width, height = height, factor = number.dimenfactors.bp, ["repeat"] = dr["repeat"], controls = dr.controls, preview = dr.preview, label = dr.label, foundname = foundname, } end) context.stopfoundexternalfigure() return data end includers.mov = includers.nongeneric -- -- -- mps -- -- -- local function internal(askedname) local spec, mprun, mpnum = match(lower(askedname),"mprun(:?)(.-)%.(%d+)") if spec == ":" then return mprun, mpnum else return "", mpnum end end function existers.mps(askedname) local mprun, mpnum = internal(askedname) if mpnum then return askedname else return existers.generic(askedname) end 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) else return checkers.nongeneric(data,function() context.docheckfiguremps(data.used.fullname) end) end end includers.mps = includers.nongeneric -- -- -- tex -- -- -- function existers.tex(askedname) askedname = resolvers.findfile(askedname) return (askedname ~= "" and askedname) or false end function checkers.tex(data) return checkers.nongeneric(data,function() context.docheckfiguretex(data.used.fullname) end) end includers.tex = includers.nongeneric -- -- -- buffer -- -- -- function existers.buffer(askedname) local name = file.nameonly(askedname) local okay = buffers.exists(name) return okay and name, true -- always quit scanning end function checkers.buffer(data) return checkers.nongeneric(data,function() context.docheckfigurebuffer(file.nameonly(data.used.fullname)) end) end includers.buffers = includers.nongeneric -- -- -- auto -- -- -- 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) else report_inclusion("format guess for %q is not possible",name) end return format and name, true, format end checkers.auto = checkers.generic includers.auto = includers.generic -- -- -- cld -- -- -- existers.cld = existers.tex function checkers.cld(data) return checkers.nongeneric(data,function() context.docheckfigurecld(data.used.fullname) end) end includers.cld = includers.nongeneric -- -- -- converters -- -- -- local function makeoptions(options) local to = type(options) return (to == "table" and concat(options," ")) or (to == "string" and options) or "" end -- programs.makeoptions = makeoptions local function runprogram(template,binary,...) local command = format(template,binary,...) local binary = match(binary,"[%S]+") -- to be sure if os.which(binary) then if trace_conversion or trace_programs then report_inclusion("running: %s",command) end os.spawn(command) else report_inclusion("program '%s' is not installed, not running: %s",binary,command) end end -- -- -- eps -- -- -- local epsconverter = { } converters.eps = epsconverter converters.ps = epsconverter programs.gs = { resolutions = { [variables.low] = "screen", [variables.medium] = "ebook", [variables.high] = "prepress", }, options = { "-dAutoRotatePages=/None", "-dPDFSETTINGS=/%s", "-dEPSCrop", }, command = (os.type == "windows" and "gswin32c") or "gs" } 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 ) end epsconverter.default = epsconverter.pdf -- -- -- svg -- -- -- local svgconverter = { } converters.svg = svgconverter converters.svgz = svgconverter -- inkscape on windows only works with complete paths programs.inkscape = { options = { "--export-dpi=600" }, command = "inkscape" } 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 ) end function svgconverter.png(oldname,newname) local inkscape = programs.inkscape runprogram ( '%s "%s" --export-png="%s" %s', inkscape.command, oldname, newname, makeoptions(inkscape.options) ) 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 tifconverter = { } converters.tif = tifconverter programs.convert = { --~ command = "convert" -- imagemagick command = "gm convert" -- graphicmagick --~ command = "tiff2pdf" } function tifconverter.pdf(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 ) end tifconverter.default = tifconverter.pdf -- 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 = figures.bases function bases.use(basename) if basename == "reset" then bases.list, bases.used, bases.found, bases.enabled = { }, { }, { }, false else basename = file.addsuffix(basename,"xml") 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 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) end end end end function bases.find(basename,askedlabel) if trace_bases then report_inclusion("checking for '%s' in base '%s'",askedlabel,basename) end basename = file.addsuffix(basename,"xml") local t = bases.found[askedlabel] if t == nil then 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] 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) end break end end end t = false if base[2] and base[3] then -- rlx:library for e in xml.collected(base[3],"/(*:library|figurelibrary)/*:figure/*:label") do page = page + 1 if xml.text(e) == askedlabel then t = { base = file.replacesuffix(base[2],"pdf"), format = "pdf", name = xml.text(e,"../*:file"), -- to be checked page = page, } bases.found[askedlabel] = t if trace_bases then report_inclusion("figure '%s' found in base '%s'",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]) end end end return t 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) if t then return t end end return false end function identifiers.base(data) if bases.enabled then local dr, du, ds = data.request, data.used, data.status local fbl = bases.locate(dr.name or dr.label) if fbl then du.page = fbl.page du.format = fbl.format du.fullname = fbl.base ds.fullname = fbl.name ds.format = fbl.format ds.page = fbl.page ds.status = 10 end end return data end identifiers.list = { identifiers.base, identifiers.default } -- tracing statistics.register("graphics processing time", function() local nofprocessed = figures.nofprocessed if nofprocessed > 0 then return format("%s seconds including tex, %s processed images", statistics.elapsedtime(figures),nofprocessed) else return nil end end) -- helper function figures.applyratio(width,height,w,h) -- width and height are strings and w and h are numbers if not width or width == "" then if not height or height == "" then return figures.defaultwidth, figures.defaultheight else height = todimen(height) if w and h then return height * w/h, height else return figures.defaultwidth, height end end else width = todimen(width) if not height or height == "" then if w and h then return width, width * h/w else return width, figures.defaultheight end else return width, todimen(height) end end end -- example of simple plugins: -- -- figures.converters.png = { -- png = function(oldname,newname,resolution) -- local command = string.format('gm convert -depth 1 "%s" "%s"',oldname,newname) -- logs.report(string.format("running command %s",command)) -- os.execute(command) -- end, -- } -- figures.converters.bmp = { -- pdf = function(oldname,newname) -- os.execute(string.format("gm convert %s %s",oldname,newname)) -- end -- } -- local fig = figures.push { name = pdffile } -- figures.identify() -- figures.check() -- local nofpages = fig.used.pages -- figures.pop() -- interfacing commands.setfigurelookuporder = figures.setorder