155 err(
"Failed to open file {} for extracting bounding box\n",fileName);
160 std::stringstream buffer;
162 std::string contents = buffer.str();
165 const std::string boundingBox = isEps ?
"%%PageBoundingBox:" :
"/MediaBox [";
168 auto extractBoundingBox = [&fileName,&boundingBox,&width,&height](
const char *s) ->
bool
172 if (sscanf(s+boundingBox.length(),
"%d %d %lf %lf",&x,&y,&w,&h)==4)
174 *width =
static_cast<int>(std::ceil(w));
175 *height =
static_cast<int>(std::ceil(h));
178 err(
"Failed to extract bounding box from generated diagram file {}\n",fileName);
183 const std::string streamStart =
"stream\n";
184 const std::string streamEnd =
"\nendstream";
186 auto detectDeflateStreamStart = [&streamStart](
const char *s)
188 size_t len = streamStart.length();
189 bool streamOK = strncmp(s,streamStart.c_str(),len)==0;
192 unsigned short header1 =
static_cast<unsigned char>(s[len])<<8;
195 unsigned short header = (
static_cast<unsigned char>(s[len+1])) | header1;
197 return ((header&0x8F20)==0x0800) && (header%31)==0;
203 const size_t l = contents.length();
207 if (!isEps && contents[i]==
's' && detectDeflateStreamStart(&contents[i]))
210 i+=streamStart.length();
211 const size_t start=i;
212 DBG((
"---- start stream at offset %08x\n",(
int)i));
215 if (contents[i]==
'\n' && strncmp(&contents[i],streamEnd.c_str(),streamEnd.length())==0)
217 DBG((
"\n---- end stream at offset %08x\n",(
int)i));
219 std::vector<char> decompressBuf;
220 const char *source = &contents[start];
221 const size_t sourceLen = i-start;
222 size_t sourcePos = 0;
223 decompressBuf.reserve(sourceLen*2);
224 auto getter = [source,&sourcePos,sourceLen]() ->
int {
225 return sourcePos<sourceLen ? static_cast<unsigned char>(source[sourcePos++]) : EOF;
227 auto putter = [&decompressBuf](
const char c) ->
int {
228 decompressBuf.push_back(c);
return c;
230 Deflate(getter,putter);
232 std::string s(decompressBuf.begin(), decompressBuf.end());
233 DBG((
"decompressed_data=[[[\n%s\n]]]\n",s.c_str()));
235 const size_t idx = s.find(boundingBox);
236 if (idx!=std::string::npos)
238 return extractBoundingBox(s.c_str()+idx);
241 i+=streamEnd.length();
246 if (col>16) { col=0;
DBG((
"\n%08x: ",
static_cast<int>(i))); }
247 DBG((
"%02x ",
static_cast<unsigned char>(contents[i])));
253 else if (((isEps && contents[i]==
'%') || (!isEps && contents[i]==
'/')) &&
254 strncmp(&contents[i],boundingBox.c_str(),boundingBox.length())==0)
256 return extractBoundingBox(&contents[i]);
263 err(
"Failed to find bounding box in generated diagram file {}\n",fileName);
284 if (dotJobs.empty())
return true;
287 std::map<std::string, std::map<std::string, std::vector<const DotJob*>>> byFormatAndDir;
288 for (
const auto &job : dotJobs)
290 byFormatAndDir[job.format.str()][job.absPath.str()].push_back(&job);
293 std::mt19937 rng(std::random_device{}());
296 for (
const auto &[fmtStr, byDir] : byFormatAndDir)
300 for (
const auto &[dirStr, jobs] : byDir)
306 const size_t numThreads =
static_cast<size_t>(
Config_getInt(DOT_NUM_THREADS));
307 const size_t batchSize =
static_cast<size_t>(
Config_getInt(DOT_BATCH_SIZE));
309 const size_t maxArgLen = 32000-exeLen;
312 std::vector<size_t> indices(jobs.size());
313 std::iota(indices.begin(), indices.end(), 0);
314 std::shuffle(indices.begin(), indices.end(), rng);
317 struct CommandArgument
319 CommandArgument(
const DString &args) : arguments(args) {}
321 size_t numDotFiles = 0;
322 const DotJob *firstJob =
nullptr;
325 std::vector<CommandArgument> partialCommands;
326 std::vector<CommandArgument> finalCommands;
328 bool hasImageMap = std::any_of(jobs.begin(),jobs.end(),[](
const auto &j) { return j->generateImageMap; });
334 baseArgs +=
" -Tcmapx";
339 for (
size_t i=0; i<numThreads; i++)
341 partialCommands.emplace_back(baseArgs);
346 for (
size_t i : indices)
348 const auto &job = jobs[i];
350 auto &cmd = partialCommands[index];
351 if (cmd.numDotFiles<batchSize && cmd.arguments.length()+fileArg.
length()<maxArgLen)
353 cmd.arguments+=fileArg;
358 finalCommands.push_back(cmd);
359 cmd.arguments=baseArgs+fileArg;
362 if (cmd.firstJob==
nullptr) cmd.firstJob=job;
363 index = (index+1)%numThreads;
367 finalCommands.insert(finalCommands.end(),partialCommands.begin(),partialCommands.end());
372 for (
const auto &cmd : finalCommands)
374 if (cmd.numDotFiles>0)
376 if (cmd.numDotFiles>1)
378 msg(
"Running dot for graphs {}-{}/{}\n",prev+1,prev+cmd.numDotFiles,dotJobs.size());
382 msg(
"Running dot for graph {}/{}\n",prev+1,dotJobs.size());
384 prev+=cmd.numDotFiles;
389 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
390 exitCode,
m_dotExe, dirStr, cmd.arguments);
399 std::vector< std::future<size_t> > results;
400 for (
auto & cmd: finalCommands)
402 if (cmd.numDotFiles>0)
404 auto locDirStr = dirStr;
405 auto process = [
this,cmd,locDirStr]() ->
size_t
411 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
412 exitCode,
m_dotExe, locDirStr, cmd.arguments);
414 return cmd.numDotFiles;
416 results.emplace_back(workers.
queue(process));
419 for (
auto &f : results)
421 size_t numDotFiles = f.get();
424 msg(
"Finished running dot for graphs {}-{}/{}\n",prev+1,prev+numDotFiles,dotJobs.size());
428 msg(
"Finished running dot for graph {}/{}\n",prev+1,dotJobs.size());
437 for (
const auto *job : jobs)
440 DString dotOutput = job->absPath + job->relDotName +
"." + format;
441 DString output = base +
"." + format;
445 err(
"Failed to rename {} to {}!\n", dotOutput, output);
449 if (job->generateImageMap)
451 DString dotMapOutput = job->absPath + job->relDotName +
".cmapx";
452 DString mapOutput = base +
".map";
455 err(
"Failed to rename {} to {}!\n", dotMapOutput, mapOutput);
461 if (format.startsWith(
"pdf"))
463 int width=0, height=0;
477 DString rerunArgs =
DString(
"-T") + format +
" -O \"" + job->relDotName +
"\"";
482 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
483 exitCode,
m_dotExe, dirStr, rerunArgs);
491 err(
"Failed to rename {} to {}!\n", dotOutput, output);
497 else if (format.startsWith(
"png"))
507 std::set<std::string> processed;
508 for (
const auto &job : dotJobs)
510 if (!processed.insert((job.absPath + job.relDotName).str()).second)
continue;
512 if (!job.md5Hash.empty())
518 fwrite(job.md5Hash.data(), 1, 32, f);