Blame src/formula.cpp

Packit 1c1d7e
/******************************************************************************
Packit 1c1d7e
 * 
Packit 1c1d7e
 *
Packit 1c1d7e
 * Copyright (C) 1997-2015 by Dimitri van Heesch.
Packit 1c1d7e
 *
Packit 1c1d7e
 * Permission to use, copy, modify, and distribute this software and its
Packit 1c1d7e
 * documentation under the terms of the GNU General Public License is hereby 
Packit 1c1d7e
 * granted. No representations are made about the suitability of this software 
Packit 1c1d7e
 * for any purpose. It is provided "as is" without express or implied warranty.
Packit 1c1d7e
 * See the GNU General Public License for more details.
Packit 1c1d7e
 *
Packit 1c1d7e
 * Documents produced by Doxygen are derivative works derived from the
Packit 1c1d7e
 * input used in their production; they are not affected by this license.
Packit 1c1d7e
 *
Packit 1c1d7e
 */
Packit 1c1d7e
Packit 1c1d7e
#include <stdlib.h>
Packit 1c1d7e
#include <qfile.h>
Packit 1c1d7e
#include <qfileinfo.h>
Packit 1c1d7e
#include <qtextstream.h>
Packit 1c1d7e
#include <qdir.h>
Packit 1c1d7e
Packit 1c1d7e
#include "formula.h"
Packit 1c1d7e
#include "image.h"
Packit 1c1d7e
#include "util.h"
Packit 1c1d7e
#include "message.h"
Packit 1c1d7e
#include "config.h"
Packit 1c1d7e
#include "portable.h"
Packit 1c1d7e
#include "index.h"
Packit 1c1d7e
#include "doxygen.h"
Packit 1c1d7e
#include "ftextstream.h"
Packit 1c1d7e
Packit 1c1d7e
Formula::Formula(const char *text)
Packit 1c1d7e
{
Packit 1c1d7e
  static int count=0;
Packit 1c1d7e
  number = count++;
Packit 1c1d7e
  form=text;
Packit 1c1d7e
}
Packit 1c1d7e
Packit 1c1d7e
Formula::~Formula()
Packit 1c1d7e
{
Packit 1c1d7e
}
Packit 1c1d7e
Packit 1c1d7e
int Formula::getId()
Packit 1c1d7e
{
Packit 1c1d7e
  return number;
Packit 1c1d7e
}
Packit 1c1d7e
Packit 1c1d7e
void FormulaList::generateBitmaps(const char *path)
Packit 1c1d7e
{
Packit 1c1d7e
  int x1,y1,x2,y2;
Packit 1c1d7e
  QDir d(path);
Packit 1c1d7e
  // store the original directory
Packit 1c1d7e
  if (!d.exists()) { err("Output dir %s does not exist!\n",path); exit(1); }
Packit 1c1d7e
  QCString oldDir = QDir::currentDirPath().utf8();
Packit 1c1d7e
  // go to the html output directory (i.e. path)
Packit 1c1d7e
  QDir::setCurrent(d.absPath());
Packit 1c1d7e
  QDir thisDir;
Packit 1c1d7e
  // generate a latex file containing one formula per page.
Packit 1c1d7e
  QCString texName="_formulas.tex";
Packit 1c1d7e
  QList<int> pagesToGenerate;
Packit 1c1d7e
  pagesToGenerate.setAutoDelete(TRUE);
Packit 1c1d7e
  FormulaListIterator fli(*this);
Packit 1c1d7e
  Formula *formula;
Packit 1c1d7e
  QFile f(texName);
Packit 1c1d7e
  bool formulaError=FALSE;
Packit 1c1d7e
  if (f.open(IO_WriteOnly))
Packit 1c1d7e
  {
Packit 1c1d7e
    FTextStream t(&f);
Packit 1c1d7e
    if (Config_getBool(LATEX_BATCHMODE)) t << "\\batchmode" << endl;
Packit 1c1d7e
    t << "\\documentclass{article}" << endl;
Packit 1c1d7e
    t << "\\usepackage{epsfig}" << endl; // for those who want to include images
Packit 1c1d7e
    writeExtraLatexPackages(t);
Packit 1c1d7e
    t << "\\pagestyle{empty}" << endl; 
Packit 1c1d7e
    t << "\\begin{document}" << endl;
Packit 1c1d7e
    int page=0;
Packit 1c1d7e
    for (fli.toFirst();(formula=fli.current());++fli)
Packit 1c1d7e
    {
Packit 1c1d7e
      QCString resultName;
Packit 1c1d7e
      resultName.sprintf("form_%d.png",formula->getId());
Packit 1c1d7e
      // only formulas for which no image exists are generated
Packit 1c1d7e
      QFileInfo fi(resultName);
Packit 1c1d7e
      if (!fi.exists())
Packit 1c1d7e
      {
Packit 1c1d7e
        // we force a pagebreak after each formula
Packit 1c1d7e
        t << formula->getFormulaText() << endl << "\\pagebreak\n\n";
Packit 1c1d7e
        pagesToGenerate.append(new int(page));
Packit 1c1d7e
      }
Packit 1c1d7e
      Doxygen::indexList->addImageFile(resultName);
Packit 1c1d7e
      page++;
Packit 1c1d7e
    }
Packit 1c1d7e
    t << "\\end{document}" << endl;
Packit 1c1d7e
    f.close();
Packit 1c1d7e
  }
Packit 1c1d7e
  if (pagesToGenerate.count()>0) // there are new formulas
Packit 1c1d7e
  {
Packit 1c1d7e
    //printf("Running latex...\n");
Packit 1c1d7e
    //system("latex _formulas.tex </dev/null >/dev/null");
Packit 1c1d7e
    QCString latexCmd = Config_getString(LATEX_CMD_NAME);
Packit 1c1d7e
    if (latexCmd.isEmpty()) latexCmd="latex";
Packit 1c1d7e
    portable_sysTimerStart();
Packit 1c1d7e
    if (portable_system(latexCmd,"_formulas.tex")!=0)
Packit 1c1d7e
    {
Packit 1c1d7e
      err("Problems running latex. Check your installation or look "
Packit 1c1d7e
          "for typos in _formulas.tex and check _formulas.log!\n");
Packit 1c1d7e
      formulaError=TRUE;
Packit 1c1d7e
      //return;
Packit 1c1d7e
    }
Packit 1c1d7e
    portable_sysTimerStop();
Packit 1c1d7e
    //printf("Running dvips...\n");
Packit 1c1d7e
    QListIterator<int> pli(pagesToGenerate);
Packit 1c1d7e
    int *pagePtr;
Packit 1c1d7e
    int pageIndex=1;
Packit 1c1d7e
    for (;(pagePtr=pli.current());++pli,++pageIndex)
Packit 1c1d7e
    {
Packit 1c1d7e
      int pageNum=*pagePtr;
Packit 1c1d7e
      msg("Generating image form_%d.png for formula\n",pageNum);
Packit 1c1d7e
      char dviArgs[4096];
Packit 1c1d7e
      QCString formBase;
Packit 1c1d7e
      formBase.sprintf("_form%d",pageNum);
Packit 1c1d7e
      // run dvips to convert the page with number pageIndex to an
Packit 1c1d7e
      // encapsulated postscript.
Packit 1c1d7e
      sprintf(dviArgs,"-q -D 600 -E -n 1 -p %d -o %s.eps _formulas.dvi",
Packit 1c1d7e
          pageIndex,formBase.data());
Packit 1c1d7e
      portable_sysTimerStart();
Packit 1c1d7e
      if (portable_system("dvips",dviArgs)!=0)
Packit 1c1d7e
      {
Packit 1c1d7e
        err("Problems running dvips. Check your installation!\n");
Packit 1c1d7e
        portable_sysTimerStop();
Packit 1c1d7e
        QDir::setCurrent(oldDir);
Packit 1c1d7e
        return;
Packit 1c1d7e
      }
Packit 1c1d7e
      portable_sysTimerStop();
Packit 1c1d7e
      // now we read the generated postscript file to extract the bounding box
Packit 1c1d7e
      QFileInfo fi(formBase+".eps");
Packit 1c1d7e
      if (fi.exists())
Packit 1c1d7e
      {
Packit 1c1d7e
        QCString eps = fileToString(formBase+".eps");
Packit 1c1d7e
        int i=eps.find("%%BoundingBox:");
Packit 1c1d7e
        if (i!=-1)
Packit 1c1d7e
        {
Packit 1c1d7e
          sscanf(eps.data()+i,"%%%%BoundingBox:%d %d %d %d",&x1,&y1,&x2,&y2;;
Packit 1c1d7e
        }
Packit 1c1d7e
        else
Packit 1c1d7e
        {
Packit 1c1d7e
          err("Couldn't extract bounding box!\n");
Packit 1c1d7e
        }
Packit 1c1d7e
      } 
Packit 1c1d7e
      // next we generate a postscript file which contains the eps
Packit 1c1d7e
      // and displays it in the right colors and the right bounding box
Packit 1c1d7e
      f.setName(formBase+".ps");
Packit 1c1d7e
      if (f.open(IO_WriteOnly))
Packit 1c1d7e
      {
Packit 1c1d7e
        FTextStream t(&f);
Packit 1c1d7e
        t << "1 1 1 setrgbcolor" << endl;  // anti-alias to white background
Packit 1c1d7e
        t << "newpath" << endl;
Packit 1c1d7e
        t << "-1 -1 moveto" << endl;
Packit 1c1d7e
        t << (x2-x1+2) << " -1 lineto" << endl;
Packit 1c1d7e
        t << (x2-x1+2) << " " << (y2-y1+2) << " lineto" << endl;
Packit 1c1d7e
        t << "-1 " << (y2-y1+2) << " lineto" <
Packit 1c1d7e
        t << "closepath" << endl;
Packit 1c1d7e
        t << "fill" << endl;
Packit 1c1d7e
        t << -x1 << " " << -y1 << " translate" << endl;
Packit 1c1d7e
        t << "0 0 0 setrgbcolor" << endl;
Packit 1c1d7e
        t << "(" << formBase << ".eps) run" << endl;
Packit 1c1d7e
        f.close();
Packit 1c1d7e
      }
Packit 1c1d7e
      // scale the image so that it is four times larger than needed.
Packit 1c1d7e
      // and the sizes are a multiple of four.
Packit 1c1d7e
      double scaleFactor = 16.0/3.0; 
Packit 1c1d7e
      int zoomFactor = Config_getInt(FORMULA_FONTSIZE);
Packit 1c1d7e
      if (zoomFactor<8 || zoomFactor>50) zoomFactor=10;
Packit 1c1d7e
      scaleFactor *= zoomFactor/10.0;
Packit 1c1d7e
      int gx = (((int)((x2-x1)*scaleFactor))+3)&~;;
Packit 1c1d7e
      int gy = (((int)((y2-y1)*scaleFactor))+3)&~;;
Packit 1c1d7e
      // Then we run ghostscript to convert the postscript to a pixmap
Packit 1c1d7e
      // The pixmap is a truecolor image, where only black and white are
Packit 1c1d7e
      // used.  
Packit 1c1d7e
Packit 1c1d7e
      char gsArgs[4096];
Packit 1c1d7e
      sprintf(gsArgs,"-q -g%dx%d -r%dx%dx -sDEVICE=ppmraw "
Packit 1c1d7e
                    "-sOutputFile=%s.pnm -dNOPAUSE -dBATCH -- %s.ps",
Packit 1c1d7e
                    gx,gy,(int)(scaleFactor*72),(int)(scaleFactor*72),
Packit 1c1d7e
                    formBase.data(),formBase.data()
Packit 1c1d7e
             );
Packit 1c1d7e
      portable_sysTimerStart();
Packit 1c1d7e
      if (portable_system(portable_ghostScriptCommand(),gsArgs)!=0)
Packit 1c1d7e
      {
Packit 1c1d7e
        err("Problem running ghostscript %s %s. Check your installation!\n",portable_ghostScriptCommand(),gsArgs);
Packit 1c1d7e
        portable_sysTimerStop();
Packit 1c1d7e
        QDir::setCurrent(oldDir);
Packit 1c1d7e
        return;
Packit 1c1d7e
      }
Packit 1c1d7e
      portable_sysTimerStop();
Packit 1c1d7e
      f.setName(formBase+".pnm");
Packit 1c1d7e
      uint imageX=0,imageY=0;
Packit 1c1d7e
      // we read the generated image again, to obtain the pixel data.
Packit 1c1d7e
      if (f.open(IO_ReadOnly))
Packit 1c1d7e
      {
Packit 1c1d7e
        QTextStream t(&f);
Packit 1c1d7e
        QCString s;
Packit 1c1d7e
        if (!t.eof())
Packit 1c1d7e
          s=t.readLine().utf8();
Packit 1c1d7e
        if (s.length()<2 || s.left(2)!="P6")
Packit 1c1d7e
          err("ghostscript produced an illegal image format!");
Packit 1c1d7e
        else
Packit 1c1d7e
        {
Packit 1c1d7e
          // assume the size is after the first line that does not start with
Packit 1c1d7e
          // # excluding the first line of the file.
Packit 1c1d7e
          while (!t.eof() && (s=t.readLine().utf8()) && !s.isEmpty() && s.at(0)=='#') { }
Packit 1c1d7e
          sscanf(s,"%d %d",&imageX,&imageY;;
Packit 1c1d7e
        }
Packit 1c1d7e
        if (imageX>0 && imageY>0)
Packit 1c1d7e
        {
Packit 1c1d7e
          //printf("Converting image...\n");
Packit 1c1d7e
          char *data = new char[imageX*imageY*3]; // rgb 8:8:8 format
Packit 1c1d7e
          uint i,x,y,ix,iy;
Packit 1c1d7e
          f.readBlock(data,imageX*imageY*3);
Packit 1c1d7e
          Image srcImage(imageX,imageY),
Packit 1c1d7e
                filteredImage(imageX,imageY),
Packit 1c1d7e
                dstImage(imageX/4,imageY/4);
Packit 1c1d7e
          uchar *ps=srcImage.getData();
Packit 1c1d7e
          // convert image to black (1) and white (0) index.
Packit 1c1d7e
          for (i=0;i
Packit 1c1d7e
          // apply a simple box filter to the image 
Packit 1c1d7e
          static int filterMask[]={1,2,1,2,8,2,1,2,1};
Packit 1c1d7e
          for (y=0;y
Packit 1c1d7e
          {
Packit 1c1d7e
            for (x=0;x
Packit 1c1d7e
            {
Packit 1c1d7e
              int s=0;
Packit 1c1d7e
              for (iy=0;iy<2;iy++)
Packit 1c1d7e
              {
Packit 1c1d7e
                for (ix=0;ix<2;ix++)
Packit 1c1d7e
                {
Packit 1c1d7e
                  s+=srcImage.getPixel(x+ix-1,y+iy-1)*filterMask[iy*3+ix];
Packit 1c1d7e
                }
Packit 1c1d7e
              }
Packit 1c1d7e
              filteredImage.setPixel(x,y,s);
Packit 1c1d7e
            }
Packit 1c1d7e
          }
Packit 1c1d7e
          // down-sample the image to 1/16th of the area using 16 gray scale
Packit 1c1d7e
          // colors.
Packit 1c1d7e
          // TODO: optimize this code.
Packit 1c1d7e
          for (y=0;y
Packit 1c1d7e
          {
Packit 1c1d7e
            for (x=0;x
Packit 1c1d7e
            {
Packit 1c1d7e
              int xp=x<<2;
Packit 1c1d7e
              int yp=y<<2;
Packit 1c1d7e
              int c=filteredImage.getPixel(xp+0,yp+0)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+1,yp+0)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+2,yp+0)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+3,yp+0)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+0,yp+1)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+1,yp+1)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+2,yp+1)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+3,yp+1)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+0,yp+2)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+1,yp+2)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+2,yp+2)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+3,yp+2)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+0,yp+3)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+1,yp+3)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+2,yp+3)+
