153 err(
"Failed to open file {} for extracting bounding box\n",fileName);
158 std::stringstream buffer;
160 std::string contents = buffer.str();
163 const std::string boundingBox = isEps ?
"%%PageBoundingBox:" :
"/MediaBox [";
166 auto extractBoundingBox = [&fileName,&boundingBox,&width,&height](
const char *s) ->
bool
170 if (sscanf(s+boundingBox.length(),
"%d %d %lf %lf",&x,&y,&w,&h)==4)
172 *width =
static_cast<int>(std::ceil(w));
173 *height =
static_cast<int>(std::ceil(h));
176 err(
"Failed to extract bounding box from generated diagram file {}\n",fileName);
181 const std::string streamStart =
"stream\n";
182 const std::string streamEnd =
"\nendstream";
184 auto detectDeflateStreamStart = [&streamStart](
const char *s)
186 size_t len = streamStart.length();
187 bool streamOK = strncmp(s,streamStart.c_str(),len)==0;
190 unsigned short header1 =
static_cast<unsigned char>(s[len])<<8;
193 unsigned short header = (
static_cast<unsigned char>(s[len+1])) | header1;
195 return ((header&0x8F20)==0x0800) && (header%31)==0;
201 const size_t l = contents.length();
205 if (!isEps && contents[i]==
's' && detectDeflateStreamStart(&contents[i]))
208 i+=streamStart.length();
209 const size_t start=i;
210 DBG((
"---- start stream at offset %08x\n",(
int)i));
213 if (contents[i]==
'\n' && strncmp(&contents[i],streamEnd.c_str(),streamEnd.length())==0)
215 DBG((
"\n---- end stream at offset %08x\n",(
int)i));
217 std::vector<char> decompressBuf;
218 const char *source = &contents[start];
219 const size_t sourceLen = i-start;
220 size_t sourcePos = 0;
221 decompressBuf.reserve(sourceLen*2);
222 auto getter = [source,&sourcePos,sourceLen]() ->
int {
223 return sourcePos<sourceLen ? static_cast<unsigned char>(source[sourcePos++]) : EOF;
225 auto putter = [&decompressBuf](
const char c) ->
int {
226 decompressBuf.push_back(c);
return c;
228 Deflate(getter,putter);
230 std::string s(decompressBuf.begin(), decompressBuf.end());
231 DBG((
"decompressed_data=[[[\n%s\n]]]\n",s.c_str()));
233 const size_t idx = s.find(boundingBox);
234 if (idx!=std::string::npos)
236 return extractBoundingBox(s.c_str()+idx);
239 i+=streamEnd.length();
244 if (col>16) { col=0;
DBG((
"\n%08x: ",
static_cast<int>(i))); }
245 DBG((
"%02x ",
static_cast<unsigned char>(contents[i])));
251 else if (((isEps && contents[i]==
'%') || (!isEps && contents[i]==
'/')) &&
252 strncmp(&contents[i],boundingBox.c_str(),boundingBox.length())==0)
254 return extractBoundingBox(&contents[i]);
261 err(
"Failed to find bounding box in generated diagram file {}\n",fileName);
282 if (dotJobs.empty())
return true;
285 std::map<std::string, std::map<std::string, std::vector<const DotJob*>>> byFormatAndDir;
286 for (
const auto &job : dotJobs)
288 byFormatAndDir[job.format.str()][job.absPath.str()].push_back(&job);
291 std::mt19937 rng(std::random_device{}());
294 for (
const auto &[fmtStr, byDir] : byFormatAndDir)
298 for (
const auto &[dirStr, jobs] : byDir)
304 const size_t numThreads =
static_cast<size_t>(
Config_getInt(DOT_NUM_THREADS));
305 const size_t batchSize =
static_cast<size_t>(
Config_getInt(DOT_BATCH_SIZE));
307 const size_t maxArgLen = 32000-exeLen;
310 std::vector<size_t> indices(jobs.size());
311 std::iota(indices.begin(), indices.end(), 0);
312 std::shuffle(indices.begin(), indices.end(), rng);
315 struct CommandArgument
317 CommandArgument(
const DString &args) : arguments(args) {}
319 size_t numDotFiles = 0;
320 const DotJob *firstJob =
nullptr;
323 std::vector<CommandArgument> partialCommands;
324 std::vector<CommandArgument> finalCommands;
326 bool hasImageMap = std::any_of(jobs.begin(),jobs.end(),[](
const auto &j) { return j->generateImageMap; });
332 baseArgs +=
" -Tcmapx";
337 for (
size_t i=0; i<numThreads; i++)
339 partialCommands.emplace_back(baseArgs);
344 for (
size_t i : indices)
346 const auto &job = jobs[i];
348 auto &cmd = partialCommands[index];
349 if (cmd.numDotFiles<batchSize && cmd.arguments.length()+fileArg.
length()<maxArgLen)
351 cmd.arguments+=fileArg;
356 finalCommands.push_back(cmd);
357 cmd.arguments=baseArgs+fileArg;
360 if (cmd.firstJob==
nullptr) cmd.firstJob=job;
361 index = (index+1)%numThreads;
365 finalCommands.insert(finalCommands.end(),partialCommands.begin(),partialCommands.end());
370 for (
const auto &cmd : finalCommands)
372 if (cmd.numDotFiles>0)
374 if (cmd.numDotFiles>1)
376 msg(
"Running dot for graphs {}-{}/{}\n",prev+1,prev+cmd.numDotFiles,dotJobs.size());
380 msg(
"Running dot for graph {}/{}\n",prev+1,dotJobs.size());
382 prev+=cmd.numDotFiles;
387 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
388 exitCode,
m_dotExe, dirStr, cmd.arguments);
397 std::vector< std::future<size_t> > results;
398 for (
auto & cmd: finalCommands)
400 if (cmd.numDotFiles>0)
402 auto locDirStr = dirStr;
403 auto process = [
this,cmd,locDirStr]() ->
size_t
409 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
410 exitCode,
m_dotExe, locDirStr, cmd.arguments);
412 return cmd.numDotFiles;
414 results.emplace_back(workers.
queue(process));
417 for (
auto &f : results)
419 size_t numDotFiles = f.get();
422 msg(
"Finished running dot for graphs {}-{}/{}\n",prev+1,prev+numDotFiles,dotJobs.size());
426 msg(
"Finished running dot for graph {}/{}\n",prev+1,dotJobs.size());
435 for (
const auto *job : jobs)
438 DString dotOutput = job->absPath + job->relDotName +
"." + format;
439 DString output = base +
"." + format;
443 err(
"Failed to rename {} to {}!\n", dotOutput, output);
447 if (job->generateImageMap)
449 DString dotMapOutput = job->absPath + job->relDotName +
".cmapx";
450 DString mapOutput = base +
".map";
453 err(
"Failed to rename {} to {}!\n", dotMapOutput, mapOutput);
459 if (format.startsWith(
"pdf"))
461 int width=0, height=0;
475 DString rerunArgs =
DString(
"-T") + format +
" -O \"" + job->relDotName +
"\"";
480 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
481 exitCode,
m_dotExe, dirStr, rerunArgs);
489 err(
"Failed to rename {} to {}!\n", dotOutput, output);
495 else if (format.startsWith(
"png"))
505 std::set<std::string> processed;
506 for (
const auto &job : dotJobs)
508 if (!processed.insert((job.absPath + job.relDotName).str()).second)
continue;
510 if (!job.md5Hash.empty())
516 fwrite(job.md5Hash.data(), 1, 32, f);