00001 /****************************************************************************** 00002 i 00003 * $Id: formula.cpp,v 1.16 2001/03/19 19:27:40 root Exp $ 00004 * 00005 * Copyright (C) 1997-2008 by Dimitri van Heesch. 00006 * 00007 * Permission to use, copy, modify, and distribute this software and its 00008 * documentation under the terms of the GNU General Public License is hereby 00009 * granted. No representations are made about the suitability of this software 00010 * for any purpose. It is provided "as is" without express or implied warranty. 00011 * See the GNU General Public License for more details. 00012 * 00013 * Documents produced by Doxygen are derivative works derived from the 00014 * input used in their production; they are not affected by this license. 00015 * 00016 */ 00017 00018 #include <stdlib.h> 00019 #include <unistd.h> 00020 00021 #include "qtbc.h" 00022 #include <qfile.h> 00023 #include <qtextstream.h> 00024 #include <qfileinfo.h> 00025 #include <qdir.h> 00026 00027 #include "formula.h" 00028 #include "image.h" 00029 #include "util.h" 00030 #include "message.h" 00031 #include "config.h" 00032 #include "portable.h" 00033 00034 Formula::Formula(const char *text) 00035 { 00036 static int count=0; 00037 number = count++; 00038 form=text; 00039 } 00040 00041 Formula::~Formula() 00042 { 00043 } 00044 00045 int Formula::getId() 00046 { 00047 return number; 00048 } 00049 00050 void FormulaList::generateBitmaps(const char *path) 00051 { 00052 int x1,y1,x2,y2; 00053 QDir d(path); 00054 // store the original directory 00055 if (!d.exists()) { err("Error: Output dir %s does not exist!\n",path); exit(1); } 00056 QCString oldDir = convertToQCString(QDir::currentDirPath()); 00057 // go to the html output directory (i.e. path) 00058 QDir::setCurrent(d.absPath()); 00059 QDir thisDir; 00060 // generate a latex file containing one formula per page. 00061 QCString texName="_formulas.tex"; 00062 QList<int> pagesToGenerate; 00063 pagesToGenerate.setAutoDelete(TRUE); 00064 FormulaListIterator fli(*this); 00065 Formula *formula; 00066 QFile f(texName); 00067 bool formulaError=FALSE; 00068 if (f.open(IO_WriteOnly)) 00069 { 00070 QTextStream t(&f); 00071 if (Config_getBool("LATEX_BATCHMODE")) t << "\\batchmode" << endl; 00072 t << "\\documentclass{article}" << endl; 00073 t << "\\usepackage{epsfig}" << endl; // for those who want to include images 00074 const char *s=Config_getList("EXTRA_PACKAGES").first(); 00075 while (s) 00076 { 00077 t << "\\usepackage{" << s << "}\n"; 00078 s=Config_getList("EXTRA_PACKAGES").next(); 00079 } 00080 t << "\\pagestyle{empty}" << endl; 00081 t << "\\begin{document}" << endl; 00082 int page=0; 00083 for (fli.toFirst();(formula=fli.current());++fli) 00084 { 00085 QCString resultName; 00086 resultName.sprintf("form_%d.png",formula->getId()); 00087 // only formulas for which no image exists are generated 00088 QFileInfo fi(resultName); 00089 if (!fi.exists()) 00090 { 00091 // we force a pagebreak after each formula 00092 t << formula->getFormulaText() << endl << "\\pagebreak\n\n"; 00093 pagesToGenerate.append(new int(page)); 00094 } 00095 page++; 00096 } 00097 t << "\\end{document}" << endl; 00098 f.close(); 00099 } 00100 if (pagesToGenerate.count()>0) // there are new formulas 00101 { 00102 //printf("Running latex...\n"); 00103 //system("latex _formulas.tex </dev/null >/dev/null"); 00104 QCString latexCmd = Config_getString("LATEX_CMD_NAME"); 00105 if (latexCmd.isEmpty()) latexCmd="latex"; 00106 if (portable_system(latexCmd,"_formulas.tex")!=0) 00107 { 00108 err("Problems running latex. Check your installation or look " 00109 "for typos in _formulas.tex and check _formulas.log!\n"); 00110 formulaError=TRUE; 00111 //return; 00112 } 00113 //printf("Running dvips...\n"); 00114 QListIterator<int> pli(pagesToGenerate); 00115 int *pagePtr; 00116 int pageIndex=1; 00117 for (;(pagePtr=pli.current());++pli,++pageIndex) 00118 { 00119 int pageNum=*pagePtr; 00120 msg("Generating image form_%d.png for formula\n",pageNum); 00121 char dviArgs[4096]; 00122 QCString formBase; 00123 formBase.sprintf("_form%d",pageNum); 00124 // run dvips to convert the page with number pageIndex to an 00125 // encapsulated postscript. 00126 sprintf(dviArgs,"-q -D 600 -E -n 1 -p %d -o %s.eps _formulas.dvi", 00127 pageIndex,formBase.data()); 00128 if (portable_system("dvips",dviArgs)!=0) 00129 { 00130 err("Problems running dvips. Check your installation!\n"); 00131 return; 00132 } 00133 // now we read the generated postscript file to extract the bounding box 00134 QFileInfo fi(formBase+".eps"); 00135 if (fi.exists()) 00136 { 00137 QCString eps = fileToString(formBase+".eps"); 00138 int i=eps.find("%%BoundingBox:"); 00139 if (i!=-1) 00140 { 00141 sscanf(eps.data()+i,"%%%%BoundingBox:%d %d %d %d",&x1,&y1,&x2,&y2); 00142 } 00143 else 00144 { 00145 err("Error: Couldn't extract bounding box!\n"); 00146 } 00147 } 00148 // next we generate a postscript file which contains the eps 00149 // and displays it in the right colors and the right bounding box 00150 f.setName(formBase+".ps"); 00151 if (f.open(IO_WriteOnly)) 00152 { 00153 QTextStream t(&f); 00154 t << "1 1 1 setrgbcolor" << endl; // anti-alias to white background 00155 t << "newpath" << endl; 00156 t << "-1 -1 moveto" << endl; 00157 t << (x2-x1+2) << " -1 lineto" << endl; 00158 t << (x2-x1+2) << " " << (y2-y1+2) << " lineto" << endl; 00159 t << "-1 " << (y2-y1+2) << " lineto" <<endl; 00160 t << "closepath" << endl; 00161 t << "fill" << endl; 00162 t << -x1 << " " << -y1 << " translate" << endl; 00163 t << "0 0 0 setrgbcolor" << endl; 00164 t << "(" << formBase << ".eps) run" << endl; 00165 f.close(); 00166 } 00167 // scale the image so that it is four times larger than needed. 00168 // and the sizes are a multiple of four. 00169 const double scaleFactor = 16.0/3.0; 00170 int gx = (((int)((x2-x1)*scaleFactor))+3)&~2; 00171 int gy = (((int)((y2-y1)*scaleFactor))+3)&~2; 00172 // Then we run ghostscript to convert the postscript to a pixmap 00173 // The pixmap is a truecolor image, where only black and white are 00174 // used. 00175 00176 char gsArgs[4096]; 00177 sprintf(gsArgs,"-q -g%dx%d -r%dx%dx -sDEVICE=ppmraw " 00178 "-sOutputFile=%s.pnm -dNOPAUSE -dBATCH -- %s.ps", 00179 gx,gy,(int)(scaleFactor*72),(int)(scaleFactor*72), 00180 formBase.data(),formBase.data() 00181 ); 00182 if (portable_system(portable_ghostScriptCommand(),gsArgs)!=0) 00183 { 00184 err("Problem running ghostscript %s %s. Check your installation!\n",portable_ghostScriptCommand(),gsArgs); 00185 return; 00186 } 00187 f.setName(formBase+".pnm"); 00188 uint imageX=0,imageY=0; 00189 // we read the generated image again, to obtain the pixel data. 00190 if (f.open(IO_ReadOnly)) 00191 { 00192 QTextStream t(&f); 00193 QCString s; 00194 if (!t.eof()) 00195 s=t.readLine(); 00196 if (s.length()<2 || s.left(2)!="P6") 00197 err("Error: ghostscript produced an illegal image format!"); 00198 else 00199 { 00200 // assume the size if after the first line that does not start with 00201 // # excluding the first line of the file. 00202 while (!t.eof() && (s=t.readLine()) && !s.isEmpty() && s.at(0)=='#'); 00203 sscanf(s,"%d %d",&imageX,&imageY); 00204 } 00205 if (imageX>0 && imageY>0) 00206 { 00207 //printf("Converting image...\n"); 00208 char *data = new char[imageX*imageY*3]; // rgb 8:8:8 format 00209 uint i,x,y,ix,iy; 00210 f.readBlock(data,imageX*imageY*3); 00211 Image srcImage(imageX,imageY), 00212 filteredImage(imageX,imageY), 00213 dstImage(imageX/4,imageY/4); 00214 uchar *ps=srcImage.getData(); 00215 // convert image to black (1) and white (0) index. 00216 for (i=0;i<imageX*imageY;i++) *ps++= (data[i*3]==0 ? 1 : 0); 00217 // apply a simple box filter to the image 00218 static int filterMask[]={1,2,1,2,8,2,1,2,1}; 00219 for (y=0;y<srcImage.getHeight();y++) 00220 { 00221 for (x=0;x<srcImage.getWidth();x++) 00222 { 00223 int s=0; 00224 for (iy=0;iy<2;iy++) 00225 { 00226 for (ix=0;ix<2;ix++) 00227 { 00228 s+=srcImage.getPixel(x+ix-1,y+iy-1)*filterMask[iy*3+ix]; 00229 } 00230 } 00231 filteredImage.setPixel(x,y,s); 00232 } 00233 } 00234 // down-sample the image to 1/16th of the area using 16 gray scale 00235 // colors. 00236 // TODO: optimize this code. 00237 for (y=0;y<dstImage.getHeight();y++) 00238 { 00239 for (x=0;x<dstImage.getWidth();x++) 00240 { 00241 int xp=x<<2; 00242 int yp=y<<2; 00243 int c=filteredImage.getPixel(xp+0,yp+0)+ 00244 filteredImage.getPixel(xp+1,yp+0)+ 00245 filteredImage.getPixel(xp+2,yp+0)+ 00246 filteredImage.getPixel(xp+3,yp+0)+ 00247 filteredImage.getPixel(xp+0,yp+1)+ 00248 filteredImage.getPixel(xp+1,yp+1)+ 00249 filteredImage.getPixel(xp+2,yp+1)+ 00250 filteredImage.getPixel(xp+3,yp+1)+ 00251 filteredImage.getPixel(xp+0,yp+2)+ 00252 filteredImage.getPixel(xp+1,yp+2)+ 00253 filteredImage.getPixel(xp+2,yp+2)+ 00254 filteredImage.getPixel(xp+3,yp+2)+ 00255 filteredImage.getPixel(xp+0,yp+3)+ 00256 filteredImage.getPixel(xp+1,yp+3)+ 00257 filteredImage.getPixel(xp+2,yp+3)+ 00258 filteredImage.getPixel(xp+3,yp+3); 00259 // here we scale and clip the color value so the 00260 // resulting image has a reasonable contrast 00261 dstImage.setPixel(x,y,QMIN(15,(c*15)/(16*10))); 00262 } 00263 } 00264 // save the result as a png 00265 QCString resultName; 00266 resultName.sprintf("form_%d.png",pageNum); 00267 // the option parameter 1 is used here as a temporary hack 00268 // to select the right color palette! 00269 dstImage.save(resultName,1); 00270 delete[] data; 00271 } 00272 f.close(); 00273 } 00274 // remove intermediate image files 00275 thisDir.remove(formBase+".eps"); 00276 thisDir.remove(formBase+".pnm"); 00277 thisDir.remove(formBase+".ps"); 00278 } 00279 // remove intermediate files produced by latex 00280 thisDir.remove("_formulas.dvi"); 00281 if (!formulaError) thisDir.remove("_formulas.log"); // keep file in case of errors 00282 thisDir.remove("_formulas.aux"); 00283 } 00284 // remove the latex file itself 00285 if (!formulaError) thisDir.remove("_formulas.tex"); 00286 // write/update the formula repository so we know what text the 00287 // generated pngs represent (we use this next time to avoid regeneration 00288 // of the pngs, and to avoid forcing the user to delete all pngs in order 00289 // to let a browser refresh the images). 00290 f.setName("formula.repository"); 00291 if (f.open(IO_WriteOnly)) 00292 { 00293 QTextStream t(&f); 00294 for (fli.toFirst();(formula=fli.current());++fli) 00295 { 00296 t << "\\form#" << formula->getId() << ":" << formula->getFormulaText() << endl; 00297 } 00298 f.close(); 00299 } 00300 // reset the directory to the original location. 00301 QDir::setCurrent(oldDir); 00302 } 00303 00304 00305 #ifdef FORMULA_TEST 00306 int main() 00307 { 00308 FormulaList fl; 00309 fl.append(new Formula("$x^2$")); 00310 fl.append(new Formula("$y^2$")); 00311 fl.append(new Formula("$\\sqrt{x_0^2+x_1^2+x_2^2}$")); 00312 fl.generateBitmaps("dest"); 00313 return 0; 00314 } 00315 #endif