Packit 1c1d7e
                    filteredImage.getPixel(xp+3,yp+3);
Packit 1c1d7e
              // here we scale and clip the color value so the
Packit 1c1d7e
              // resulting image has a reasonable contrast
Packit 1c1d7e
              dstImage.setPixel(x,y,QMIN(15,(c*15)/(16*10)));
Packit 1c1d7e
            }
Packit 1c1d7e
          }
Packit 1c1d7e
          // save the result as a bitmap
Packit 1c1d7e
          QCString resultName;
Packit 1c1d7e
          resultName.sprintf("form_%d.png",pageNum);
Packit 1c1d7e
          // the option parameter 1 is used here as a temporary hack
Packit 1c1d7e
          // to select the right color palette! 
Packit 1c1d7e
          dstImage.save(resultName,1);
Packit 1c1d7e
          delete[] data;
Packit 1c1d7e
        }
Packit 1c1d7e
        f.close();
Packit 1c1d7e
      } 
Packit 1c1d7e
      // remove intermediate image files
Packit 1c1d7e
      thisDir.remove(formBase+".eps");
Packit 1c1d7e
      thisDir.remove(formBase+".pnm");
Packit 1c1d7e
      thisDir.remove(formBase+".ps");
Packit 1c1d7e
    }
Packit 1c1d7e
    // remove intermediate files produced by latex
