/****************************************************************************** * * Copyright (C) 1997-2019 by Dimitri van Heesch. * * Permission to use, copy, modify, and distribute this software and its * documentation under the terms of the GNU General Public License is hereby * granted. No representations are made about the suitability of this software * for any purpose. It is provided "as is" without express or implied warranty. * See the GNU General Public License for more details. * * Documents produced by Doxygen are derivative works derived from the * input used in their production; they are not affected by this license. * */ #include #include #include "dotrunner.h" #include "util.h" #include "portable.h" #include "dot.h" #include "message.h" #include "config.h" #include "dir.h" #include "doxygen.h" // the graphicx LaTeX has a limitation of maximum size of 16384 // To be on the save side we take it a little bit smaller i.e. 150 inch * 72 dpi // It is anyway hard to view these size of images #define MAX_LATEX_GRAPH_INCH 150 #define MAX_LATEX_GRAPH_SIZE (MAX_LATEX_GRAPH_INCH * 72) //----------------------------------------------------------------------------------------- // since dot silently reproduces the input file when it does not // support the PNG format, we need to check the result. static void checkPngResult(const QCString &imgName) { FILE *f = Portable::fopen(imgName,"rb"); if (f) { char data[4]; if (fread(data,1,4,f)==4) { if (!(data[1]=='P' && data[2]=='N' && data[3]=='G')) { err("Image '%s' produced by dot is not a valid PNG!\n" "You should either select a different format " "(DOT_IMAGE_FORMAT in the config file) or install a more " "recent version of graphviz (1.7+)\n",qPrint(imgName) ); } } else { err("Could not read image '%s' generated by dot!\n",qPrint(imgName)); } fclose(f); } else { err("Could not open image '%s' generated by dot!\n",qPrint(imgName)); } } static bool resetPDFSize(const int width,const int height, const QCString &base) { std::string tmpName = base.str()+".tmp"; std::string patchFile = base.str()+".dot"; Dir thisDir; if (!thisDir.rename(patchFile,tmpName)) { err("Failed to rename file %s to %s!\n",qPrint(patchFile),qPrint(tmpName)); return FALSE; } std::ifstream fi(tmpName,std::ifstream::in); std::ofstream t(patchFile,std::ofstream::out | std::ofstream::binary); if (!fi.is_open()) { err("problem opening file %s for patching!\n",qPrint(tmpName)); thisDir.rename(tmpName,patchFile); return FALSE; } if (!t.is_open()) { err("problem opening file %s for patching!\n",qPrint(patchFile)); thisDir.rename(tmpName,patchFile); return FALSE; } std::string line; while (getline(fi,line)) // foreach line { if (line.find("LATEX_PDF_SIZE") != std::string::npos) { double scale = (width > height ? width : height)/double(MAX_LATEX_GRAPH_INCH); t << " size=\""<(std::ceil(w)); *height = static_cast(std::ceil(h)); return TRUE; } } err("Failed to extract bounding box from generated diagram file %s\n",qPrint(fileName)); fclose(f); return FALSE; } //--------------------------------------------------------------------------------- DotRunner::DotRunner(const QCString& absDotName, const QCString& md5Hash) : m_file(absDotName) , m_md5Hash(md5Hash) , m_dotExe(Doxygen::verifiedDotPath) , m_cleanUp(Config_getBool(DOT_CLEANUP)) { } void DotRunner::addJob(const QCString &format, const QCString &output, const QCString &srcFile,int srcLine) { for (auto& s: m_jobs) { if (s.format != format) continue; if (s.output != output) continue; // we have this job already return; } auto args = QCString("-T") + format + " -o \"" + output + "\""; m_jobs.emplace_back(format, output, args, srcFile, srcLine); } QCString getBaseNameOfOutput(const QCString &output) { int index = output.findRev('.'); if (index < 0) return output; return output.left(index); } bool DotRunner::run() { int exitCode=0; QCString dotArgs; QCString srcFile; int srcLine=-1; // create output if (Config_getBool(DOT_MULTI_TARGETS)) { dotArgs=QCString("\"")+m_file+"\""; for (auto& s: m_jobs) { dotArgs+=' '; dotArgs+=s.args; } if (!m_jobs.empty()) { srcFile = m_jobs.front().srcFile; srcLine = m_jobs.front().srcLine; } if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error; } else { for (auto& s : m_jobs) { srcFile = s.srcFile; srcLine = s.srcLine; dotArgs=QCString("\"")+m_file+"\" "+s.args; if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error; } } // check output // As there should be only one pdf file be generated, we don't need code for regenerating multiple pdf files in one call for (auto& s : m_jobs) { if (s.format.startsWith("pdf")) { int width=0,height=0; if (!readBoundingBox(s.output,&width,&height,FALSE)) goto error; if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE)) { if (!resetPDFSize(width,height,getBaseNameOfOutput(s.output))) goto error; dotArgs=QCString("\"")+m_file+"\" "+s.args; if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error; } } if (s.format.startsWith("png")) { checkPngResult(s.output); } } // remove .dot files if (m_cleanUp) { //printf("removing dot file %s\n",qPrint(m_file)); Portable::unlink(m_file); } // create checksum file if (!m_md5Hash.isEmpty()) { QCString md5Name = getBaseNameOfOutput(m_file) + ".md5"; FILE *f = Portable::fopen(md5Name,"w"); if (f) { fwrite(m_md5Hash.data(),1,32,f); fclose(f); } } return TRUE; error: err_full(srcFile,srcLine,"Problems running dot: exit code=%d, command='%s', arguments='%s'\n", exitCode,qPrint(m_dotExe),qPrint(dotArgs)); return FALSE; } //-------------------------------------------------------------------- void DotRunnerQueue::enqueue(DotRunner *runner) { std::lock_guard locker(m_mutex); m_queue.push(runner); m_bufferNotEmpty.notify_all(); } DotRunner *DotRunnerQueue::dequeue() { std::unique_lock locker(m_mutex); // wait until something is added to the queue m_bufferNotEmpty.wait(locker, [this]() { return !m_queue.empty(); }); DotRunner *result = m_queue.front(); m_queue.pop(); return result; } size_t DotRunnerQueue::size() const { std::lock_guard locker(m_mutex); return m_queue.size(); } //-------------------------------------------------------------------- DotWorkerThread::DotWorkerThread(DotRunnerQueue *queue) : m_queue(queue) { } DotWorkerThread::~DotWorkerThread() { if (isRunning()) { wait(); } } void DotWorkerThread::run() { DotRunner *runner; while ((runner=m_queue->dequeue())) { runner->run(); } } void DotWorkerThread::start() { assert(!m_thread); m_thread = std::make_unique(&DotWorkerThread::run, this); }