summaryrefslogtreecommitdiffstats
path: root/src/dotrunner.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dotrunner.cpp')
-rw-r--r--src/dotrunner.cpp300
1 files changed, 300 insertions, 0 deletions
diff --git a/src/dotrunner.cpp b/src/dotrunner.cpp
new file mode 100644
index 0000000..07736bd
--- /dev/null
+++ b/src/dotrunner.cpp
@@ -0,0 +1,300 @@
+/******************************************************************************
+*
+* 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 "dotrunner.h"
+
+#include "util.h"
+#include "portable.h"
+#include "dot.h"
+#include "message.h"
+#include "ftextstream.h"
+#include "config.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 char *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",imgName
+ );
+ }
+ }
+ else
+ {
+ err("Could not read image `%s' generated by dot!\n",imgName);
+ }
+ fclose(f);
+ }
+ else
+ {
+ err("Could not open image `%s' generated by dot!\n",imgName);
+ }
+}
+
+static bool resetPDFSize(const int width,const int height, const char *base)
+{
+ QString tmpName = QString::fromUtf8(QCString(base)+".tmp");
+ QString patchFile = QString::fromUtf8(QCString(base)+".dot");
+ if (!QDir::current().rename(patchFile,tmpName))
+ {
+ err("Failed to rename file %s to %s!\n",patchFile.data(),tmpName.data());
+ return FALSE;
+ }
+ QFile fi(tmpName);
+ QFile fo(patchFile);
+ if (!fi.open(IO_ReadOnly))
+ {
+ err("problem opening file %s for patching!\n",tmpName.data());
+ QDir::current().rename(tmpName,patchFile);
+ return FALSE;
+ }
+ if (!fo.open(IO_WriteOnly))
+ {
+ err("problem opening file %s for patching!\n",patchFile.data());
+ QDir::current().rename(tmpName,patchFile);
+ fi.close();
+ return FALSE;
+ }
+ FTextStream t(&fo);
+ const int maxLineLen=100*1024;
+ while (!fi.atEnd()) // foreach line
+ {
+ QCString line(maxLineLen);
+ int numBytes = fi.readLine(line.rawData(),maxLineLen);
+ if (numBytes<=0)
+ {
+ break;
+ }
+ line.resize(numBytes+1);
+ if (line.find("LATEX_PDF_SIZE") != -1)
+ {
+ double scale = (width > height ? width : height)/double(MAX_LATEX_GRAPH_INCH);
+ t << " size=\""<<width/scale << "," <<height/scale <<"\";\n";
+ }
+ else
+ t << line;
+ }
+ fi.close();
+ fo.close();
+ // remove temporary file
+ QDir::current().remove(tmpName);
+ return TRUE;
+}
+
+bool DotRunner::readBoundingBox(const char *fileName,int *width,int *height,bool isEps)
+{
+ QCString bb = isEps ? QCString("%%PageBoundingBox:") : QCString("/MediaBox [");
+ QFile f(fileName);
+ if (!f.open(IO_ReadOnly|IO_Raw))
+ {
+ //printf("readBoundingBox: could not open %s\n",fileName);
+ return FALSE;
+ }
+ const int maxLineLen=1024;
+ char buf[maxLineLen];
+ while (!f.atEnd())
+ {
+ int numBytes = f.readLine(buf,maxLineLen-1); // read line
+ if (numBytes>0)
+ {
+ buf[numBytes]='\0';
+ const char *p = strstr(buf,bb);
+ if (p) // found PageBoundingBox or /MediaBox string
+ {
+ int x,y;
+ if (sscanf(p+bb.length(),"%d %d %d %d",&x,&y,width,height)!=4)
+ {
+ //printf("readBoundingBox sscanf fail\n");
+ return FALSE;
+ }
+ return TRUE;
+ }
+ }
+ else // read error!
+ {
+ //printf("Read error %d!\n",numBytes);
+ return FALSE;
+ }
+ }
+ err("Failed to extract bounding box from generated diagram file %s\n",fileName);
+ return FALSE;
+}
+
+bool DotRunner::DOT_CLEANUP;
+bool DotRunner::DOT_MULTI_TARGETS;
+DotConstString DotRunner::DOT_EXE;
+
+DotRunner::DotRunner(const QCString& absDotName, const QCString& md5Hash)
+ : m_file(absDotName), m_md5Hash(md5Hash), m_cleanUp(DOT_CLEANUP)
+{
+ m_jobs.setAutoDelete(TRUE);
+}
+
+void DotRunner::addJob(const char *format,const char *output)
+{
+ QListIterator<DotJob> li(m_jobs);
+ DotJob *s;
+ for (li.toFirst(); (s = li.current()); ++li)
+ {
+ if (qstrcmp(s->format.data(), format) != 0) continue;
+ if (qstrcmp(s->output.data(), output) != 0) continue;
+ // we have this job already
+ return;
+ }
+ QCString args = QCString("-T")+format+" -o \""+output+"\"";
+ m_jobs.append(new DotJob(format, output, args));
+}
+
+QCString getBaseNameOfOutput(QCString const& output)
+{
+ int index = output.findRev('.');
+ if (index < 0) return output;
+ return output.left(index);
+}
+
+bool DotRunner::run()
+{
+ int exitCode=0;
+
+ QCString dotArgs;
+ QListIterator<DotJob> li(m_jobs);
+ DotJob *s;
+
+ // create output
+ if (DOT_MULTI_TARGETS)
+ {
+ dotArgs=QCString("\"")+m_file.data()+"\"";
+ for (li.toFirst();(s=li.current());++li)
+ {
+ dotArgs+=' ';
+ dotArgs+=s->args.data();
+ }
+ if ((exitCode=portable_system(DOT_EXE.data(),dotArgs,FALSE))!=0) goto error;
+ }
+ else
+ {
+ for (li.toFirst();(s=li.current());++li)
+ {
+ dotArgs=QCString("\"")+m_file.data()+"\" "+s->args.data();
+ if ((exitCode=portable_system(DOT_EXE.data(),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 (li.toFirst();(s=li.current());++li)
+ {
+ if (qstrncmp(s->format.data(), "pdf", 3) == 0)
+ {
+ int width=0,height=0;
+ if (!readBoundingBox(s->output.data(),&width,&height,FALSE)) goto error;
+ if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE))
+ {
+ if (!resetPDFSize(width,height,getBaseNameOfOutput(s->output.data()))) goto error;
+ dotArgs=QCString("\"")+m_file.data()+"\" "+s->args.data();
+ if ((exitCode=portable_system(DOT_EXE.data(),dotArgs,FALSE))!=0) goto error;
+ }
+ }
+
+ if (qstrncmp(s->format.data(), "png", 3) == 0)
+ {
+ checkPngResult(s->output.data());
+ }
+ }
+
+ // remove .dot files
+ if (m_cleanUp)
+ {
+ //printf("removing dot file %s\n",m_file.data());
+ QFile::remove(m_file.data());
+ }
+
+ // create checksum file
+ if (!m_md5Hash.isEmpty()) {
+ QCString md5Name = getBaseNameOfOutput(m_file.data()) + ".md5";
+ QFile f(md5Name);
+ if (f.open(IO_WriteOnly))
+ {
+ f.writeBlock(m_md5Hash.data(),32);
+ f.close();
+ }
+ }
+ return TRUE;
+error:
+ err("Problems running dot: exit code=%d, command='%s', arguments='%s'\n",
+ exitCode,DOT_EXE.data(),dotArgs.data());
+ return FALSE;
+}
+
+
+//--------------------------------------------------------------------
+
+void DotRunnerQueue::enqueue(DotRunner *runner)
+{
+ QMutexLocker locker(&m_mutex);
+ m_queue.enqueue(runner);
+ m_bufferNotEmpty.wakeAll();
+}
+
+DotRunner *DotRunnerQueue::dequeue()
+{
+ QMutexLocker locker(&m_mutex);
+ while (m_queue.isEmpty())
+ {
+ // wait until something is added to the queue
+ m_bufferNotEmpty.wait(&m_mutex);
+ }
+ DotRunner *result = m_queue.dequeue();
+ return result;
+}
+
+uint DotRunnerQueue::count() const
+{
+ QMutexLocker locker(&m_mutex);
+ return m_queue.count();
+}
+
+//--------------------------------------------------------------------
+
+DotWorkerThread::DotWorkerThread(DotRunnerQueue *queue)
+ : m_queue(queue)
+{
+}
+
+void DotWorkerThread::run()
+{
+ DotRunner *runner;
+ while ((runner=m_queue->dequeue()))
+ {
+ runner->run();
+ }
+}