1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
|
local dvireader = require "make4ht-dvireader"
local mkutils = require "mkutils"
local filter = require "make4ht-filter"
local M = {}
-- mapping between tex4ht image names and hashed image names
local output_map = {}
local dvisvgm_options = "-n --exact -c 1.15,1.15"
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
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, "r")
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)
print(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
local f = io.open(idv_file, "r")
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
print(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
end
end)
-- fix src attributes
local process = filter {
function(str)
return str:gsub('src="([^"]+)', function(filename)
local newname = output_map[filename] or filename
print("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
v.params.t4ht_par = v.params.t4ht_par .. " -p"
end
end
make:image(".", function() return "" end)
return make
end
return M
|