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