Packit 1c1d7e
    thisDir.remove("_formulas.dvi");
Packit 1c1d7e
    if (!formulaError) thisDir.remove("_formulas.log"); // keep file in case of errors
Packit 1c1d7e
    thisDir.remove("_formulas.aux");
Packit 1c1d7e
  }
Packit 1c1d7e
  // remove the latex file itself
Packit 1c1d7e
  if (!formulaError) thisDir.remove("_formulas.tex");
Packit 1c1d7e
  // write/update the formula repository so we know what text the 
Packit 1c1d7e
  // generated images represent (we use this next time to avoid regeneration
Packit 1c1d7e
  // of the images, and to avoid forcing the user to delete all images in order
Packit 1c1d7e
  // to let a browser refresh the images).
Packit 1c1d7e
  f.setName("formula.repository");
Packit 1c1d7e
  if (f.open(IO_WriteOnly))
Packit 1c1d7e
  {
Packit 1c1d7e
    FTextStream t(&f);
Packit 1c1d7e
    for (fli.toFirst();(formula=fli.current());++fli)
Packit 1c1d7e
    {
Packit 1c1d7e
      t << "\\form#" << formula->getId() << ":" << formula->getFormulaText() << endl;
Packit 1c1d7e
    }
Packit 1c1d7e
    f.close();
Packit 1c1d7e
  }
Packit 1c1d7e
  // reset the directory to the original location.
Packit 1c1d7e
  QDir::setCurrent(oldDir);
Packit 1c1d7e
}
Packit 1c1d7e
Packit 1c1d7e
Packit 1c1d7e
#ifdef FORMULA_TEST
Packit 1c1d7e
int main()
Packit 1c1d7e
{
Packit 1c1d7e
  FormulaList fl;
Packit 1c1d7e
  fl.append(new Formula("$x^2$"));
Packit 1c1d7e
  fl.append(new Formula("$y^2$"));
Packit 1c1d7e
  fl.append(new Formula("$\\sqrt{x_0^2+x_1^2+x_2^2}$"));
Packit 1c1d7e
  fl.generateBitmaps("dest");
Packit 1c1d7e
  return 0;
Packit 1c1d7e
}
Packit 1c1d7e
#endif