formula.cpp 11.3 KB
Newer Older
mueller's avatar
mueller committed
1
/******************************************************************************
dimitri's avatar
dimitri committed
2
 * 
mueller's avatar
mueller committed
3
 *
dimitri's avatar
dimitri committed
4
 * Copyright (C) 1997-2012 by Dimitri van Heesch.
mueller's avatar
mueller committed
5 6 7 8 9 10 11
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation under the terms of the GNU General Public License is hereby 
 * granted. No representations are made about the suitability of this software 
 * for any purpose. It is provided "as is" without express or implied warranty.
 * See the GNU General Public License for more details.
 *
dimitri's avatar
dimitri committed
12 13
 * Documents produced by Doxygen are derivative works derived from the
 * input used in their production; they are not affected by this license.
mueller's avatar
mueller committed
14 15 16 17 18 19
 *
 */

#include <stdlib.h>
#include <unistd.h>

mueller's avatar
mueller committed
20
#include "qtbc.h"
mueller's avatar
mueller committed
21
#include <qfile.h>
mueller's avatar
mueller committed
22
#include <qfileinfo.h>
dimitri's avatar
dimitri committed
23
#include <qtextstream.h>
mueller's avatar
mueller committed
24 25 26 27 28 29
#include <qdir.h>

#include "formula.h"
#include "image.h"
#include "util.h"
#include "message.h"
30
#include "config.h"
dimitri's avatar
dimitri committed
31
#include "portable.h"
dimitri's avatar
dimitri committed
32 33
#include "index.h"
#include "doxygen.h"
dimitri's avatar
dimitri committed
34
#include "ftextstream.h"
mueller's avatar
mueller committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

Formula::Formula(const char *text)
{
  static int count=0;
  number = count++;
  form=text;
}

Formula::~Formula()
{
}

int Formula::getId()
{
  return number;
}

