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