diff options
Diffstat (limited to 'Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua')
-rw-r--r-- | Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua new file mode 100644 index 00000000000..87f58abd57c --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua @@ -0,0 +1,269 @@ +local dvireader = require "make4ht-dvireader" +local mkutils = require "mkutils" +local filter = require "make4ht-filter" +local log = logging.new "dvisvgm_hashes" + + +local dvisvgm_par = {} + +local M = {} +-- mapping between tex4ht image names and hashed image names +local output_map = {} +local dvisvgm_options = "-n --exact -c ${scale},${scale}" +local parallel_size = 64 +-- local parallel_size = 3 + +local function make_hashed_name(base, hash) + return base .. "-" ..hash..".svg" +end + +-- detect the number of available processors +local cpu_cnt = 3 -- set a reasonable default for non-Linux systems + +if os.name == 'linux' then + cpu_cnt = 0 + local cpuinfo=assert(io.open('/proc/cpuinfo', 'r')) + for line in cpuinfo:lines() do + if line:match('^processor') then + cpu_cnt = cpu_cnt + 1 + end + end + -- set default number of threds if no CPU core have been found + if cpu_cnt == 0 then cpu_cnt = 1 end + cpuinfo:close() +elseif os.name == 'cygwin' or os.type == 'windows' then + -- windows has NUMBER_OF_PROCESSORS environmental value + local nop = os.getenv('NUMBER_OF_PROCESSORS') + if tonumber(nop) then + cpu_cnt = nop + end +end + + + +-- process output of dvisvgm and find output page numbers and corresponding files +local function get_generated_pages(output, pages) + local pages = pages or {} + local pos = 1 + local pos, finish, page = string.find(output, "processing page (%d+)", pos) + while(pos) do + pos, finish, file = string.find(output, "output written to ([^\n]+)", finish) + pages[tonumber(page)] = file + if not finish then break end + pos, finish, page = string.find(output, "processing page (%d+)", finish) + end + return pages +end + +local function make_ranges(pages) + local newpages = {} + local start, stop + for i=1,#pages do + local current = pages[i] + local next_el = pages[i+1] or current + 100 -- just select a big number + local diff = next_el - current + if diff == 1 then + if not start then start = current end + else + local element + if start then + element = start .. "-" .. current + else + element = current + end + newpages[#newpages+1] = element + start = nil + end + end + return newpages +end + +local function read_log(dvisvgmlog) + local f = io.open(dvisvgmlog, "rb") + if not f then return nil, "Cannot read dvisvgm log" end + local output = f:read("*all") + f:close() + return output +end + +-- test the existence of GNU Make, which can execute tasks in parallel +local function test_make() + local make = io.popen("make -v", "r") + if not make then return false end + local content = make:read("*all") + make:close() + return true +end + +local function save_file(filename, text) + local f = io.open(filename, "w") + f:write(text) + f:close() +end + + +local function make_makefile_command(idvfile, page_sequences) + local logs = {} + local all = {} -- list of targets in the "all:" makefile target + local targets = {} + local basename = idvfile:gsub(".idv$", "") + local makefilename = basename .. "-images" .. ".mk" + -- build make targets + for i, ranges in ipairs(page_sequences) do + local target = basename .. "-" .. i + local logfile = target .. ".dlog" + logs[#logs + 1] = logfile + all[#all+1] = target + local chunk = target .. ":\n\tdvisvgm -v4 " .. dvisvgm_options .. " -p " .. ranges .. " " .. idvfile .. " 2> " .. logfile .. "\n" + targets[#targets + 1] = chunk + end + -- construct makefile and save it + local makefile = "all: " .. table.concat(all, " ") .. "\n\n" .. table.concat(targets, "\n") + save_file(makefilename, makefile) + local command = "make -j" .. cpu_cnt .." -f " .. makefilename + return command, logs +end + +local function prepare_command(idvfile, pages) + local logs = {} + if #pages > parallel_size and test_make() then + local page_sequences = {} + for i=1, #pages, parallel_size do + local current_pages = {} + for x = i, i+parallel_size -1 do + current_pages[#current_pages + 1] = pages[x] + end + table.insert(page_sequences,table.concat(make_ranges(current_pages), ",")) + end + return make_makefile_command(idvfile, page_sequences) + end + -- else + local pagesequence = table.concat(make_ranges(pages), ",") + -- the stderr from dvisvgm must be redirected and postprocessed + local dvisvgmlog = idvfile:gsub("idv$", "dlog") + -- local dvisvgm = io.popen("dvisvgm -v4 -n --exact -c 1.15,1.15 -p " .. pagesequence .. " " .. idvfile, "r") + local command = "dvisvgm -v4 " .. dvisvgm_options .. " -p " .. pagesequence .. " " .. idvfile .. " 2> " .. dvisvgmlog + return command, {dvisvgmlog} + -- end +end + +local function execute_dvisvgm(idvfile, pages) + if #pages < 1 then return nil, "No pages to convert" end + local command, logs = prepare_command(idvfile, pages) + log:info(command) + os.execute(command) + local generated_pages = {} + for _, dvisvgmlog in ipairs(logs) do + local output = read_log(dvisvgmlog) + generated_pages = get_generated_pages(output, generated_pages) + end + return generated_pages +end + +local function get_dvi_pages(arg) + -- list of pages to convert in this run + local to_convert = {} + local idv_file = arg.input .. ".idv" + -- set extension options + local extoptions = mkutils.get_filter_settings "dvisvgm_hashes" or {} + dvisvgm_options = arg.options or extoptions.options or dvisvgm_options + parallel_size = arg.parallel_size or extoptions.parallel_size or parallel_size + cpu_cnt = arg.cpu_cnt or extoptions.cpu_cnt or cpu_cnt + dvisvgm_par.scale = arg.scale or extoptions.scale or 1.15 + dvisvgm_options = dvisvgm_options % dvisvgm_par + local f = io.open(idv_file, "rb") + if not f then return nil, "Cannot open idv file: " .. idv_file end + local content = f:read("*all") + f:close() + local dvi_pages = dvireader.get_pages(content) + -- we must find page numbers and output name sfor the generated images + local lg = mkutils.parse_lg(arg.input ..".lg") + for _, name in ipairs(lg.images) do + local page = tonumber(name.page) + local hash = dvi_pages[page] + local tex4ht_name = name.output + local output_name = make_hashed_name(arg.input, hash) + output_map[tex4ht_name] = output_name + if not mkutils.file_exists(output_name) then + log:debug("output file: ".. output_name) + to_convert[#to_convert+1] = page + end + end + local generated_files, msg = execute_dvisvgm(idv_file, to_convert) + if not generated_files then + return nil, msg + end + + -- rename the generated files to the hashed filenames + for page, file in pairs(generated_files) do + os.rename(file, make_hashed_name(arg.input, dvi_pages[page])) + end + +end + +function M.test(format) + -- ODT format doesn't support SVG + if format == "odt" then return false end + return true +end + +function M.modify_build(make) + -- this must be used in the .mk4 file as + -- Make:dvisvgm_hashes {} + make:add("dvisvgm_hashes", function(arg) + get_dvi_pages(arg) + end, + { + }) + + -- insert dvisvgm_hashes command at the end of the build sequence -- it needs to be called after t4ht + make:dvisvgm_hashes {} + + -- replace original image names with hashed names + local executed = false + make:match(".*", function(arg) + if not executed then + executed = true + local lgfiles = make.lgfile.files + for i, filename in ipairs(lgfiles) do + local replace = output_map[filename] + if replace then + lgfiles[i] = replace + end + end + -- tex4ebook process also the images table, so we need to replace generated filenames here as well + local lgimages = make.lgfile.images + for _, image in ipairs(lgimages) do + local replace = output_map[image.output] + if replace then + image.output = replace + end + end + end + end) + + -- fix src attributes + local process = filter { + function(str) + return str:gsub('src="([^"]+)', function(filename) + local newname = output_map[filename] or filename + log:debug("newname", newname) + return 'src="'.. newname + end) + end + } + + make:match("htm.?$", process) + + -- disable the image processing + for _,v in ipairs(make.build_seq) do + if v.name == "t4ht" then + local t4ht_par = v.params.t4ht_par or make.params.t4ht_par or "" + v.params.t4ht_par = t4ht_par .. " -p" + end + end + make:image(".", function() return "" end) + return make +end + +return M |