void FormulaList::generateBitmaps(const char *path)
{
  int x1,y1,x2,y2;
  QDir d(path);
  // store the original directory
dimitri's avatar
dimitri committed
57
  if (!d.exists()) { err("error: Output dir %s does not exist!\n",path); exit(1); }
mueller's avatar
mueller committed
58
  QCString oldDir = convertToQCString(QDir::currentDirPath());
dimitri's avatar
dimitri committed
59
  // go to the html output directory (i.e. path)
mueller's avatar
mueller committed
60 61 62
  QDir::setCurrent(d.absPath());
  QDir thisDir;
  // generate a latex file containing one formula per page.
mueller's avatar
mueller committed
63
  QCString texName="_formulas.tex";
mueller's avatar
mueller committed
64 65 66 67 68
  QList<int> pagesToGenerate;
  pagesToGenerate.setAutoDelete(TRUE);
  FormulaListIterator fli(*this);
  Formula *formula;
  QFile f(texName);
dimitri's avatar
dimitri committed
69
  bool formulaError=FALSE;
mueller's avatar
mueller committed
70 71
  if (f.open(IO_WriteOnly))
  {
dimitri's avatar
dimitri committed
72
    FTextStream t(&f);
dimitri's avatar
dimitri committed
73
    if (Config_getBool("LATEX_BATCHMODE")) t << "\\batchmode" << endl;
mueller's avatar
mueller committed
74
    t << "\\documentclass{article}" << endl;
dimitri's avatar
dimitri committed
75
    t << "\\usepackage{epsfig}" << endl; // for those who want to include images
dimitri's avatar
dimitri committed
76
    const char *s=Config_getList("EXTRA_PACKAGES").first();
77 78 79
    while (s)
    {
      t << "\\usepackage{" << s << "}\n";
dimitri's avatar
dimitri committed
80
      s=Config_getList("EXTRA_PACKAGES").next();
81
    }
mueller's avatar
mueller committed
82 83 84 85 86
    t << "\\pagestyle{empty}" << endl; 
    t << "\\begin{document}" << endl;
    int page=0;
    for (fli.toFirst();(formula=fli.current());++fli)
    {
mueller's avatar
mueller committed
87
      QCString resultName;
dimitri's avatar
dimitri committed
88
      resultName.sprintf("form_%d.png",formula->getId());
mueller's avatar
mueller committed
89 90 91 92 93 94 95 96
      // only formulas for which no image exists are generated
      QFileInfo fi(resultName);
      if (!fi.exists())
      {
        // we force a pagebreak after each formula
        t << formula->getFormulaText() << endl << "\\pagebreak\n\n";
        pagesToGenerate.append(new int(page));
      }
dimitri's avatar
dimitri committed
97
      Doxygen::indexList.addImageFile(resultName);
mueller's avatar
mueller committed
98 99 100 101 102 103 104 105 106
      page++;
    }
    t << "\\end{document}" << endl;
    f.close();
  }
  if (pagesToGenerate.count()>0) // there are new formulas
  {
    //printf("Running latex...\n");
    //system("latex _formulas.tex </dev/null >/dev/null");
dimitri's avatar
dimitri committed
107 108
    QCString latexCmd = Config_getString("LATEX_CMD_NAME");
    if (latexCmd.isEmpty()) latexCmd="latex";
dimitri's avatar
dimitri committed
109
    portable_sysTimerStart();
dimitri's avatar
dimitri committed
110
    if (portable_system(latexCmd,"_formulas.tex")!=0)
mueller's avatar
mueller committed
111
    {
dimitri's avatar
dimitri committed
112 113
      err("Problems running latex. Check your installation or look "
          "for typos in _formulas.tex and check _formulas.log!\n");
dimitri's avatar
dimitri committed
114 115
      formulaError=TRUE;
      //return;
mueller's avatar
mueller committed
116
    }
dimitri's avatar
dimitri committed
117
    portable_sysTimerStop();
mueller's avatar
mueller committed
118 119 120 121 122 123 124
    //printf("Running dvips...\n");
    QListIterator<int> pli(pagesToGenerate);
    int *pagePtr;
    int pageIndex=1;
    for (;(pagePtr=pli.current());++pli,++pageIndex)
    {
      int pageNum=*pagePtr;
dimitri's avatar
dimitri committed
125
      msg("Generating image form_%d.png for formula\n",pageNum);
dimitri's avatar
dimitri committed
126
      char dviArgs[4096];
mueller's avatar
mueller committed
127
      QCString formBase;
mueller's avatar
mueller committed
128 129 130
      formBase.sprintf("_form%d",pageNum);
      // run dvips to convert the page with number pageIndex to an
      // encapsulated postscript.
dimitri's avatar
dimitri committed
131
      sprintf(dviArgs,"-q -D 600 -E -n 1 -p %d -o %s.eps _formulas.dvi",
mueller's avatar
mueller committed
132
          pageIndex,formBase.data());
dimitri's avatar
dimitri committed
133
      portable_sysTimerStart();
dimitri's avatar
dimitri committed
134
      if (portable_system("dvips",dviArgs)!=0)
mueller's avatar
mueller committed
135 136
      {
        err("Problems running dvips. Check your installation!\n");
dimitri's avatar
dimitri committed
137
        portable_sysTimerStop();
mueller's avatar
mueller committed
138 139
        return;
      }
dimitri's avatar
dimitri committed
140
      portable_sysTimerStop();
mueller's avatar
mueller committed
141 142 143 144
      // now we read the generated postscript file to extract the bounding box
      QFileInfo fi(formBase+".eps");
      if (fi.exists())
      {
mueller's avatar
mueller committed
145
        QCString eps = fileToString(formBase+".eps");
mueller's avatar
mueller committed
146 147 148 149 150 151 152
        int i=eps.find("%%BoundingBox:");
        if (i!=-1)
        {
          sscanf(eps.data()+i,"%%%%BoundingBox:%d %d %d %d",&x1,&y1,&x2,&y2);
        }
        else
        {
dimitri's avatar
dimitri committed
153
          err("error: Couldn't extract bounding box!\n");
mueller's avatar
mueller committed
154 155 156 157 158 159 160
        }
      } 
      // next we generate a postscript file which contains the eps
      // and displays it in the right colors and the right bounding box
      f.setName(formBase+".ps");
      if (f.open(IO_WriteOnly))
      {
dimitri's avatar
dimitri committed
161
        FTextStream t(&f);
mueller's avatar
mueller committed
162 163 164
        t << "1 1 1 setrgbcolor" << endl;  // anti-alias to white background
        t << "newpath" << endl;
        t << "-1 -1 moveto" << endl;
mueller's avatar
mueller committed
165 166 167
        t << (x2-x1+2) << " -1 lineto" << endl;
        t << (x2-x1+2) << " " << (y2-y1+2) << " lineto" << endl;
        t << "-1 " << (y2-y1+2) << " lineto" <<endl;
mueller's avatar
mueller committed
168 169 170 171 172 173 174 175 176
        t << "closepath" << endl;
        t << "fill" << endl;
        t << -x1 << " " << -y1 << " translate" << endl;
        t << "0 0 0 setrgbcolor" << endl;
        t << "(" << formBase << ".eps) run" << endl;
        f.close();
      }
      // scale the image so that it is four times larger than needed.
      // and the sizes are a multiple of four.
dimitri's avatar
dimitri committed
177 178 179 180
      double scaleFactor = 16.0/3.0; 
      int zoomFactor = Config_getInt("FORMULA_FONTSIZE");
      if (zoomFactor<8 || zoomFactor>50) zoomFactor=10;
      scaleFactor *= zoomFactor/10.0;
dimitri's avatar
dimitri committed
181 182
      int gx = (((int)((x2-x1)*scaleFactor))+3)&~1;
      int gy = (((int)((y2-y1)*scaleFactor))+3)&~1;
mueller's avatar
mueller committed
183 184 185
      // Then we run ghostscript to convert the postscript to a pixmap
      // The pixmap is a truecolor image, where only black and white are
      // used.  
dimitri's avatar
dimitri committed
186

dimitri's avatar
dimitri committed
187 188
      char gsArgs[4096];
      sprintf(gsArgs,"-q -g%dx%d -r%dx%dx -sDEVICE=ppmraw "
dimitri's avatar
dimitri committed
189
                    "-sOutputFile=%s.pnm -dNOPAUSE -dBATCH -- %s.ps",
mueller's avatar
mueller committed
190 191 192
                    gx,gy,(int)(scaleFactor*72),(int)(scaleFactor*72),
                    formBase.data(),formBase.data()
             );
dimitri's avatar
dimitri committed
193
      portable_sysTimerStart();
dimitri's avatar
dimitri committed
194
      if (portable_system(portable_ghostScriptCommand(),gsArgs)!=0)
mueller's avatar
mueller committed
195
      {
dimitri's avatar
dimitri committed
196
        err("Problem running ghostscript %s %s. Check your installation!\n",portable_ghostScriptCommand(),gsArgs);
dimitri's avatar
dimitri committed
197
        portable_sysTimerStop();
mueller's avatar
mueller committed
198 199
        return;
      }
dimitri's avatar
dimitri committed
200
      portable_sysTimerStop();
mueller's avatar
mueller committed
201 202 203 204 205 206
      f.setName(formBase+".pnm");
      uint imageX=0,imageY=0;
      // we read the generated image again, to obtain the pixel data.
      if (f.open(IO_ReadOnly))
      {
        QTextStream t(&f);
mueller's avatar
mueller committed
207
        QCString s;
mueller's avatar
mueller committed
208
        if (!t.eof())
dimitri's avatar
dimitri committed
209
          s=t.readLine().utf8();
mueller's avatar
mueller committed
210
        if (s.length()<2 || s.left(2)!="P6")
dimitri's avatar
dimitri committed
211
          err("error: ghostscript produced an illegal image format!");
mueller's avatar
mueller committed
212 213 214 215
        else
        {
          // assume the size if after the first line that does not start with
          // # excluding the first line of the file.
dimitri's avatar
dimitri committed
216
          while (!t.eof() && (s=t.readLine().utf8()) && !s.isEmpty() && s.at(0)=='#') { }
mueller's avatar
mueller committed
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
          sscanf(s,"%d %d",&imageX,&imageY);
        }
        if (imageX>0 && imageY>0)
        {
          //printf("Converting image...\n");
          char *data = new char[imageX*imageY*3]; // rgb 8:8:8 format
          uint i,x,y,ix,iy;
          f.readBlock(data,imageX*imageY*3);
          Image srcImage(imageX,imageY),
                filteredImage(imageX,imageY),
                dstImage(imageX/4,imageY/4);
          uchar *ps=srcImage.getData();
          // convert image to black (1) and white (0) index.
          for (i=0;i<imageX*imageY;i++) *ps++= (data[i*3]==0 ? 1 : 0);
          // apply a simple box filter to the image 
          static int filterMask[]={1,2,1,2,8,2,1,2,1};
          for (y=0;y<srcImage.getHeight();y++)
          {
            for (x=0;x<srcImage.getWidth();x++)
            {
              int s=0;
              for (iy=0;iy<2;iy++)
              {
                for (ix=0;ix<2;ix++)
                {
                  s+=srcImage.getPixel(x+ix-1,y+iy-1)*filterMask[iy*3+ix];
                }
              }
              filteredImage.setPixel(x,y,s);
            }
          }
          // down-sample the image to 1/16th of the area using 16 gray scale
          // colors.
dimitri's avatar
dimitri committed
250
          // TODO: optimize this code.
mueller's avatar
mueller committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
          for (y=0;y<dstImage.getHeight();y++)
          {
            for (x=0;x<dstImage.getWidth();x++)
            {
              int xp=x<<2;
              int yp=y<<2;
              int c=filteredImage.getPixel(xp+0,yp+0)+
                    filteredImage.getPixel(xp+1,yp+0)+
                    filteredImage.getPixel(xp+2,yp+0)+
                    filteredImage.getPixel(xp+3,yp+0)+
                    filteredImage.getPixel(xp+0,yp+1)+
                    filteredImage.getPixel(xp+1,yp+1)+
                    filteredImage.getPixel(xp+2,yp+1)+
                    filteredImage.getPixel(xp+3,yp+1)+
                    filteredImage.getPixel(xp+0,yp+2)+
                    filteredImage.getPixel(xp+1,yp+2)+
                    filteredImage.getPixel(xp+2,yp+2)+
                    filteredImage.getPixel(xp+3,yp+2)+
                    filteredImage.getPixel(xp+0,yp+3)+
                    filteredImage.getPixel(xp+1,yp+3)+
                    filteredImage.getPixel(xp+2,yp+3)+
                    filteredImage.getPixel(xp+3,yp+3);
              // here we scale and clip the color value so the
              // resulting image has a reasonable contrast
              dstImage.setPixel(x,y,QMIN(15,(c*15)/(16*10)));
            }
          }
dimitri's avatar
dimitri committed
278
          // save the result as a bitmap
mueller's avatar
mueller committed
279
          QCString resultName;
dimitri's avatar
dimitri committed
280
          resultName.sprintf("form_%d.png",pageNum);
mueller's avatar
mueller committed
281 282 283 284 285 286 287 288 289 290 291 292 293 294
          // the option parameter 1 is used here as a temporary hack
          // to select the right color palette! 
          dstImage.save(resultName,1);
          delete[] data;
        }
        f.close();
      } 
      // remove intermediate image files
      thisDir.remove(formBase+".eps");
      thisDir.remove(formBase+".pnm");
      thisDir.remove(formBase+".ps");
    }
    // remove intermediate files produced by latex
    thisDir.remove("_formulas.dvi");
dimitri's avatar
dimitri committed
295
    if (!formulaError) thisDir.remove("_formulas.log"); // keep file in case of errors
mueller's avatar
mueller committed
296 297 298
    thisDir.remove("_formulas.aux");
  }
  // remove the latex file itself
dimitri's avatar
dimitri committed
299
  if (!formulaError) thisDir.remove("_formulas.tex");
mueller's avatar
mueller committed
300
  // write/update the formula repository so we know what text the 
dimitri's avatar
dimitri committed
301 302
  // generated images represent (we use this next time to avoid regeneration
  // of the images, and to avoid forcing the user to delete all images in order
mueller's avatar
mueller committed
303 304 305 306
  // to let a browser refresh the images).
  f.setName("formula.repository");
  if (f.open(IO_WriteOnly))
  {
dimitri's avatar
dimitri committed
307
    FTextStream t(&f);
mueller's avatar
mueller committed
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
    for (fli.toFirst();(formula=fli.current());++fli)
    {
      t << "\\form#" << formula->getId() << ":" << formula->getFormulaText() << endl;
    }
    f.close();
  }
  // reset the directory to the original location.
  QDir::setCurrent(oldDir);
}


#ifdef FORMULA_TEST
int main()
{
  FormulaList fl;
  fl.append(new Formula("$x^2$"));
  fl.append(new Formula("$y^2$"));
  fl.append(new Formula("$\\sqrt{x_0^2+x_1^2+x_2^2}$"));
  fl.generateBitmaps("dest");
  return 0;
}
#endif