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 <sstream>
17
18 #include "config.h"
19 #include "doxygen.h"
20 #include "index.h"
21 #include "md5.h"
22 #include "message.h"
23 #include "util.h"
24
25 #include "dot.h"
26 #include "dotrunner.h"
27 #include "dotgraph.h"
28 #include "dotnode.h"
29 #include "dotfilepatcher.h"
30 #include "fileinfo.h"
31
32 #define MAP_CMD "cmapx"
33
34 //QCString DotGraph::DOT_FONTNAME; // will be initialized in initDot
35 //int DotGraph::DOT_FONTSIZE; // will be initialized in initDot
36
37 /*! Checks if a file "baseName".md5 exists. If so the contents
38 * are compared with \a md5. If equal FALSE is returned.
39 * The .md5 is created or updated after successful creation of the output file.
40 */
sameMd5Signature(const QCString & baseName,const QCString & md5)41 static bool sameMd5Signature(const QCString &baseName,
42 const QCString &md5)
43 {
44 bool same = false;
45 char md5stored[33];
46 md5stored[0]=0;
47 std::ifstream f(baseName.str()+".md5",std::ifstream::in | std::ifstream::binary);
48 if (f.is_open())
49 {
50 // read checksum
51 f.read(md5stored,32);
52 md5stored[32]='\0';
53 // compare checksum
54 if (!f.fail() && md5==md5stored)
55 {
56 same = true;
57 }
58 //printf("sameSignature(%s,%s==%s)=%d\n",qPrint(baseName),md5stored,qPrint(md5),same);
59 }
60 else
61 {
62 //printf("sameSignature(%s) not found\n",qPrint(baseName));
63 }
64 return same;
65 }
66
deliverablesPresent(const QCString & file1,const QCString & file2)67 static bool deliverablesPresent(const QCString &file1,const QCString &file2)
68 {
69 bool file1Ok = true;
70 bool file2Ok = true;
71 if (!file1.isEmpty())
72 {
73 FileInfo fi(file1.str());
74 file1Ok = (fi.exists() && fi.size()>0);
75 }
76 if (!file2.isEmpty())
77 {
78 FileInfo fi(file2.str());
79 file2Ok = (fi.exists() && fi.size()>0);
80 }
81 return file1Ok && file2Ok;
82 }
83
insertMapFile(TextStream & out,const QCString & mapFile,const QCString & relPath,const QCString & mapLabel)84 static bool insertMapFile(TextStream &out,const QCString &mapFile,
85 const QCString &relPath,const QCString &mapLabel)
86 {
87 FileInfo fi(mapFile.str());
88 if (fi.exists() && fi.size()>0) // reuse existing map file
89 {
90 TextStream t;
91 DotFilePatcher::convertMapFile(t,mapFile,relPath,false);
92 if (!t.empty())
93 {
94 out << "<map name=\"" << mapLabel << "\" id=\"" << mapLabel << "\">\n";
95 out << t.str();
96 out << "</map>\n";
97 }
98 return true;
99 }
100 return false; // no map file yet, need to generate it
101 }
102
103 //--------------------------------------------------------------------
104
imgName() const105 QCString DotGraph::imgName() const
106 {
107 return m_baseName + ((m_graphFormat == GOF_BITMAP) ?
108 ("." + getDotImageExtension()) : (Config_getBool(USE_PDFLATEX) ? ".pdf" : ".eps"));
109 }
110
writeGraph(TextStream & t,GraphOutputFormat gf,EmbeddedOutputFormat ef,const QCString & path,const QCString & fileName,const QCString & relPath,bool generateImageMap,int graphId)111 QCString DotGraph::writeGraph(
112 TextStream& t, // output stream for the code file (html, ...)
113 GraphOutputFormat gf, // bitmap(png/svg) or ps(eps/pdf)
114 EmbeddedOutputFormat ef, // html, latex, ...
115 const QCString &path, // output folder
116 const QCString &fileName, // name of the code file (for code patcher)
117 const QCString &relPath, // output folder relative to code file
118 bool generateImageMap, // in case of bitmap, shall there be code generated?
119 int graphId) // number of this graph in the current code, used in svg code
120 {
121 m_graphFormat = gf;
122 m_textFormat = ef;
123 m_dir = Dir(path.str());
124 m_fileName = fileName;
125 m_relPath = relPath;
126 m_generateImageMap = generateImageMap;
127 m_graphId = graphId;
128
129 m_absPath = m_dir.absPath() + "/";
130 m_baseName = getBaseName();
131
132 computeTheGraph();
133
134 m_regenerate = prepareDotFile();
135
136 if (!m_doNotAddImageToIndex) Doxygen::indexList->addImageFile(imgName());
137
138 generateCode(t);
139
140 return m_baseName;
141 }
142
prepareDotFile()143 bool DotGraph::prepareDotFile()
144 {
145 if (!m_dir.exists())
146 {
147 term("Output dir %s does not exist!\n", m_dir.path().c_str());
148 }
149
150 char sigStr[33];
151 uchar md5_sig[16];
152 // calculate md5
153 MD5Buffer((const unsigned char*)m_theGraph.data(), m_theGraph.length(), md5_sig);
154 // convert result to a string
155 MD5SigToString(md5_sig, sigStr);
156
157 // already queued files are processed again in case the output format has changed
158
159 if (sameMd5Signature(absBaseName(), sigStr) &&
160 deliverablesPresent(absImgName(),
161 m_graphFormat == GOF_BITMAP && m_generateImageMap ? absMapName() : QCString()
162 )
163 )
164 {
165 // all needed files are there
166 return FALSE;
167 }
168
169 // need to rebuild the image
170
171 // write .dot file because image was new or has changed
172 std::ofstream f(absDotName().str(),std::ofstream::out | std::ofstream::binary);
173 if (!f.is_open())
174 {
175 err("Could not open file %s for writing\n",qPrint(absDotName()));
176 return TRUE;
177 }
178 f << m_theGraph;
179 f.close();
180
181 if (m_graphFormat == GOF_BITMAP)
182 {
183 // run dot to create a bitmap image
184 DotRunner * dotRun = DotManager::instance()->createRunner(absDotName(), sigStr);
185 dotRun->addJob(Config_getEnumAsString(DOT_IMAGE_FORMAT), absImgName(), absDotName(), 1);
186 if (m_generateImageMap) dotRun->addJob(MAP_CMD, absMapName(), absDotName(), 1);
187 }
188 else if (m_graphFormat == GOF_EPS)
189 {
190 // run dot to create a .eps image
191 DotRunner *dotRun = DotManager::instance()->createRunner(absDotName(), sigStr);
192 if (Config_getBool(USE_PDFLATEX))
193 {
194 dotRun->addJob("pdf",absImgName(),absDotName(),1);
195 }
196 else
197 {
198 dotRun->addJob("ps",absImgName(),absDotName(),1);
199 }
200 }
201 return TRUE;
202 }
203
generateCode(TextStream & t)204 void DotGraph::generateCode(TextStream &t)
205 {
206 QCString imgExt = getDotImageExtension();
207 if (m_graphFormat==GOF_BITMAP && m_textFormat==EOF_DocBook)
208 {
209 t << "<para>\n";
210 t << " <informalfigure>\n";
211 t << " <mediaobject>\n";
212 t << " <imageobject>\n";
213 t << " <imagedata";
214 t << " width=\"50%\" align=\"center\" valign=\"middle\" scalefit=\"0\" fileref=\"" << m_relPath << m_baseName << "." << imgExt << "\">";
215 t << "</imagedata>\n";
216 t << " </imageobject>\n";
217 t << " </mediaobject>\n";
218 t << " </informalfigure>\n";
219 t << "</para>\n";
220 }
221 else if (m_graphFormat==GOF_BITMAP && m_generateImageMap) // produce HTML to include the image
222 {
223 if (imgExt=="svg") // add link to SVG file without map file
224 {
225 if (!m_noDivTag) t << "<div class=\"center\">";
226 if (m_regenerate || !DotFilePatcher::writeSVGFigureLink(t,m_relPath,m_baseName,absImgName())) // need to patch the links in the generated SVG file
227 {
228 if (m_regenerate)
229 {
230 DotManager::instance()->
231 createFilePatcher(absImgName())->
232 addSVGConversion(m_relPath,FALSE,QCString(),m_zoomable,m_graphId);
233 }
234 int mapId = DotManager::instance()->
235 createFilePatcher(m_fileName)->
236 addSVGObject(m_baseName,absImgName(),m_relPath);
237 t << "<!-- SVG " << mapId << " -->\n";
238 }
239 if (!m_noDivTag) t << "</div>\n";
240 }
241 else // add link to bitmap file with image map
242 {
243 if (!m_noDivTag) t << "<div class=\"center\">";
244 t << "<img src=\"" << relImgName() << "\" border=\"0\" usemap=\"#" << correctId(getMapLabel()) << "\" alt=\"" << getImgAltText() << "\"/>";
245 if (!m_noDivTag) t << "</div>";
246 t << "\n";
247 if (m_regenerate || !insertMapFile(t, absMapName(), m_relPath, correctId(getMapLabel())))
248 {
249 int mapId = DotManager::instance()->
250 createFilePatcher(m_fileName)->
251 addMap(absMapName(), m_relPath, m_urlOnly, QCString(), getMapLabel());
252 t << "<!-- MAP " << mapId << " -->\n";
253 }
254 }
255 }
256 else if (m_graphFormat==GOF_EPS) // produce tex to include the .eps image
257 {
258 if (m_regenerate || !DotFilePatcher::writeVecGfxFigure(t,m_baseName,absBaseName()))
259 {
260 int figId = DotManager::instance()->
261 createFilePatcher(m_fileName)->
262 addFigure(m_baseName,absBaseName(),FALSE /*TRUE*/);
263 t << "\n% FIG " << figId << "\n";
264 }
265 }
266 }
267
writeGraphHeader(TextStream & t,const QCString & title)268 void DotGraph::writeGraphHeader(TextStream &t,const QCString &title)
269 {
270 int fontSize = Config_getInt(DOT_FONTSIZE);
271 QCString fontName = Config_getString(DOT_FONTNAME);
272 t << "digraph ";
273 if (title.isEmpty())
274 {
275 t << "\"Dot Graph\"";
276 }
277 else
278 {
279 t << "\"" << convertToXML(title) << "\"";
280 }
281 t << "\n{\n";
282 if (Config_getBool(INTERACTIVE_SVG)) // insert a comment to force regeneration when this
283 // option is toggled
284 {
285 t << " // INTERACTIVE_SVG=YES\n";
286 }
287 t << " // LATEX_PDF_SIZE\n"; // write placeholder for LaTeX PDF bounding box size replacement
288 if (Config_getBool(DOT_TRANSPARENT))
289 {
290 t << " bgcolor=\"transparent\";\n";
291 }
292 t << " edge [fontname=\"" << fontName << "\","
293 "fontsize=\"" << fontSize << "\","
294 "labelfontname=\"" << fontName << "\","
295 "labelfontsize=\"" << fontSize << "\"];\n";
296 t << " node [fontname=\"" << fontName << "\","
297 "fontsize=\"" << fontSize << "\",shape=record];\n";
298 }
299
writeGraphFooter(TextStream & t)300 void DotGraph::writeGraphFooter(TextStream &t)
301 {
302 t << "}\n";
303 }
304
computeGraph(DotNode * root,GraphType gt,GraphOutputFormat format,const QCString & rank,bool renderParents,bool backArrows,const QCString & title,QCString & graphStr)305 void DotGraph::computeGraph(DotNode *root,
306 GraphType gt,
307 GraphOutputFormat format,
308 const QCString &rank, // either "LR", "RL", or ""
309 bool renderParents,
310 bool backArrows,
311 const QCString &title,
312 QCString &graphStr)
313 {
314 //printf("computeMd5Signature\n");
315 TextStream md5stream;
316 writeGraphHeader(md5stream,title);
317 if (!rank.isEmpty())
318 {
319 md5stream << " rankdir=\"" << rank << "\";\n";
320 }
321 root->clearWriteFlag();
322 root->write(md5stream, gt, format, gt!=CallGraph && gt!=Dependency, TRUE, backArrows);
323 if (renderParents)
324 {
325 for (const auto &pn : root->parents())
326 {
327 if (pn->isVisible())
328 {
329 const auto &children = pn->children();
330 auto child_it = std::find(children.begin(),children.end(),root);
331 size_t index = child_it - children.begin();
332 root->writeArrow(md5stream, // stream
333 gt, // graph type
334 format, // output format
335 pn, // child node
336 &pn->edgeInfo()[index], // edge info
337 FALSE, // topDown?
338 backArrows // point back?
339 );
340 }
341 pn->write(md5stream, // stream
342 gt, // graph type
343 format, // output format
344 TRUE, // topDown?
345 FALSE, // toChildren?
346 backArrows // backward pointing arrows?
347 );
348 }
349 }
350 writeGraphFooter(md5stream);
351
352 graphStr=md5stream.str();
353 }
354
355