1 /*****************************************************************************
2  *
3  * Copyright (C) 1997-2019 by Dimitri van Heesch.
4  *
5  * Permission to use, copy, modify, and distribute this software and its
6  * documentation under the terms of the GNU General Public License is hereby
7  * granted. No representations are made about the suitability of this software
8  * for any purpose. It is provided "as is" without express or implied warranty.
9  * See the GNU General Public License for more details.
10  *
11  * Documents produced by Doxygen are derivative works derived from the
12  * input used in their production; they are not affected by this license.
13  *
14  */
15 
16 #include <cstdlib>
17 #include <cassert>
18 #include <sstream>
19 #include <algorithm>
20 
21 #include "config.h"
22 #include "dot.h"
23 #include "dotrunner.h"
24 #include "dotfilepatcher.h"
25 #include "util.h"
26 #include "portable.h"
27 #include "message.h"
28 #include "doxygen.h"
29 #include "language.h"
30 #include "index.h"
31 #include "dir.h"
32 
33 #define MAP_CMD "cmapx"
34 
35 //--------------------------------------------------------------------
36 
37 static QCString g_dotFontPath;
38 
setDotFontPath(const QCString & path)39 static void setDotFontPath(const QCString &path)
40 {
41   ASSERT(g_dotFontPath.isEmpty());
42   g_dotFontPath = Portable::getenv("DOTFONTPATH");
43   QCString newFontPath = Config_getString(DOT_FONTPATH);
44   if (!newFontPath.isEmpty() && !path.isEmpty())
45   {
46     newFontPath.prepend(path+Portable::pathListSeparator());
47   }
48   else if (newFontPath.isEmpty() && !path.isEmpty())
49   {
50     newFontPath=path;
51   }
52   else
53   {
54     Portable::unsetenv("DOTFONTPATH");
55     return;
56   }
57   Portable::setenv("DOTFONTPATH",newFontPath);
58 }
59 
unsetDotFontPath()60 static void unsetDotFontPath()
61 {
62   if (g_dotFontPath.isEmpty())
63   {
64     Portable::unsetenv("DOTFONTPATH");
65   }
66   else
67   {
68     Portable::setenv("DOTFONTPATH",g_dotFontPath);
69   }
70   g_dotFontPath="";
71 }
72 
73 //--------------------------------------------------------------------
74 
75 DotManager *DotManager::m_theInstance = 0;
76 
instance()77 DotManager *DotManager::instance()
78 {
79   if (!m_theInstance)
80   {
81     m_theInstance = new DotManager;
82   }
83   return m_theInstance;
84 }
85 
deleteInstance()86 void DotManager::deleteInstance()
87 {
88   delete m_theInstance;
89   m_theInstance=0;
90 }
91 
DotManager()92 DotManager::DotManager() : m_runners(), m_filePatchers()
93 {
94   m_queue = new DotRunnerQueue;
95   int i;
96   int dotNumThreads = Config_getInt(DOT_NUM_THREADS);
97   if (dotNumThreads!=1)
98   {
99     for (i=0;i<dotNumThreads;i++)
100     {
101       std::unique_ptr<DotWorkerThread> thread = std::make_unique<DotWorkerThread>(m_queue);
102       thread->start();
103       if (thread->isRunning())
104       {
105         m_workers.push_back(std::move(thread));
106       }
107       else // no more threads available!
108       {
109       }
110     }
111     ASSERT(m_workers.size()>0);
112   }
113 }
114 
~DotManager()115 DotManager::~DotManager()
116 {
117   delete m_queue;
118 }
119 
createRunner(const QCString & absDotName,const QCString & md5Hash)120 DotRunner* DotManager::createRunner(const QCString &absDotName, const QCString& md5Hash)
121 {
122   DotRunner* rv = nullptr;
123   auto const runit = m_runners.find(absDotName.str());
124   if (runit == m_runners.end())
125   {
126     auto insobj = std::make_unique<DotRunner>(absDotName, md5Hash);
127     rv = insobj.get();
128     m_runners.emplace(absDotName.str(), std::move(insobj));
129   }
130   else
131   {
132     // we have a match
133     if (md5Hash != runit->second->getMd5Hash())
134     {
135       err("md5 hash does not match for two different runs of %s !\n", qPrint(absDotName));
136     }
137     rv = runit->second.get();
138   }
139   assert(rv);
140   return rv;
141 }
142 
createFilePatcher(const QCString & fileName)143 DotFilePatcher *DotManager::createFilePatcher(const QCString &fileName)
144 {
145   auto patcher = m_filePatchers.find(fileName.str());
146 
147   if (patcher != m_filePatchers.end()) return &(patcher->second);
148 
149   auto rv = m_filePatchers.emplace(std::make_pair(fileName.str(), fileName));
150   assert(rv.second);
151   return &(rv.first->second);
152 }
153 
run() const154 bool DotManager::run() const
155 {
156   size_t numDotRuns = m_runners.size();
157   size_t numFilePatchers = m_filePatchers.size();
158   if (numDotRuns+numFilePatchers>1)
159   {
160     if (m_workers.size()==0)
161     {
162       msg("Generating dot graphs in single threaded mode...\n");
163     }
164     else
165     {
166       msg("Generating dot graphs using %zu parallel threads...\n",std::min(numDotRuns+numFilePatchers,m_workers.size()));
167     }
168   }
169   size_t i=1;
170 
171   bool setPath=FALSE;
172   if (Config_getBool(GENERATE_HTML))
173   {
174     setDotFontPath(Config_getString(HTML_OUTPUT));
175     setPath=TRUE;
176   }
177   else if (Config_getBool(GENERATE_LATEX))
178   {
179     setDotFontPath(Config_getString(LATEX_OUTPUT));
180     setPath=TRUE;
181   }
182   else if (Config_getBool(GENERATE_RTF))
183   {
184     setDotFontPath(Config_getString(RTF_OUTPUT));
185     setPath=TRUE;
186   }
187   else if (Config_getBool(GENERATE_DOCBOOK))
188   {
189     setDotFontPath(Config_getString(DOCBOOK_OUTPUT));
190     setPath=TRUE;
191   }
192   Portable::sysTimerStart();
193   // fill work queue with dot operations
194   size_t prev=1;
195   if (m_workers.size()==0) // no threads to work with
196   {
197     for (auto & dr : m_runners)
198     {
199       msg("Running dot for graph %zu/%zu\n",prev,numDotRuns);
200       dr.second->run();
201       prev++;
202     }
203   }
204   else // use multiple threads to run instances of dot in parallel
205   {
206     for (auto & dr: m_runners)
207     {
208       m_queue->enqueue(dr.second.get());
209     }
210     // wait for the queue to become empty
211     while ((i=m_queue->size())>0)
212     {
213       i = numDotRuns - i;
214       while (i>=prev)
215       {
216         msg("Running dot for graph %zu/%zu\n",prev,numDotRuns);
217         prev++;
218       }
219       Portable::sleep(100);
220     }
221     while (numDotRuns>=prev)
222     {
223       msg("Running dot for graph %zu/%zu\n",prev,numDotRuns);
224       prev++;
225     }
226     // signal the workers we are done
227     for (i=0;i<m_workers.size();i++)
228     {
229       m_queue->enqueue(0); // add terminator for each worker
230     }
231     // wait for the workers to finish
232     for (i=0;i<m_workers.size();i++)
233     {
234       m_workers.at(i)->wait();
235     }
236   }
237   Portable::sysTimerStop();
238   if (setPath)
239   {
240     unsetDotFontPath();
241   }
242 
243   // patch the output file and insert the maps and figures
244   i=1;
245   // since patching the svg files may involve patching the header of the SVG
246   // (for zoomable SVGs), and patching the .html files requires reading that
247   // header after the SVG is patched, we first process the .svg files and
248   // then the other files.
249   for (auto & fp : m_filePatchers)
250   {
251     if (fp.second.isSVGFile())
252     {
253       msg("Patching output file %zu/%zu\n",i,numFilePatchers);
254       if (!fp.second.run()) return FALSE;
255       i++;
256     }
257   }
258   for (auto& fp : m_filePatchers)
259   {
260     if (!fp.second.isSVGFile())
261     {
262       msg("Patching output file %zu/%zu\n",i,numFilePatchers);
263       if (!fp.second.run()) return FALSE;
264       i++;
265     }
266   }
267   return TRUE;
268 }
269 
270 //--------------------------------------------------------------------
271 
writeDotGraphFromFile(const QCString & inFile,const QCString & outDir,const QCString & outFile,GraphOutputFormat format,const QCString & srcFile,int srcLine)272 void writeDotGraphFromFile(const QCString &inFile,const QCString &outDir,
273                            const QCString &outFile,GraphOutputFormat format,
274                            const QCString &srcFile,int srcLine)
275 {
276   Dir d(outDir.str());
277   if (!d.exists())
278   {
279     term("Output dir %s does not exist!\n",qPrint(outDir));
280   }
281 
282   QCString imgExt = getDotImageExtension();
283   QCString imgName = (QCString)outFile+"."+imgExt;
284   QCString absImgName = QCString(d.absPath())+"/"+imgName;
285   QCString absOutFile = QCString(d.absPath())+"/"+outFile;
286 
287   DotRunner dotRun(inFile);
288   if (format==GOF_BITMAP)
289   {
290     dotRun.addJob(Config_getEnumAsString(DOT_IMAGE_FORMAT),absImgName,srcFile,srcLine);
291   }
292   else // format==GOF_EPS
293   {
294     if (Config_getBool(USE_PDFLATEX))
295     {
296       dotRun.addJob("pdf",absOutFile+".pdf",srcFile,srcLine);
297     }
298     else
299     {
300       dotRun.addJob("ps",absOutFile+".eps",srcFile,srcLine);
301     }
302   }
303 
304   dotRun.preventCleanUp();
305   if (!dotRun.run())
306   {
307      return;
308   }
309 
310   Doxygen::indexList->addImageFile(imgName);
311 
312 }
313 
314 /*! Writes user defined image map to the output.
315  *  \param t text stream to write to
316  *  \param inFile just the basename part of the filename
317  *  \param outDir output directory
318  *  \param relPath relative path the to root of the output dir
319  *  \param baseName the base name of the output files
320  *  \param context the scope in which this graph is found (for resolving links)
321  *  \param graphId a unique id for this graph, use for dynamic sections
322  *  \param srcFile the source file
323  *  \param srcLine the line number in the source file
324  */
writeDotImageMapFromFile(TextStream & t,const QCString & inFile,const QCString & outDir,const QCString & relPath,const QCString & baseName,const QCString & context,int graphId,const QCString & srcFile,int srcLine)325 void writeDotImageMapFromFile(TextStream &t,
326                             const QCString &inFile, const QCString &outDir,
327                             const QCString &relPath, const QCString &baseName,
328                             const QCString &context,int graphId,
329                             const QCString &srcFile,int srcLine)
330 {
331 
332   Dir d(outDir.str());
333   if (!d.exists())
334   {
335     term("Output dir %s does not exist!\n",qPrint(outDir));
336   }
337 
338   QCString mapName = baseName+".map";
339   QCString imgExt = getDotImageExtension();
340   QCString imgName = baseName+"."+imgExt;
341   QCString absOutFile = QCString(d.absPath())+"/"+mapName;
342 
343   DotRunner dotRun(inFile);
344   dotRun.addJob(MAP_CMD,absOutFile,srcFile,srcLine);
345   dotRun.preventCleanUp();
346   if (!dotRun.run())
347   {
348     return;
349   }
350 
351   if (imgExt=="svg") // vector graphics
352   {
353     QCString svgName = outDir+"/"+baseName+".svg";
354     DotFilePatcher::writeSVGFigureLink(t,relPath,baseName,svgName);
355     DotFilePatcher patcher(svgName);
356     patcher.addSVGConversion("",TRUE,context,TRUE,graphId);
357     patcher.run();
358   }
359   else // bitmap graphics
360   {
361     TextStream tt;
362     t << "<img src=\"" << relPath << imgName << "\" alt=\""
363       << imgName << "\" border=\"0\" usemap=\"#" << mapName << "\"/>\n";
364     DotFilePatcher::convertMapFile(tt, absOutFile, relPath ,TRUE, context);
365     if (!tt.empty())
366     {
367       t << "<map name=\"" << mapName << "\" id=\"" << mapName << "\">";
368       t << tt.str();
369       t << "</map>\n";
370     }
371   }
372   d.remove(absOutFile.str());
373 }
374