/******************************************************************************
*
* 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 "config.h"
#include "doxygen.h"
#include "index.h"
#include "md5.h"
#include "message.h"
#include "util.h"
#include "dot.h"
#include "dotrunner.h"
#include "dotgraph.h"
#include "dotnode.h"
#include "dotfilepatcher.h"
#define MAP_CMD "cmapx"
//QCString DotGraph::DOT_FONTNAME; // will be initialized in initDot
//int DotGraph::DOT_FONTSIZE; // will be initialized in initDot
/*! Checks if a file "baseName".md5 exists. If so the contents
* are compared with \a md5. If equal FALSE is returned.
* The .md5 is created or updated after successful creation of the output file.
*/
static bool checkMd5Signature(const QCString &baseName,
const QCString &md5)
{
QFile f(baseName+".md5");
if (f.open(IO_ReadOnly))
{
// read checksum
QCString md5stored(33);
int bytesRead=f.readBlock(md5stored.rawData(),32);
md5stored[32]='\0';
// compare checksum
if (bytesRead==32 && md5==md5stored)
{
// bail out if equal
return FALSE;
}
}
f.close();
return TRUE;
}
static bool checkDeliverables(const QCString &file1,
const QCString &file2=QCString())
{
bool file1Ok = TRUE;
bool file2Ok = TRUE;
if (!file1.isEmpty())
{
QFileInfo fi(file1);
file1Ok = (fi.exists() && fi.size()>0);
}
if (!file2.isEmpty())
{
QFileInfo fi(file2);
file2Ok = (fi.exists() && fi.size()>0);
}
return file1Ok && file2Ok;
}
static void removeDotGraph(const QCString &dotName)
{
if (Config_getBool(DOT_CLEANUP))
{
QDir d;
d.remove(dotName);
}
}
static bool insertMapFile(FTextStream &out,const QCString &mapFile,
const QCString &relPath,const QCString &mapLabel)
{
QFileInfo fi(mapFile);
if (fi.exists() && fi.size()>0) // reuse existing map file
{
QGString tmpstr;
FTextStream tmpout(&tmpstr);
DotFilePatcher::convertMapFile(tmpout,mapFile,relPath,FALSE);
if (!tmpstr.isEmpty())
{
out << "" << endl;
}
return TRUE;
}
return FALSE; // no map file yet, need to generate it
}
//--------------------------------------------------------------------
QCString DotGraph::imgName() const
{
return m_baseName + ((m_graphFormat == GOF_BITMAP) ?
("." + getDotImageExtension()) : (Config_getBool(USE_PDFLATEX) ? ".pdf" : ".eps"));
}
QCString DotGraph::writeGraph(
FTextStream& t, // output stream for the code file (html, ...)
GraphOutputFormat gf, // bitmap(png/svg) or ps(eps/pdf)
EmbeddedOutputFormat ef, // html, latex, ...
const char* path, // output folder
const char* fileName, // name of the code file (for code patcher)
const char* relPath, // output folder relative to code file
bool generateImageMap, // in case of bitmap, shall there be code generated?
int graphId) // number of this graph in the current code, used in svg code
{
m_graphFormat = gf;
m_textFormat = ef;
m_dir = QDir(path);
m_fileName = fileName;
m_relPath = relPath;
m_generateImageMap = generateImageMap;
m_graphId = graphId;
m_absPath = QCString(m_dir.absPath().data()) + "/";
m_baseName = getBaseName();
computeTheGraph();
m_regenerate = prepareDotFile();
if (!m_doNotAddImageToIndex) Doxygen::indexList->addImageFile(imgName());
generateCode(t);
return m_baseName;
}
bool DotGraph::prepareDotFile()
{
if (!m_dir.exists())
{
term("Output dir %s does not exist!\n", m_dir.path().data());
}
QCString sigStr(33);
uchar md5_sig[16];
// calculate md5
MD5Buffer((const unsigned char*)m_theGraph.data(), m_theGraph.length(), md5_sig);
// convert result to a string
MD5SigToString(md5_sig, sigStr.rawData(), 33);
// already queued files are processed again in case the output format has changed
if (!checkMd5Signature(absBaseName(), sigStr) &&
checkDeliverables(absImgName(),
m_graphFormat == GOF_BITMAP && m_generateImageMap ? absMapName() : QCString()
)
)
{
// all needed files are there
removeDotGraph(absDotName());
return FALSE;
}
// need to rebuild the image
// write .dot file because image was new or has changed
QFile f(absDotName());
if (!f.open(IO_WriteOnly))
{
err("Could not open file %s for writing\n",f.name().data());
return TRUE;
}
FTextStream t(&f);
t << m_theGraph;
f.close();
if (m_graphFormat == GOF_BITMAP)
{
// run dot to create a bitmap image
DotRunner * dotRun = DotManager::instance()->createRunner(absDotName(), sigStr);
dotRun->addJob(Config_getEnum(DOT_IMAGE_FORMAT), absImgName());
if (m_generateImageMap) dotRun->addJob(MAP_CMD, absMapName());
}
else if (m_graphFormat == GOF_EPS)
{
// run dot to create a .eps image
DotRunner *dotRun = DotManager::instance()->createRunner(absDotName(), sigStr);
if (Config_getBool(USE_PDFLATEX))
{
dotRun->addJob("pdf",absImgName());
}
else
{
dotRun->addJob("ps",absImgName());
}
}
return TRUE;
}
void DotGraph::generateCode(FTextStream &t)
{
QCString imgExt = getDotImageExtension();
if (m_graphFormat==GOF_BITMAP && m_textFormat==EOF_DocBook)
{
t << "