Doxygen
formula.cpp
浏览该文件的文档.
1 /******************************************************************************
2  *
3  * Copyright (C) 1997-2020 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 "formula.h"
17 #include "message.h"
18 #include "config.h"
19 #include "util.h"
20 #include "portable.h"
21 #include "image.h"
22 #include "fileinfo.h"
23 #include "dir.h"
24 
25 #include <map>
26 #include <vector>
27 #include <string>
28 #include <utility>
29 #include <fstream>
30 
31 // TODO: remove these dependencies
32 #include "doxygen.h" // for Doxygen::indexList
33 #include "index.h" // for Doxygen::indexList
34 
35 static int determineInkscapeVersion(Dir &thisDir);
36 
37 // Remove the temporary files
38 #define RM_TMP_FILES (true)
39 //#define RM_TMP_FILES (false)
40 
42 {
43  void storeDisplaySize(int id,int w,int h)
44  {
45  displaySizeMap.insert(std::make_pair(id,DisplaySize(w,h)));
46  }
48  {
49  auto it = displaySizeMap.find(id);
50  if (it!=displaySizeMap.end())
51  {
52  return it->second;
53  }
54  return DisplaySize(-1,-1);
55  }
58  std::map<int,DisplaySize> displaySizeMap;
59 };
60 
62 {
63 }
64 
66 {
67  static FormulaManager fm;
68  return fm;
69 }
70 
71 void FormulaManager::readFormulas(const QCString &dir,bool doCompare)
72 {
73  std::ifstream f(dir.str()+"/formula.repository",std::ifstream::in);
74  if (f.is_open())
75  {
76  uint formulaCount=0;
77  msg("Reading formula repository...\n");
78  std::string line;
79  int lineNr=1;
80  while (getline(f,line))
81  {
82  // format: \_form#<digits>=<digits>x<digits>:formula
83  size_t hi=line.find('#');
84  size_t ei=line.find('=');
85  size_t se=line.find(':'); // find name and text separator.
86  if (ei==std::string::npos || hi==std::string::npos || se==std::string::npos || hi>se || ei<hi || ei>se)
87  {
88  warn_uncond("%s/formula.repository is corrupted at line %d!\n",qPrint(dir),lineNr);
89  break;
90  }
91  else
92  {
93  std::string formName = line.substr(0,se); // '\_form#<digits>=<digits>x<digits>' part
94  std::string formText = line.substr(se+1); // 'formula' part
95  int w=-1,h=-1;
96  size_t xi=formName.find('x',ei);
97  if (xi!=std::string::npos)
98  {
99  w=std::stoi(formName.substr(ei+1,xi-ei-1)); // digits from '=<digits>x' part as int
100  h=std::stoi(formName.substr(xi+1)); // digits from 'x<digits>' part as int
101  }
102  formName = formName.substr(0,ei); // keep only the '\_form#<digits>' part
103  if (doCompare)
104  {
105  int formId = std::stoi(formName.substr(hi+1));
106  std::string storedFormText = FormulaManager::instance().findFormula(formId);
107  if (storedFormText!=formText)
108  {
109  term("discrepancy between formula repositories! Remove "
110  "formula.repository and from_* files from output directories.\n");
111  }
112  formulaCount++;
113  }
114  int id = addFormula(formText);
115  if (w!=-1 && h!=-1)
116  {
117  p->storeDisplaySize(id,w,h);
118  }
119  }
120  lineNr++;
121  }
122  if (doCompare && formulaCount!=p->formulas.size())
123  {
124  term("size discrepancy between formula repositories! Remove "
125  "formula.repository and from_* files from output directories.\n");
126  }
127  }
128 }
129 
130 void FormulaManager::generateImages(const QCString &path,Format format,HighDPI hd) const
131 {
132  Dir d(path.str());
133  // store the original directory
134  if (!d.exists())
135  {
136  term("Output directory '%s' does not exist!\n",qPrint(path));
137  }
138  std::string oldDir = Dir::currentDirPath();
139  QCString macroFile = Config_getString(FORMULA_MACROFILE);
140  QCString stripMacroFile;
141  if (!macroFile.isEmpty())
142  {
143  FileInfo fi(macroFile.str());
144  macroFile=fi.absFilePath();
145  stripMacroFile = fi.fileName();
146  }
147 
148  // go to the html output directory (i.e. path)
150  Dir thisDir;
151  // generate a latex file containing one formula per page.
152  QCString texName="_formulas.tex";
153  IntVector formulasToGenerate;
154  std::ofstream f(texName.str(),std::ofstream::out | std::ofstream::binary);
155  if (f.is_open())
156  {
157  TextStream t(&f);
158  t << "\\documentclass{article}\n";
159  t << "\\usepackage{ifthen}\n";
160  t << "\\usepackage{epsfig}\n"; // for those who want to include images
161  t << "\\usepackage[utf8]{inputenc}\n"; // looks like some older distributions with newunicode package 1.1 need this option.
164  if (!macroFile.isEmpty())
165  {
166  copyFile(macroFile,stripMacroFile);
167  t << "\\input{" << stripMacroFile << "}\n";
168  }
169  t << "\\pagestyle{empty}\n";
170  t << "\\begin{document}\n";
171  for (int i=0; i<(int)p->formulas.size(); i++)
172  {
173  QCString resultName;
174  resultName.sprintf("form_%d.%s",i,format==Format::Vector?"svg":"png");
175  // only formulas for which no image exists are generated
176  FileInfo fi(resultName.str());
177  if (!fi.exists())
178  {
179  // we force a pagebreak after each formula
180  t << p->formulas[i].c_str() << "\n\\pagebreak\n\n";
181  formulasToGenerate.push_back(i);
182  }
183  Doxygen::indexList->addImageFile(resultName);
184  }
185  t << "\\end{document}\n";
186  t.flush();
187  f.close();
188  }
189  if (!formulasToGenerate.empty()) // there are new formulas
190  {
191  QCString latexCmd = "latex";
192  char args[4096];
194  int rerunCount=1;
195  while (rerunCount<8)
196  {
197  //printf("Running latex...\n");
198  sprintf(args,"-interaction=batchmode _formulas.tex >%s",Portable::devNull());
199  if ((Portable::system(latexCmd,args)!=0) || (Portable::system(latexCmd,args)!=0))
200  {
201  err("Problems running latex. Check your installation or look "
202  "for typos in _formulas.tex and check _formulas.log!\n");
204  Dir::setCurrent(oldDir);
205  return;
206  }
207  // check the log file if we need to run latex again to resolve references
208  QCString logFile = fileToString("_formulas.log");
209  if (logFile.isEmpty() ||
210  (logFile.find("Rerun to get cross-references right")==-1 && logFile.find("Rerun LaTeX")==-1))
211  {
212  break;
213  }
214  rerunCount++;
215  }
217  //printf("Running dvips...\n");
218  int pageIndex=1;
219  for (int pageNum : formulasToGenerate)
220  {
221  msg("Generating image form_%d.%s for formula\n",pageNum,(format==Format::Vector) ? "svg" : "png");
222  QCString formBase;
223  formBase.sprintf("_form%d",pageNum);
224  // run dvips to convert the page with number pageIndex to an
225  // postscript file.
226  sprintf(args,"-q -D 600 -n 1 -p %d -o %s_tmp.ps _formulas.dvi",
227  pageIndex,qPrint(formBase));
229  if (Portable::system("dvips",args)!=0)
230  {
231  err("Problems running dvips. Check your installation!\n");
233  Dir::setCurrent(oldDir);
234  return;
235  }
237 
238  // extract the bounding box for the postscript file
239  sprintf(args,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=bbox %s_tmp.ps 2>%s_tmp.epsi",
240  qPrint(formBase),qPrint(formBase));
243  {
244  err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand());
246  Dir::setCurrent(oldDir);
247  return;
248  }
250 
251  // extract the bounding box info from the generate .epsi file
252  int x1=0,y1=0,x2=0,y2=0;
253  FileInfo fi((formBase+"_tmp.epsi").str());
254  if (fi.exists())
255  {
256  QCString eps = fileToString(formBase+"_tmp.epsi");
257  int i = eps.find("%%BoundingBox:");
258  if (i!=-1)
259  {
260  sscanf(eps.data()+i,"%%%%BoundingBox:%d %d %d %d",&x1,&y1,&x2,&y2);
261  }
262  else
263  {
264  err("Couldn't extract bounding box from %s_tmp.epsi",qPrint(formBase));
265  }
266  }
267  //printf("Bounding box [%d %d %d %d]\n",x1,y1,x2,y2);
268 
269  // convert the corrected EPS to a bitmap
270  double scaleFactor = 1.25;
271  int zoomFactor = Config_getInt(FORMULA_FONTSIZE);
272  if (zoomFactor<8 || zoomFactor>50) zoomFactor=10;
273  scaleFactor *= zoomFactor/10.0;
274 
275  int width = (int)((x2-x1)*scaleFactor+0.5);
276  int height = (int)((y2-y1)*scaleFactor+0.5);
277  p->storeDisplaySize(pageNum,width,height);
278 
279  if (format==Format::Vector)
280  {
281  // crop the image to its bounding box
282  sprintf(args,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=pdfwrite"
283  " -o %s_tmp.pdf -c \"[/CropBox [%d %d %d %d] /PAGES pdfmark\" -f %s_tmp.ps",
284  qPrint(formBase),x1,y1,x2,y2,qPrint(formBase));
287  {
288  err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand());
290  Dir::setCurrent(oldDir);
291  return;
292  }
294 
295  // if we have pdf2svg available use it to create a SVG image
296  if (Portable::checkForExecutable("pdf2svg"))
297  {
298  sprintf(args,"%s_tmp.pdf form_%d.svg",qPrint(formBase),pageNum);
300  if (Portable::system("pdf2svg",args)!=0)
301  {
302  err("Problems running pdf2svg. Check your installation!\n");
304  Dir::setCurrent(oldDir);
305  return;
306  }
308  }
309  else if (Portable::checkForExecutable("inkscape")) // alternative is to use inkscape
310  {
311  int inkscapeVersion = determineInkscapeVersion(thisDir);
312  if (inkscapeVersion == -1)
313  {
314  err("Problems determining the version of inkscape. Check your installation!\n");
315  Dir::setCurrent(oldDir);
316  return;
317  }
318  else if (inkscapeVersion == 0)
319  {
320  sprintf(args,"-l form_%d.svg -z %s_tmp.pdf 2>%s",pageNum,qPrint(formBase),Portable::devNull());
321  }
322  else // inkscapeVersion >= 1
323  {
324  sprintf(args,"--export-type=svg --export-filename=form_%d.svg %s_tmp.pdf 2>%s",pageNum,qPrint(formBase),Portable::devNull());
325  }
327  if (Portable::system("inkscape",args)!=0)
328  {
329  err("Problems running inkscape. Check your installation!\n");
331  Dir::setCurrent(oldDir);
332  return;
333  }
335  }
336  else
337  {
338  err("Neither 'pdf2svg' nor 'inkscape' present for conversion of formula to 'svg'\n");
339  return;
340  }
341 
342  if (RM_TMP_FILES)
343  {
344  thisDir.remove(formBase.str()+"_tmp.pdf");
345  }
346  }
347  else // format==Format::Bitmap
348  {
349  // crop the image to its bounding box
350  sprintf(args,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=eps2write"
351  " -o %s_tmp.eps -f %s_tmp.ps",qPrint(formBase),qPrint(formBase));
354  {
355  err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand());
357  Dir::setCurrent(oldDir);
358  return;
359  }
360 
361  // read back %s_tmp.eps and replace
362  // bounding box values with x1,y1,x2,y2 and remove the HiResBoundingBox
363  std::ifstream epsIn(formBase.str()+"_tmp.eps",std::ifstream::in);
364  std::ofstream epsOut(formBase.str()+"_tmp_corr.eps",std::ofstream::out | std::ofstream::binary);
365  if (epsIn.is_open() && epsOut.is_open())
366  {
367  std::string line;
368  while (getline(epsIn,line))
369  {
370  if (line.rfind("%%BoundingBox",0)==0)
371  {
372  epsOut << "%%BoundingBox: " << x1 << " " << y1 << " " << x2 << " " << y2 << "\n";
373  }
374  else if (line.rfind("%%HiResBoundingBox",0)==0) // skip this one
375  {
376  }
377  else
378  {
379  epsOut << line << "\n";
380  }
381  }
382  epsIn.close();
383  epsOut.close();
384  }
385  else
386  {
387  err("Problems correcting the eps files from %s_tmp.eps to %s_tmp_corr.eps\n",
388  qPrint(formBase),qPrint(formBase));
389  Dir::setCurrent(oldDir);
390  return;
391  }
392 
393  if (hd==HighDPI::On) // for high DPI display it looks much better if the
394  // image resolution is higher than the display resolution
395  {
396  scaleFactor*=2;
397  }
398 
400  sprintf(args,"-q -dNOSAFER -dBATCH -dNOPAUSE -dEPSCrop -sDEVICE=pnggray -dGraphicsAlphaBits=4 -dTextAlphaBits=4 "
401  "-r%d -sOutputFile=form_%d.png %s_tmp_corr.eps",(int)(scaleFactor*72),pageNum,qPrint(formBase));
404  {
405  err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand());
407  Dir::setCurrent(oldDir);
408  return;
409  }
411 
412  if (RM_TMP_FILES)
413  {
414  thisDir.remove(formBase.str()+"_tmp.eps");
415  thisDir.remove(formBase.str()+"_tmp_corr.eps");
416  }
417  }
418 
419  // remove intermediate image files
420  if (RM_TMP_FILES)
421  {
422  thisDir.remove(formBase.str()+"_tmp.ps");
423  thisDir.remove(formBase.str()+"_tmp.epsi");
424  }
425  pageIndex++;
426  }
427  // remove intermediate files produced by latex
428  if (RM_TMP_FILES)
429  {
430  thisDir.remove("_formulas.dvi");
431  thisDir.remove("_formulas.log"); // keep file in case of errors
432  thisDir.remove("_formulas.aux");
433  }
434  }
435  // remove the latex file itself
436  if (RM_TMP_FILES) thisDir.remove("_formulas.tex");
437 
438  // write/update the formula repository so we know what text the
439  // generated images represent (we use this next time to avoid regeneration
440  // of the images, and to avoid forcing the user to delete all images in order
441  // to let a browser refresh the images).
442  f.open("formula.repository",std::ofstream::out | std::ofstream::binary);
443  if (f.is_open())
444  {
445  TextStream t(&f);
446  for (int i=0; i<(int)p->formulas.size(); i++)
447  {
448  DisplaySize size = p->getDisplaySize(i);
449  t << "\\_form#" << i;
450  if (size.width!=-1 && size.height!=-1)
451  {
452  t << "=" << size.width << "x" << size.height;
453  }
454  t << ":" << p->formulas[i].c_str() << "\n";
455  }
456  }
457  // reset the directory to the original location.
458  Dir::setCurrent(oldDir);
459 }
460 
462 {
463  p->formulas.clear();
464  p->formulaMap.clear();
465 }
466 
467 int FormulaManager::addFormula(const std::string &formulaText)
468 {
469  auto it = p->formulaMap.find(formulaText);
470  if (it!=p->formulaMap.end()) // already stored
471  {
472  return it->second;
473  }
474  // store new formula
475  int id = (int)p->formulas.size();
476  p->formulaMap.insert(std::pair<std::string,int>(formulaText,id));
477  p->formulas.push_back(formulaText);
478  return id;
479 }
480 
481 std::string FormulaManager::findFormula(int formulaId) const
482 {
483  if (formulaId>=0 && formulaId<(int)p->formulas.size())
484  {
485  return p->formulas[formulaId];
486  }
487  return std::string();
488 }
489 
491 {
492  return !p->formulas.empty();
493 }
494 
496 {
497  return p->getDisplaySize(formulaId);
498 }
499 
500 // helper function to detect and return the major version of inkscape.
501 // return -1 if the version cannot be determined.
502 static int determineInkscapeVersion(Dir &thisDir)
503 {
504  // The command line interface (CLI) of Inkscape 1.0 has changed in comparison to
505  // previous versions. In order to invokine Inkscape, the used version is detected
506  // and based on the version the right syntax of the CLI is chosen.
507  static int inkscapeVersion = -2;
508  if (inkscapeVersion == -2) // initial one time version check
509  {
510  QCString inkscapeVersionFile = "inkscape_version" ;
511  inkscapeVersion = -1;
512  QCString args = "-z --version >"+inkscapeVersionFile+" 2>"+Portable::devNull();
514  if (Portable::system("inkscape",args)!=0)
515  {
516  // looks like the old syntax gave problems, lets try the new syntax
517  args = " --version >"+inkscapeVersionFile+" 2>"+Portable::devNull();
518  if (Portable::system("inkscape",args)!=0)
519  {
521  return -1;
522  }
523  }
524  // read version file and determine major version
525  std::ifstream inkscapeVersionIn(inkscapeVersionFile.str(),std::ifstream::in);
526  if (inkscapeVersionIn.is_open())
527  {
528  std::string line;
529  while (getline(inkscapeVersionIn,line))
530  {
531  size_t dotPos = line.find('.');
532  if (line.rfind("Inkscape ",0)==0 && dotPos>0)
533  {
534  // get major version
535  inkscapeVersion = std::stoi(line.substr(9,dotPos-9));
536  break;
537  }
538  }
539  inkscapeVersionIn.close();
540  }
541  else // failed to open version file
542  {
544  return -1;
545  }
546  if (RM_TMP_FILES)
547  {
548  thisDir.remove(inkscapeVersionFile.str());
549  }
551  }
552  return inkscapeVersion;
553 }
formula.h
FormulaManager::HighDPI
HighDPI
Definition: formula.h:52
StringVector
std::vector< std::string > StringVector
Definition: containers.h:32
Dir::currentDirPath
static std::string currentDirPath()
Definition: dir.cpp:282
FormulaManager::readFormulas
void readFormulas(const QCString &dir, bool doCompare=false)
Definition: formula.cpp:71
fileinfo.h
FormulaManager::FormulaManager
FormulaManager()
Definition: formula.cpp:61
Dir::remove
bool remove(const std::string &path, bool acceptsAbsPath=true) const
Definition: dir.cpp:256
Dir
Class representing a directory in the file system
Definition: dir.h:68
Portable::devNull
const char * devNull()
Definition: portable.cpp:595
QCString::isEmpty
bool isEmpty() const
Returns TRUE iff the string is empty
Definition: qcstring.h:144
index.h
Doxygen::indexList
static IndexList * indexList
Definition: doxygen.h:114
FormulaManager::p
std::unique_ptr< Private > p
Definition: formula.h:63
copyFile
bool copyFile(const QCString &src, const QCString &dest)
Copies the contents of file with name src to the newly created file with name dest.
Definition: util.cpp:6439
FormulaManager::Format
Format
Definition: formula.h:51
FormulaManager::Format::Vector
@ Vector
FormulaManager::instance
static FormulaManager & instance()
Definition: formula.cpp:65
QCString::str
std::string str() const
Definition: qcstring.h:442
IntVector
std::vector< int > IntVector
Definition: containers.h:36
err
void err(const char *fmt,...)
Definition: message.cpp:203
TextStream
Text streaming class that buffers data.
Definition: textstream.h:33
QCString::find
int find(char c, int index=0, bool cs=TRUE) const
Definition: qcstring.cpp:38
FormulaManager::Private::formulaMap
IntMap formulaMap
Definition: formula.cpp:57
TextStream::flush
void flush()
Flushes the buffer.
Definition: textstream.h:188
FormulaManager::clear
void clear()
Definition: formula.cpp:461
FormulaManager::Private::storeDisplaySize
void storeDisplaySize(int id, int w, int h)
Definition: formula.cpp:43
determineInkscapeVersion
static int determineInkscapeVersion(Dir &thisDir)
Definition: formula.cpp:502
uint
unsigned uint
Definition: qcstring.h:40
warn_uncond
void warn_uncond(const char *fmt,...)
Definition: message.cpp:194
Config_getInt
#define Config_getInt(name)
Definition: config.h:34
FileInfo::exists
bool exists() const
Definition: fileinfo.cpp:30
message.h
Portable::ghostScriptCommand
const char * ghostScriptCommand()
Definition: portable.cpp:410
Portable::sysTimerStart
void sysTimerStart()
Definition: portable.cpp:470
IndexList::addImageFile
void addImageFile(const QCString &name)
Definition: index.h:105
Dir::absPath
std::string absPath() const
Definition: dir.cpp:305
doxygen.h
FormulaManager::HighDPI::On
@ On
FormulaManager::Private::formulas
StringVector formulas
Definition: formula.cpp:56
image.h
fileToString
QCString fileToString(const QCString &name, bool filter, bool isSourceCode)
Definition: util.cpp:1394
FormulaManager::DisplaySize::height
int height
Definition: formula.h:64
Portable::system
int system(const QCString &command, const QCString &args, bool commandHasConsole=true)
Definition: portable.cpp:42
Dir::setCurrent
static bool setCurrent(const std::string &path)
Definition: dir.cpp:290
FormulaManager::Private::displaySizeMap
std::map< int, DisplaySize > displaySizeMap
Definition: formula.cpp:58
writeLatexSpecialFormulaChars
void writeLatexSpecialFormulaChars(TextStream &t)
Definition: util.cpp:7084
RM_TMP_FILES
#define RM_TMP_FILES
Definition: formula.cpp:38
writeExtraLatexPackages
void writeExtraLatexPackages(TextStream &t)
Definition: util.cpp:7066
Portable::checkForExecutable
bool checkForExecutable(const QCString &fileName)
Definition: portable.cpp:396
FormulaManager::displaySize
DisplaySize displaySize(int formulaId) const
Definition: formula.cpp:495
FormulaManager::DisplaySize
Definition: formula.h:45
FormulaManager::Private
Definition: formula.cpp:41
FormulaManager
Definition: formula.h:27
FormulaManager::DisplaySize::width
int width
Definition: formula.h:63
IntMap
std::map< std::string, int > IntMap
Definition: containers.h:35
FormulaManager::hasFormulas
bool hasFormulas() const
Definition: formula.cpp:490
msg
void msg(const char *fmt,...)
Definition: message.cpp:53
term
void term(const char *fmt,...)
Definition: message.cpp:220
FileInfo
Minimal replacement for QFileInfo.
Definition: fileinfo.h:22
FileInfo::absFilePath
std::string absFilePath() const
Definition: fileinfo.cpp:101
qPrint
const char * qPrint(const char *s)
Definition: qcstring.h:589
Config_getString
#define Config_getString(name)
Definition: config.h:32
FormulaManager::addFormula
int addFormula(const std::string &formulaText)
Definition: formula.cpp:467
config.h
Portable::sysTimerStop
void sysTimerStop()
Definition: portable.cpp:475
QCString::data
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string
Definition: qcstring.h:153
FormulaManager::findFormula
std::string findFormula(int formulaId) const
Definition: formula.cpp:481
Dir::exists
bool exists() const
Definition: dir.cpp:199
FormulaManager::generateImages
void generateImages(const QCString &outputDir, Format format, HighDPI hd=HighDPI::Off) const
Definition: formula.cpp:130
FileInfo::fileName
std::string fileName() const
Definition: fileinfo.cpp:118
FormulaManager::Private::getDisplaySize
DisplaySize getDisplaySize(int id)
Definition: formula.cpp:47
portable.h
Portable versions of functions that are platform dependent.
dir.h
util.h
A bunch of utility functions.
QCString::sprintf
QCString & sprintf(const char *format,...)
Definition: qcstring.cpp:24
QCString
This is an alternative implementation of QCString.
Definition: qcstring.h:108