151 err(
"Failed to open file {} for extracting bounding box\n",fileName);
156 std::stringstream buffer;
158 std::string contents = buffer.str();
161 const std::string boundingBox = isEps ?
"%%PageBoundingBox:" :
"/MediaBox [";
164 auto extractBoundingBox = [&fileName,&boundingBox,&width,&height](
const char *s) ->
bool
168 if (sscanf(s+boundingBox.length(),
"%d %d %lf %lf",&x,&y,&w,&h)==4)
170 *width =
static_cast<int>(std::ceil(w));
171 *height =
static_cast<int>(std::ceil(h));
174 err(
"Failed to extract bounding box from generated diagram file {}\n",fileName);
179 const std::string streamStart =
"stream\n";
180 const std::string streamEnd =
"\nendstream";
182 auto detectDeflateStreamStart = [&streamStart](
const char *s)
184 size_t len = streamStart.length();
185 bool streamOK = strncmp(s,streamStart.c_str(),len)==0;
188 unsigned short header1 =
static_cast<unsigned char>(s[len])<<8;
191 unsigned short header = (
static_cast<unsigned char>(s[len+1])) | header1;
193 return ((header&0x8F20)==0x0800) && (header%31)==0;
199 const size_t l = contents.length();
203 if (!isEps && contents[i]==
's' && detectDeflateStreamStart(&contents[i]))
206 i+=streamStart.length();
207 const size_t start=i;
208 DBG((
"---- start stream at offset %08x\n",(
int)i));
211 if (contents[i]==
'\n' && strncmp(&contents[i],streamEnd.c_str(),streamEnd.length())==0)
213 DBG((
"\n---- end stream at offset %08x\n",(
int)i));
215 std::vector<char> decompressBuf;
216 const char *source = &contents[start];
217 const size_t sourceLen = i-start;
218 size_t sourcePos = 0;
219 decompressBuf.reserve(sourceLen*2);
220 auto getter = [source,&sourcePos,sourceLen]() ->
int {
221 return sourcePos<sourceLen ? static_cast<unsigned char>(source[sourcePos++]) : EOF;
223 auto putter = [&decompressBuf](
const char c) ->
int {
224 decompressBuf.push_back(c);
return c;
226 Deflate(getter,putter);
228 std::string s(decompressBuf.begin(), decompressBuf.end());
229 DBG((
"decompressed_data=[[[\n%s\n]]]\n",s.c_str()));
231 const size_t idx = s.find(boundingBox);
232 if (idx!=std::string::npos)
237 i+=streamEnd.length();
242 if (col>16) { col=0;
DBG((
"\n%08x: ",
static_cast<int>(i))); }
243 DBG((
"%02x ",
static_cast<unsigned char>(contents[i])));
249 else if (((isEps && contents[i]==
'%') || (!isEps && contents[i]==
'/')) &&
250 strncmp(&contents[i],boundingBox.c_str(),boundingBox.length())==0)
259 err(
"Failed to find bounding box in generated diagram file {}\n",fileName);
280 if (dotJobs.empty())
return TRUE;
283 std::map<std::string, std::map<std::string, std::vector<const DotJob*>>> byFormatAndDir;
284 for (
const auto &job : dotJobs)
286 byFormatAndDir[job.format.str()][job.absPath.str()].push_back(&job);
289 std::mt19937 rng(std::random_device{}());
292 for (
const auto &[fmtStr, byDir] : byFormatAndDir)
296 for (
const auto &[dirStr, jobs] : byDir)
302 const size_t numThreads =
static_cast<size_t>(
Config_getInt(DOT_NUM_THREADS));
303 const size_t batchSize =
static_cast<size_t>(
Config_getInt(DOT_BATCH_SIZE));
304 const size_t exeLen =
m_dotExe.length() + 1;
305 const size_t maxArgLen = 32000-exeLen;
308 std::vector<size_t> indices(jobs.size());
309 std::iota(indices.begin(), indices.end(), 0);
310 std::shuffle(indices.begin(), indices.end(), rng);
313 struct CommandArgument
315 CommandArgument(
const QCString &args) : arguments(args) {}
317 size_t numDotFiles = 0;
318 const DotJob *firstJob =
nullptr;
321 std::vector<CommandArgument> partialCommands;
322 std::vector<CommandArgument> finalCommands;
324 bool hasImageMap = std::any_of(jobs.begin(),jobs.end(),[](
const auto &j) { return j->generateImageMap; });
330 baseArgs +=
" -Tcmapx";
335 for (
size_t i=0; i<numThreads; i++)
337 partialCommands.emplace_back(baseArgs);
342 for (
size_t i : indices)
344 const auto &job = jobs[i];
346 auto &cmd = partialCommands[index];
347 if (cmd.numDotFiles<batchSize && cmd.arguments.length()+fileArg.
length()<maxArgLen)
349 cmd.arguments+=fileArg;
354 finalCommands.push_back(cmd);
355 cmd.arguments=baseArgs+fileArg;
358 if (cmd.firstJob==
nullptr) cmd.firstJob=job;
359 index = (index+1)%numThreads;
363 finalCommands.insert(finalCommands.end(),partialCommands.begin(),partialCommands.end());
368 for (
const auto &cmd : finalCommands)
370 if (cmd.numDotFiles>0)
372 if (cmd.numDotFiles>1)
374 msg(
"Running dot for graphs {}-{}/{}\n",prev+1,prev+cmd.numDotFiles,dotJobs.size());
378 msg(
"Running dot for graph {}/{}\n",prev+1,dotJobs.size());
380 prev+=cmd.numDotFiles;
385 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
386 exitCode,
m_dotExe, dirStr, cmd.arguments);
395 std::vector< std::future<size_t> > results;
396 for (
auto & cmd: finalCommands)
398 if (cmd.numDotFiles>0)
400 auto process = [
this,cmd,dirStr]() ->
size_t
406 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
407 exitCode,
m_dotExe, dirStr, cmd.arguments);
409 return cmd.numDotFiles;
411 results.emplace_back(workers.
queue(process));
414 for (
auto &f : results)
416 size_t numDotFiles = f.get();
419 msg(
"Finished running dot for graphs {}-{}/{}\n",prev+1,prev+numDotFiles,dotJobs.size());
423 msg(
"Finished running dot for graph {}/{}\n",prev+1,dotJobs.size());
432 for (
const auto *job : jobs)
435 QCString dotOutput = job->absPath + job->relDotName +
"." + format;
436 QCString output = base +
"." + format;
440 err(
"Failed to rename {} to {}!\n", dotOutput, output);
444 if (job->generateImageMap)
446 QCString dotMapOutput = job->absPath + job->relDotName +
".cmapx";
450 err(
"Failed to rename {} to {}!\n", dotMapOutput, mapOutput);
456 if (format.startsWith(
"pdf"))
458 int width=0, height=0;
472 QCString rerunArgs =
QCString(
"-T") + format +
" -O \"" + job->relDotName +
"\"";
477 "Problems running dot: exit code={}, command='{}', dir='{}', arguments='{}'",
478 exitCode,
m_dotExe, dirStr, rerunArgs);
486 err(
"Failed to rename {} to {}!\n", dotOutput, output);
492 else if (format.startsWith(
"png"))
502 std::set<std::string> processed;
503 for (
const auto &job : dotJobs)
505 if (!processed.insert((job.absPath + job.relDotName).str()).second)
continue;
507 if (!job.md5Hash.isEmpty())
513 fwrite(job.md5Hash.data(), 1, 32, f);