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