Doxygen
Loading...
Searching...
No Matches
dotrunner.cpp
Go to the documentation of this file.
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 <cassert>
17#include <cmath>
18
19#ifdef _MSC_VER
20#pragma warning( push )
21#pragma warning( disable : 4242 )
22#pragma warning( disable : 4244 )
23#pragma warning( disable : 4996 )
24#pragma warning( disable : 4456 )
25#pragma warning( disable : 4805 )
26#endif
27#if defined(__clang__)
28#pragma clang diagnostic push
29#pragma clang diagnostic ignored "-Wdeprecated-declarations"
30#pragma clang diagnostic ignored "-Wshadow"
31#endif
32#if defined(__GNUC__)
33#pragma GCC diagnostic push
34#pragma GCC diagnostic ignored "-Wshadow"
35#endif
36#include <gunzip.hh>
37#if defined(__GNUC__)
38#pragma GCC diagnostic pop
39#endif
40#if defined(__clang__)
41#pragma clang diagnostic pop
42#endif
43#ifdef _MSC_VER
44#pragma warning( pop )
45#endif
46
47#include "dotrunner.h"
48#include "util.h"
49#include "portable.h"
50#include "dot.h"
51#include "message.h"
52#include "config.h"
53#include "dir.h"
54#include "doxygen.h"
55
56// the graphicx LaTeX has a limitation of maximum size of 16384
57// To be on the save side we take it a little bit smaller i.e. 150 inch * 72 dpi
58// It is anyway hard to view these size of images
59#define MAX_LATEX_GRAPH_INCH 150
60#define MAX_LATEX_GRAPH_SIZE (MAX_LATEX_GRAPH_INCH * 72)
61
62//#define DBG(x) printf x
63#define DBG(x) do {} while(0)
64
65//-----------------------------------------------------------------------------------------
66
67// since dot silently reproduces the input file when it does not
68// support the PNG format, we need to check the result.
69static void checkPngResult(const QCString &imgName)
70{
71 FILE *f = Portable::fopen(imgName,"rb");
72 if (!f)
73 {
74 err("Could not read image '{}' generated by dot!\n",imgName);
75 return;
76 }
77
78 char data[4];
79 if (fread(data, 1, 4, f) != 4)
80 {
81 err("Could not read image '{}' generated by dot!\n",imgName);
82 fclose(f);
83 return;
84 }
85
86 if (!(data[1] == 'P' && data[2] == 'N' && data[3] == 'G'))
87 {
88 err("Image '{}' produced by dot is not a valid PNG!\n"
89 "You should either select a different format "
90 "(DOT_IMAGE_FORMAT in the config file) or install a more "
91 "recent version of graphviz (1.7+)\n", imgName);
92 }
93
94 fclose(f);
95}
96
97static bool resetPDFSize(const int width,const int height, const QCString &base)
98{
99 QCString tmpName = base+".tmp";
100 QCString patchFile = base+".dot";
101 Dir thisDir;
102 if (!thisDir.rename(patchFile.str(),tmpName.str()))
103 {
104 err("Failed to rename file {} to {}!\n",patchFile,tmpName);
105 return FALSE;
106 }
107 std::ifstream fi = Portable::openInputStream(tmpName);
108 std::ofstream t = Portable::openOutputStream(patchFile);
109 if (!fi.is_open())
110 {
111 err("problem opening file {} for patching!\n",tmpName);
112 thisDir.rename(tmpName.str(),patchFile.str());
113 return FALSE;
114 }
115 if (!t.is_open())
116 {
117 err("problem opening file {} for patching!\n",patchFile);
118 thisDir.rename(tmpName.str(),patchFile.str());
119 return FALSE;
120 }
121 std::string line;
122 while (getline(fi,line)) // foreach line
123 {
124 if (line.find("LATEX_PDF_SIZE") != std::string::npos)
125 {
126 double scale = (width > height ? width : height)/double(MAX_LATEX_GRAPH_INCH);
127 t << " size=\""<<width/scale << "," <<height/scale << "\";\n";
128 }
129 else
130 t << line << "\n";
131 }
132 fi.close();
133 t.close();
134 // remove temporary file
135 thisDir.remove(tmpName.str());
136 return TRUE;
137}
138
139bool DotRunner::readBoundingBox(const QCString &fileName,int *width,int *height,bool isEps)
140{
141 std::ifstream f = Portable::openInputStream(fileName);
142 if (!f.is_open())
143 {
144 err("Failed to open file {} for extracting bounding box\n",fileName);
145 return false;
146 }
147
148 // read file contents into string 'contents'
149 std::stringstream buffer;
150 buffer << f.rdbuf();
151 std::string contents = buffer.str();
152
153 // start of bounding box marker we are looking for
154 const std::string boundingBox = isEps ? "%%PageBoundingBox:" : "/MediaBox [";
155
156 // helper routine to extract the bounding boxes width and height
157 auto extractBoundingBox = [&fileName,&boundingBox,&width,&height](const char *s) -> bool
158 {
159 int x=0, y=0;
160 double w=0, h=0;
161 if (sscanf(s+boundingBox.length(),"%d %d %lf %lf",&x,&y,&w,&h)==4)
162 {
163 *width = static_cast<int>(std::ceil(w));
164 *height = static_cast<int>(std::ceil(h));
165 return true;
166 }
167 err("Failed to extract bounding box from generated diagram file {}\n",fileName);
168 return false;
169 };
170
171 // compressed segment start and end markers
172 const std::string streamStart = "stream\n";
173 const std::string streamEnd = "\nendstream";
174
175 auto detectDeflateStreamStart = [&streamStart](const char *s)
176 {
177 size_t len = streamStart.length();
178 bool streamOK = strncmp(s,streamStart.c_str(),len)==0;
179 if (streamOK) // ASCII marker matches, check stream header bytes as well
180 {
181 unsigned short header1 = static_cast<unsigned char>(s[len])<<8; // CMF byte
182 if (header1) // not end of string
183 {
184 unsigned short header = (static_cast<unsigned char>(s[len+1])) | header1; // FLG byte
185 // check for correct header (see https://www.rfc-editor.org/rfc/rfc1950)
186 return ((header&0x8F20)==0x0800) && (header%31)==0;
187 }
188 }
189 return false;
190 };
191
192 const size_t l = contents.length();
193 size_t i=0;
194 while (i<l)
195 {
196 if (!isEps && contents[i]=='s' && detectDeflateStreamStart(&contents[i]))
197 { // compressed stream start
198 int col=17;
199 i+=streamStart.length();
200 const size_t start=i;
201 DBG(("---- start stream at offset %08x\n",(int)i));
202 while (i<l)
203 {
204 if (contents[i]=='\n' && strncmp(&contents[i],streamEnd.c_str(),streamEnd.length())==0)
205 { // compressed block found in range [start..i]
206 DBG(("\n---- end stream at offset %08x\n",(int)i));
207 // decompress it into decompressBuf
208 std::vector<char> decompressBuf;
209 const char *source = &contents[start];
210 const size_t sourceLen = i-start;
211 size_t sourcePos = 0;
212 decompressBuf.reserve(sourceLen*2);
213 auto getter = [source,&sourcePos,sourceLen]() -> int {
214 return sourcePos<sourceLen ? static_cast<unsigned char>(source[sourcePos++]) : EOF;
215 };
216 auto putter = [&decompressBuf](const char c) -> int {
217 decompressBuf.push_back(c); return c;
218 };
219 Deflate(getter,putter);
220 // convert decompression buffer to string
221 std::string s(decompressBuf.begin(), decompressBuf.end());
222 DBG(("decompressed_data=[[[\n%s\n]]]\n",s.c_str()));
223 // search for bounding box marker
224 const size_t idx = s.find(boundingBox);
225 if (idx!=std::string::npos) // found bounding box in uncompressed data
226 {
227 return extractBoundingBox(s.c_str()+idx);
228 }
229 // continue searching after end stream marker
230 i+=streamEnd.length();
231 break;
232 }
233 else // compressed stream character
234 {
235 if (col>16) { col=0; DBG(("\n%08x: ",static_cast<int>(i))); }
236 DBG(("%02x ",static_cast<unsigned char>(contents[i])));
237 col++;
238 i++;
239 }
240 }
241 }
242 else if (((isEps && contents[i]=='%') || (!isEps && contents[i]=='/')) &&
243 strncmp(&contents[i],boundingBox.c_str(),boundingBox.length())==0)
244 { // uncompressed bounding box
245 return extractBoundingBox(&contents[i]);
246 }
247 else // uncompressed stream character
248 {
249 i++;
250 }
251 }
252 err("Failed to find bounding box in generated diagram file {}\n",fileName);
253 // nothing found
254 return false;
255}
256
257//---------------------------------------------------------------------------------
258
259DotRunner::DotRunner(const QCString& absDotName, const QCString& md5Hash)
260 : m_file(absDotName)
261 , m_md5Hash(md5Hash)
262 , m_dotExe(Doxygen::verifiedDotPath)
263 , m_cleanUp(Config_getBool(DOT_CLEANUP))
264{
265}
266
267
268void DotRunner::addJob(const QCString &format, const QCString &output,
269 const QCString &srcFile,int srcLine)
270{
271
272 for (auto& s: m_jobs)
273 {
274 if (s.format != format) continue;
275 if (s.output != output) continue;
276 // we have this job already
277 return;
278 }
279 auto args = QCString("-T") + format + " -o \"" + output + "\"";
280 m_jobs.emplace_back(format, output, args, srcFile, srcLine);
281}
282
284{
285 int index = output.findRev('.');
286 if (index < 0) return output;
287 return output.left(index);
288}
289
291{
292 int exitCode=0;
293
294 QCString dotArgs;
295
296 QCString srcFile;
297 int srcLine=-1;
298
299 // create output
300 if (Config_getBool(DOT_MULTI_TARGETS))
301 {
302 dotArgs=QCString("\"")+m_file+"\"";
303 for (auto& s: m_jobs)
304 {
305 dotArgs+=' ';
306 dotArgs+=s.args;
307 }
308 if (!m_jobs.empty())
309 {
310 srcFile = m_jobs.front().srcFile;
311 srcLine = m_jobs.front().srcLine;
312 }
313 if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error;
314 }
315 else
316 {
317 for (auto& s : m_jobs)
318 {
319 srcFile = s.srcFile;
320 srcLine = s.srcLine;
321 dotArgs=QCString("\"")+m_file+"\" "+s.args;
322 if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error;
323 }
324 }
325
326 // check output
327 // As there should be only one pdf file be generated, we don't need code for regenerating multiple pdf files in one call
328 for (auto& s : m_jobs)
329 {
330 if (s.format.startsWith("pdf"))
331 {
332 int width=0,height=0;
333 if (!readBoundingBox(s.output,&width,&height,FALSE)) goto error;
334 if ((width > MAX_LATEX_GRAPH_SIZE) || (height > MAX_LATEX_GRAPH_SIZE))
335 {
336 if (!resetPDFSize(width,height,getBaseNameOfOutput(s.output))) goto error;
337 dotArgs=QCString("\"")+m_file+"\" "+s.args;
338 if ((exitCode=Portable::system(m_dotExe,dotArgs,FALSE))!=0) goto error;
339 }
340 }
341
342 if (s.format.startsWith("png"))
343 {
344 checkPngResult(s.output);
345 }
346 }
347
348 // remove .dot files
349 if (m_cleanUp)
350 {
351 //printf("removing dot file %s\n",qPrint(m_file));
353 }
354
355 // create checksum file
356 if (!m_md5Hash.isEmpty())
357 {
358 QCString md5Name = getBaseNameOfOutput(m_file) + ".md5";
359 FILE *f = Portable::fopen(md5Name,"w");
360 if (f)
361 {
362 fwrite(m_md5Hash.data(),1,32,f);
363 fclose(f);
364 }
365 }
366 return TRUE;
367error:
368 err_full(srcFile,srcLine,"Problems running dot: exit code={}, command='{}', arguments='{}'",
369 exitCode,m_dotExe,dotArgs);
370 return FALSE;
371}
372
373
Class representing a directory in the file system.
Definition dir.h:75
bool remove(const std::string &path, bool acceptsAbsPath=true) const
Definition dir.cpp:314
bool rename(const std::string &orgName, const std::string &newName, bool acceptsAbsPath=true) const
Definition dir.cpp:321
QCString m_file
Definition dotrunner.h:64
bool m_cleanUp
Definition dotrunner.h:67
QCString m_md5Hash
Definition dotrunner.h:65
DotRunner(const QCString &absDotName, const QCString &md5Hash=QCString())
Creates a runner for a dot file.
void addJob(const QCString &format, const QCString &output, const QCString &srcFile, int srcLine)
Adds an additional job to the run.
bool run()
Runs dot for all jobs added.
std::vector< DotJob > m_jobs
Definition dotrunner.h:68
static bool readBoundingBox(const QCString &fileName, int *width, int *height, bool isEps)
QCString m_dotExe
Definition dotrunner.h:66
This class serves as a namespace for global variables used by doxygen.
Definition doxygen.h:94
This is an alternative implementation of QCString.
Definition qcstring.h:101
const std::string & str() const
Definition qcstring.h:552
int findRev(char c, int index=-1, bool cs=TRUE) const
Definition qcstring.cpp:96
QCString left(size_t len) const
Definition qcstring.h:229
#define Config_getBool(name)
Definition config.h:33
#define MAX_LATEX_GRAPH_INCH
Definition dotrunner.cpp:59
#define DBG(x)
Definition dotrunner.cpp:63
QCString getBaseNameOfOutput(const QCString &output)
static void checkPngResult(const QCString &imgName)
Definition dotrunner.cpp:69
#define MAX_LATEX_GRAPH_SIZE
Definition dotrunner.cpp:60
static bool resetPDFSize(const int width, const int height, const QCString &base)
Definition dotrunner.cpp:97
static bool extractBoundingBox(const QCString &formBase, int *x1, int *y1, int *x2, int *y2, double *x1hi, double *y1hi, double *x2hi, double *y2hi)
Definition formula.cpp:299
#define err(fmt,...)
Definition message.h:127
#define err_full(file, line, fmt,...)
Definition message.h:132
std::ifstream openInputStream(const QCString &name, bool binary=false, bool openAtEnd=false)
Definition portable.cpp:660
std::ofstream openOutputStream(const QCString &name, bool append=false)
Definition portable.cpp:649
void unlink(const QCString &fileName)
Definition portable.cpp:545
FILE * fopen(const QCString &fileName, const QCString &mode)
Definition portable.cpp:350
int system(const QCString &command, const QCString &args, bool commandHasConsole=true)
Definition portable.cpp:106
Portable versions of functions that are platform dependent.
#define TRUE
Definition qcstring.h:37
#define FALSE
Definition qcstring.h:34
A bunch of utility functions.