Imported Upstream version 1.8.15
[platform/upstream/doxygen.git] / src / formula.cpp
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
40 Formula::~Formula()
41 {
42 }
43
44 int Formula::getId()
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{ifthen}" << endl;
73     t << "\\usepackage{epsfig}" << endl; // for those who want to include images
74     writeExtraLatexPackages(t);
75     writeLatexSpecialFormulaChars(t);
76     t << "\\pagestyle{empty}" << endl; 
77     t << "\\begin{document}" << endl;
78     int page=0;
79     for (fli.toFirst();(formula=fli.current());++fli)
80     {
81       QCString resultName;
82       resultName.sprintf("form_%d.png",formula->getId());
83       // only formulas for which no image exists are generated
84       QFileInfo fi(resultName);
85       if (!fi.exists())
86       {
87         // we force a pagebreak after each formula
88         t << formula->getFormulaText() << endl << "\\pagebreak\n\n";
89         pagesToGenerate.append(new int(page));
90       }
91       Doxygen::indexList->addImageFile(resultName);
92       page++;
93     }
94     t << "\\end{document}" << endl;
95     f.close();
96   }
97   if (pagesToGenerate.count()>0) // there are new formulas
98   {
99     //printf("Running latex...\n");
100     //system("latex _formulas.tex </dev/null >/dev/null");
101     QCString latexCmd = "latex";
102     portable_sysTimerStart();
103     if (portable_system(latexCmd,"_formulas.tex")!=0)
104     {
105       err("Problems running latex. Check your installation or look "
106           "for typos in _formulas.tex and check _formulas.log!\n");
107       formulaError=TRUE;
108       //return;
109     }
110     portable_sysTimerStop();
111     //printf("Running dvips...\n");
112     QListIterator<int> pli(pagesToGenerate);
113     int *pagePtr;
114     int pageIndex=1;
115     for (;(pagePtr=pli.current());++pli,++pageIndex)
116     {
117       int pageNum=*pagePtr;
118       msg("Generating image form_%d.png for formula\n",pageNum);
119       char dviArgs[4096];
120       QCString formBase;
121       formBase.sprintf("_form%d",pageNum);
122       // run dvips to convert the page with number pageIndex to an
123       // encapsulated postscript.
124       sprintf(dviArgs,"-q -D 600 -E -n 1 -p %d -o %s.eps _formulas.dvi",
125           pageIndex,formBase.data());
126       portable_sysTimerStart();
127       if (portable_system("dvips",dviArgs)!=0)
128       {
129         err("Problems running dvips. Check your installation!\n");
130         portable_sysTimerStop();
131         QDir::setCurrent(oldDir);
132         return;
133       }
134       portable_sysTimerStop();
135       // now we read the generated postscript file to extract the bounding box
136       QFileInfo fi(formBase+".eps");
137       if (fi.exists())
138       {
139         QCString eps = fileToString(formBase+".eps");
140         int i=eps.find("%%BoundingBox:");
141         if (i!=-1)
142         {
143           sscanf(eps.data()+i,"%%%%BoundingBox:%d %d %d %d",&x1,&y1,&x2,&y2);
144         }
145         else
146         {
147           err("Couldn't extract bounding box!\n");
148         }
149       } 
150       // next we generate a postscript file which contains the eps
151       // and displays it in the right colors and the right bounding box
152       f.setName(formBase+".ps");
153       if (f.open(IO_WriteOnly))
154       {
155         FTextStream t(&f);
156         t << "1 1 1 setrgbcolor" << endl;  // anti-alias to white background
157         t << "newpath" << endl;
158         t << "-1 -1 moveto" << endl;
159         t << (x2-x1+2) << " -1 lineto" << endl;
160         t << (x2-x1+2) << " " << (y2-y1+2) << " lineto" << endl;
161         t << "-1 " << (y2-y1+2) << " lineto" <<endl;
162         t << "closepath" << endl;
163         t << "fill" << endl;
164         t << -x1 << " " << -y1 << " translate" << endl;
165         t << "0 0 0 setrgbcolor" << endl;
166         t << "(" << formBase << ".eps) run" << endl;
167         f.close();
168       }
169       // scale the image so that it is four times larger than needed.
170       // and the sizes are a multiple of four.
171       double scaleFactor = 16.0/3.0; 
172       int zoomFactor = Config_getInt(FORMULA_FONTSIZE);
173       if (zoomFactor<8 || zoomFactor>50) zoomFactor=10;
174       scaleFactor *= zoomFactor/10.0;
175       int gx = (((int)((x2-x1)*scaleFactor))+3)&~1;
176       int gy = (((int)((y2-y1)*scaleFactor))+3)&~1;
177       // Then we run ghostscript to convert the postscript to a pixmap
178       // The pixmap is a truecolor image, where only black and white are
179       // used.  
180
181       char gsArgs[4096];
182       sprintf(gsArgs,"-q -g%dx%d -r%dx%dx -sDEVICE=ppmraw "
183                     "-sOutputFile=%s.pnm -dNOPAUSE -dBATCH -- %s.ps",
184                     gx,gy,(int)(scaleFactor*72),(int)(scaleFactor*72),
185                     formBase.data(),formBase.data()
186              );
187       portable_sysTimerStart();
188       if (portable_system(portable_ghostScriptCommand(),gsArgs)!=0)
189       {
190         err("Problem running ghostscript %s %s. Check your installation!\n",portable_ghostScriptCommand(),gsArgs);
191         portable_sysTimerStop();
192         QDir::setCurrent(oldDir);
193         return;
194       }
195       portable_sysTimerStop();
196       f.setName(formBase+".pnm");
197       uint imageX=0,imageY=0;
198       // we read the generated image again, to obtain the pixel data.
199       if (f.open(IO_ReadOnly))
200       {
201         QTextStream t(&f);
202         QCString s;
203         if (!t.eof())
204           s=t.readLine().utf8();
205         if (s.length()<2 || s.left(2)!="P6")
206           err("ghostscript produced an illegal image format!");
207         else
208         {
209           // assume the size is after the first line that does not start with
210           // # excluding the first line of the file.
211           while (!t.eof() && (s=t.readLine().utf8()) && !s.isEmpty() && s.at(0)=='#') { }
212           sscanf(s,"%d %d",&imageX,&imageY);
213         }
214         if (imageX>0 && imageY>0)
215         {
216           //printf("Converting image...\n");
217           char *data = new char[imageX*imageY*3]; // rgb 8:8:8 format
218           uint i,x,y,ix,iy;
219           f.readBlock(data,imageX*imageY*3);
220           Image srcImage(imageX,imageY),
221                 filteredImage(imageX,imageY),
222                 dstImage(imageX/4,imageY/4);
223           uchar *ps=srcImage.getData();
224           // convert image to black (1) and white (0) index.
225           for (i=0;i<imageX*imageY;i++) *ps++= (data[i*3]==0 ? 1 : 0);
226           // apply a simple box filter to the image 
227           static int filterMask[]={1,2,1,2,8,2,1,2,1};
228           for (y=0;y<srcImage.getHeight();y++)
229           {
230             for (x=0;x<srcImage.getWidth();x++)
231             {
232               int s=0;
233               for (iy=0;iy<2;iy++)
234               {
235                 for (ix=0;ix<2;ix++)
236                 {
237                   s+=srcImage.getPixel(x+ix-1,y+iy-1)*filterMask[iy*3+ix];
238                 }
239               }
240               filteredImage.setPixel(x,y,s);
241             }
242           }
243           // down-sample the image to 1/16th of the area using 16 gray scale
244           // colors.
245           // TODO: optimize this code.
246           for (y=0;y<dstImage.getHeight();y++)
247           {
248             for (x=0;x<dstImage.getWidth();x++)
249             {
250               int xp=x<<2;
251               int yp=y<<2;
252               int c=filteredImage.getPixel(xp+0,yp+0)+
253                     filteredImage.getPixel(xp+1,yp+0)+
254                     filteredImage.getPixel(xp+2,yp+0)+
255                     filteredImage.getPixel(xp+3,yp+0)+
256                     filteredImage.getPixel(xp+0,yp+1)+
257                     filteredImage.getPixel(xp+1,yp+1)+
258                     filteredImage.getPixel(xp+2,yp+1)+
259                     filteredImage.getPixel(xp+3,yp+1)+
260                     filteredImage.getPixel(xp+0,yp+2)+
261                     filteredImage.getPixel(xp+1,yp+2)+
262                     filteredImage.getPixel(xp+2,yp+2)+
263                     filteredImage.getPixel(xp+3,yp+2)+
264                     filteredImage.getPixel(xp+0,yp+3)+
265                     filteredImage.getPixel(xp+1,yp+3)+
266                     filteredImage.getPixel(xp+2,yp+3)+
267                     filteredImage.getPixel(xp+3,yp+3);
268               // here we scale and clip the color value so the
269               // resulting image has a reasonable contrast
270               dstImage.setPixel(x,y,QMIN(15,(c*15)/(16*10)));
271             }
272           }
273           // save the result as a bitmap
274           QCString resultName;
275           resultName.sprintf("form_%d.png",pageNum);
276           // the option parameter 1 is used here as a temporary hack
277           // to select the right color palette! 
278           dstImage.save(resultName,1);
279           delete[] data;
280         }
281         f.close();
282       } 
283       // remove intermediate image files
284       thisDir.remove(formBase+".eps");
285       thisDir.remove(formBase+".pnm");
286       thisDir.remove(formBase+".ps");
287     }
288     // remove intermediate files produced by latex
289     thisDir.remove("_formulas.dvi");
290     if (!formulaError) thisDir.remove("_formulas.log"); // keep file in case of errors
291     thisDir.remove("_formulas.aux");
292   }
293   // remove the latex file itself
294   if (!formulaError) thisDir.remove("_formulas.tex");
295   // write/update the formula repository so we know what text the 
296   // generated images represent (we use this next time to avoid regeneration
297   // of the images, and to avoid forcing the user to delete all images in order
298   // to let a browser refresh the images).
299   f.setName("formula.repository");
300   if (f.open(IO_WriteOnly))
301   {
302     FTextStream t(&f);
303     for (fli.toFirst();(formula=fli.current());++fli)
304     {
305       t << "\\form#" << formula->getId() << ":" << formula->getFormulaText() << endl;
306     }
307     f.close();
308   }
309   // reset the directory to the original location.
310   QDir::setCurrent(oldDir);
311 }
312
313
314 #ifdef FORMULA_TEST
315 int main()
316 {
317   FormulaList fl;
318   fl.append(new Formula("$x^2$"));
319   fl.append(new Formula("$y^2$"));
320   fl.append(new Formula("$\\sqrt{x_0^2+x_1^2+x_2^2}$"));
321   fl.generateBitmaps("dest");
322   return 0;
323 }
324 #endif