My Project
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
formula.cpp
Go to the documentation of this file.
1 /******************************************************************************
2  *
3  *
4  * Copyright (C) 1997-2015 by Dimitri van Heesch.
5  *
6  * Permission to use, copy, modify, and distribute this software and its
7  * documentation under the terms of the GNU General Public License is hereby
8  * granted. No representations are made about the suitability of this software
9  * for any purpose. It is provided "as is" without express or implied warranty.
10  * See the GNU General Public License for more details.
11  *
12  * Documents produced by Doxygen are derivative works derived from the
13  * input used in their production; they are not affected by this license.
14  *
15  */
16 
17 #include <stdlib.h>
18 #include <qfile.h>
19 #include <qfileinfo.h>
20 #include <qtextstream.h>
21 #include <qdir.h>
22 
23 #include "formula.h"
24 #include "image.h"
25 #include "util.h"
26 #include "message.h"
27 #include "config.h"
28 #include "portable.h"
29 #include "index.h"
30 #include "doxygen.h"
31 #include "ftextstream.h"
32 
33 Formula::Formula(const char *text)
34 {
35  static int count=0;
36  number = count++;
37  form=text;
38 }
39 
41 {
42 }
43 
45 {
46  return number;
47 }
48 
49 void FormulaList::generateBitmaps(const char *path)
50 {
51  int x1,y1,x2,y2;
52  QDir d(path);
53  // store the original directory
54  if (!d.exists()) { err("Output dir %s does not exist!\n",path); exit(1); }
55  QCString oldDir = QDir::currentDirPath().utf8();
56  // go to the html output directory (i.e. path)
57  QDir::setCurrent(d.absPath());
58  QDir thisDir;
59  // generate a latex file containing one formula per page.
60  QCString texName="_formulas.tex";
61  QList<int> pagesToGenerate;
62  pagesToGenerate.setAutoDelete(TRUE);
63  FormulaListIterator fli(*this);
64  Formula *formula;
65  QFile f(texName);
66  bool formulaError=FALSE;
67  if (f.open(IO_WriteOnly))
68  {
69  FTextStream t(&f);
70  if (Config_getBool(LATEX_BATCHMODE)) t << "\\batchmode" << endl;
71  t << "\\documentclass{article}" << endl;
72  t << "\\usepackage{epsfig}" << endl; // for those who want to include images
74  t << "\\pagestyle{empty}" << endl;
75  t << "\\begin{document}" << endl;
76  int page=0;
77  for (fli.toFirst();(formula=fli.current());++fli)
78  {
79  QCString resultName;
80  resultName.sprintf("form_%d.png",formula->getId());
81  // only formulas for which no image exists are generated
82  QFileInfo fi(resultName);
83  if (!fi.exists())
84  {
85  // we force a pagebreak after each formula
86  t << formula->getFormulaText() << endl << "\\pagebreak\n\n";
87  pagesToGenerate.append(new int(page));
88  }
89  Doxygen::indexList->addImageFile(resultName);
90  page++;
91  }
92  t << "\\end{document}" << endl;
93  f.close();
94  }
95  if (pagesToGenerate.count()>0) // there are new formulas
96  {
97  //printf("Running latex...\n");
98  //system("latex _formulas.tex </dev/null >/dev/null");
99  QCString latexCmd = Config_getString(LATEX_CMD_NAME);
100  if (latexCmd.isEmpty()) latexCmd="latex";
102  if (portable_system(latexCmd,"_formulas.tex")!=0)
103  {
104  err("Problems running latex. Check your installation or look "
105  "for typos in _formulas.tex and check _formulas.log!\n");
106  formulaError=TRUE;
107  //return;
108  }
110  //printf("Running dvips...\n");
111  QListIterator<int> pli(pagesToGenerate);
112  int *pagePtr;
113  int pageIndex=1;
114  for (;(pagePtr=pli.current());++pli,++pageIndex)
115  {
116  int pageNum=*pagePtr;
117  msg("Generating image form_%d.png for formula\n",pageNum);
118  char dviArgs[4096];
119  QCString formBase;
120  formBase.sprintf("_form%d",pageNum);
121  // run dvips to convert the page with number pageIndex to an
122  // encapsulated postscript.
123  sprintf(dviArgs,"-q -D 600 -E -n 1 -p %d -o %s.eps _formulas.dvi",
124  pageIndex,formBase.data());
126  if (portable_system("dvips",dviArgs)!=0)
127  {
128  err("Problems running dvips. Check your installation!\n");
130  QDir::setCurrent(oldDir);
131  return;
132  }
134  // now we read the generated postscript file to extract the bounding box
135  QFileInfo fi(formBase+".eps");
136  if (fi.exists())
137  {
138  QCString eps = fileToString(formBase+".eps");
139  int i=eps.find("%%BoundingBox:");
140  if (i!=-1)
141  {
142  sscanf(eps.data()+i,"%%%%BoundingBox:%d %d %d %d",&x1,&y1,&x2,&y2);
143  }
144  else
145  {
146  err("Couldn't extract bounding box!\n");
147  }
148  }
149  // next we generate a postscript file which contains the eps
150  // and displays it in the right colors and the right bounding box
151  f.setName(formBase+".ps");
152  if (f.open(IO_WriteOnly))
153  {
154  FTextStream t(&f);
155  t << "1 1 1 setrgbcolor" << endl; // anti-alias to white background
156  t << "newpath" << endl;
157  t << "-1 -1 moveto" << endl;
158  t << (x2-x1+2) << " -1 lineto" << endl;
159  t << (x2-x1+2) << " " << (y2-y1+2) << " lineto" << endl;
160  t << "-1 " << (y2-y1+2) << " lineto" <<endl;
161  t << "closepath" << endl;
162  t << "fill" << endl;
163  t << -x1 << " " << -y1 << " translate" << endl;
164  t << "0 0 0 setrgbcolor" << endl;
165  t << "(" << formBase << ".eps) run" << endl;
166  f.close();
167  }
168  // scale the image so that it is four times larger than needed.
169  // and the sizes are a multiple of four.
170  double scaleFactor = 16.0/3.0;
171  int zoomFactor = Config_getInt(FORMULA_FONTSIZE);
172  if (zoomFactor<8 || zoomFactor>50) zoomFactor=10;
173  scaleFactor *= zoomFactor/10.0;
174  int gx = (((int)((x2-x1)*scaleFactor))+3)&~1;
175  int gy = (((int)((y2-y1)*scaleFactor))+3)&~1;
176  // Then we run ghostscript to convert the postscript to a pixmap
177  // The pixmap is a truecolor image, where only black and white are
178  // used.
179 
180  char gsArgs[4096];
181  sprintf(gsArgs,"-q -g%dx%d -r%dx%dx -sDEVICE=ppmraw "
182  "-sOutputFile=%s.pnm -dNOPAUSE -dBATCH -- %s.ps",
183  gx,gy,(int)(scaleFactor*72),(int)(scaleFactor*72),
184  formBase.data(),formBase.data()
185  );
188  {
189  err("Problem running ghostscript %s %s. Check your installation!\n",portable_ghostScriptCommand(),gsArgs);
191  QDir::setCurrent(oldDir);
192  return;
193  }
195  f.setName(formBase+".pnm");
196  uint imageX=0,imageY=0;
197  // we read the generated image again, to obtain the pixel data.
198  if (f.open(IO_ReadOnly))
199  {
200  QTextStream t(&f);
201  QCString s;
202  if (!t.eof())
203  s=t.readLine().utf8();
204  if (s.length()<2 || s.left(2)!="P6")
205  err("ghostscript produced an illegal image format!");
206  else
207  {
208  // assume the size is after the first line that does not start with
209  // # excluding the first line of the file.
210  while (!t.eof() && (s=t.readLine().utf8()) && !s.isEmpty() && s.at(0)=='#') { }
211  sscanf(s,"%d %d",&imageX,&imageY);
212  }
213  if (imageX>0 && imageY>0)
214  {
215  //printf("Converting image...\n");
216  char *data = new char[imageX*imageY*3]; // rgb 8:8:8 format
217  uint i,x,y,ix,iy;
218  f.readBlock(data,imageX*imageY*3);
219  Image srcImage(imageX,imageY),
220  filteredImage(imageX,imageY),
221  dstImage(imageX/4,imageY/4);
222  uchar *ps=srcImage.getData();
223  // convert image to black (1) and white (0) index.
224  for (i=0;i<imageX*imageY;i++) *ps++= (data[i*3]==0 ? 1 : 0);
225  // apply a simple box filter to the image
226  static int filterMask[]={1,2,1,2,8,2,1,2,1};
227  for (y=0;y<srcImage.getHeight();y++)
228  {
229  for (x=0;x<srcImage.getWidth();x++)
230  {
231  int s=0;
232  for (iy=0;iy<2;iy++)
233  {
234  for (ix=0;ix<2;ix++)
235  {
236  s+=srcImage.getPixel(x+ix-1,y+iy-1)*filterMask[iy*3+ix];
237  }
238  }
239  filteredImage.setPixel(x,y,s);
240  }
241  }
242  // down-sample the image to 1/16th of the area using 16 gray scale
243  // colors.
244  // TODO: optimize this code.
245  for (y=0;y<dstImage.getHeight();y++)
246  {
247  for (x=0;x<dstImage.getWidth();x++)
248  {
249  int xp=x<<2;
250  int yp=y<<2;
251  int c=filteredImage.getPixel(xp+0,yp+0)+
252  filteredImage.getPixel(xp+1,yp+0)+
253  filteredImage.getPixel(xp+2,yp+0)+
254  filteredImage.getPixel(xp+3,yp+0)+
255  filteredImage.getPixel(xp+0,yp+1)+
256  filteredImage.getPixel(xp+1,yp+1)+
257  filteredImage.getPixel(xp+2,yp+1)+
258  filteredImage.getPixel(xp+3,yp+1)+
259  filteredImage.getPixel(xp+0,yp+2)+
260  filteredImage.getPixel(xp+1,yp+2)+
261  filteredImage.getPixel(xp+2,yp+2)+
262  filteredImage.getPixel(xp+3,yp+2)+
263  filteredImage.getPixel(xp+0,yp+3)+
264  filteredImage.getPixel(xp+1,yp+3)+
265  filteredImage.getPixel(xp+2,yp+3)+
266  filteredImage.getPixel(xp+3,yp+3);
267  // here we scale and clip the color value so the
268  // resulting image has a reasonable contrast
269  dstImage.setPixel(x,y,QMIN(15,(c*15)/(16*10)));
270  }
271  }
272  // save the result as a bitmap
273  QCString resultName;
274  resultName.sprintf("form_%d.png",pageNum);
275  // the option parameter 1 is used here as a temporary hack
276  // to select the right color palette!
277  dstImage.save(resultName,1);
278  delete[] data;
279  }
280  f.close();
281  }
282  // remove intermediate image files
283  thisDir.remove(formBase+".eps");
284  thisDir.remove(formBase+".pnm");
285  thisDir.remove(formBase+".ps");
286  }
287  // remove intermediate files produced by latex
288  thisDir.remove("_formulas.dvi");
289  if (!formulaError) thisDir.remove("_formulas.log"); // keep file in case of errors
290  thisDir.remove("_formulas.aux");
291  }
292  // remove the latex file itself
293  if (!formulaError) thisDir.remove("_formulas.tex");
294  // write/update the formula repository so we know what text the
295  // generated images represent (we use this next time to avoid regeneration
296  // of the images, and to avoid forcing the user to delete all images in order
297  // to let a browser refresh the images).
298  f.setName("formula.repository");
299  if (f.open(IO_WriteOnly))
300  {
301  FTextStream t(&f);
302  for (fli.toFirst();(formula=fli.current());++fli)
303  {
304  t << "\\form#" << formula->getId() << ":" << formula->getFormulaText() << endl;
305  }
306  f.close();
307  }
308  // reset the directory to the original location.
309  QDir::setCurrent(oldDir);
310 }
311 
312 
313 #ifdef FORMULA_TEST
314 int main()
315 {
316  FormulaList fl;
317  fl.append(new Formula("$x^2$"));
318  fl.append(new Formula("$y^2$"));
319  fl.append(new Formula("$\\sqrt{x_0^2+x_1^2+x_2^2}$"));
320  fl.generateBitmaps("dest");
321  return 0;
322 }
323 #endif