diff options
Diffstat (limited to 'src/formula.cpp')
-rw-r--r-- | src/formula.cpp | 483 |
1 files changed, 297 insertions, 186 deletions
diff --git a/src/formula.cpp b/src/formula.cpp index 01a96b3..063684e 100644 --- a/src/formula.cpp +++ b/src/formula.cpp @@ -1,7 +1,6 @@ /****************************************************************************** - * * - * Copyright (C) 1997-2015 by Dimitri van Heesch. + * Copyright (C) 1997-2020 by Dimitri van Heesch. * * Permission to use, copy, modify, and distribute this software and its * documentation under the terms of the GNU General Public License is hereby @@ -14,41 +13,135 @@ * */ -#include <stdlib.h> -#include <qfile.h> -#include <qfileinfo.h> -#include <qtextstream.h> -#include <qdir.h> - #include "formula.h" -#include "image.h" -#include "util.h" #include "message.h" #include "config.h" -#include "portable.h" -#include "index.h" -#include "doxygen.h" #include "ftextstream.h" +#include "util.h" +#include "portable.h" +#include "image.h" + +#include <qfile.h> +#include <qtextstream.h> +#include <qdir.h> + +#include <map> +#include <vector> +#include <string> +#include <utility> -Formula::Formula(const char *text) +// TODO: remove these dependencies +#include "doxygen.h" // for Doxygen::indexList +#include "index.h" // for Doxygen::indexList + +// Remove the temporary files +#define RM_TMP_FILES (true) +//#define RM_TMP_FILES (false) + +struct FormulaManager::Private +{ + void storeDisplaySize(int id,int w,int h) + { + displaySizeMap.insert(std::make_pair(id,DisplaySize(w,h))); + } + DisplaySize getDisplaySize(int id) + { + auto it = displaySizeMap.find(id); + if (it!=displaySizeMap.end()) + { + return it->second; + } + return DisplaySize(-1,-1); + } + std::vector<std::string> formulas; + std::map<std::string,int> formulaMap; + std::map<int,DisplaySize> displaySizeMap; +}; + +FormulaManager::FormulaManager() : p(new Private) { - static int count=0; - number = count++; - form=text; } -Formula::~Formula() +FormulaManager &FormulaManager::instance() { + static FormulaManager fm; + return fm; } -int Formula::getId() +void FormulaManager::readFormulas(const char *dir,bool doCompare) { - return number; + QFile f(QCString(dir)+"/formula.repository"); + if (f.open(IO_ReadOnly)) // open repository + { + int formulaCount=0; + msg("Reading formula repository...\n"); + QTextStream t(&f); + QCString line; + int lineNr=1; + while (!t.eof()) + { + line=t.readLine().utf8(); + // old format: \_form#<digits>:formula + // new format: \_form#<digits>=<digits>x<digits>:formula + int hi=line.find('#'); + int ei=line.find('='); + int se=line.find(':'); // find name and text separator. + if (hi==-1 || se==-1 || hi>se) + { + warn_uncond("%s/formula.repository is corrupted at line %d!\n",dir,lineNr); + break; + } + else + { + QCString formName = line.left(se); + QCString formText = line.right(line.length()-se-1); + int w=-1,h=-1; + if (ei!=-1 && ei>hi && ei<se) // new format + { + int xi=formName.find('x',hi); + if (xi!=-1) + { + w=formName.mid(hi+1,xi-hi-1).toInt(); + h=formName.mid(xi+1).toInt(); + } + formName = formName.left(ei); + } + else + { + ei=formName.length(); + } + if (doCompare) + { + int formId = formName.mid(hi+1,ei-hi-1).toInt(); + QCString storedFormText = FormulaManager::instance().findFormula(formId); + if (storedFormText!=formText) + { + term("discrepancy between formula repositories! Remove " + "formula.repository and from_* files from output directories.\n"); + } + formulaCount++; + } + else + { + int id = addFormula(formText); + if (w!=-1 && h!=-1) + { + p->storeDisplaySize(id,w,h); + } + } + } + lineNr++; + } + if (doCompare && formulaCount!=p->formulas.size()) + { + term("size discrepancy between formula repositories! Remove " + "formula.repository and from_* files from output directories.\n"); + } + } } -void FormulaList::generateBitmaps(const char *path) +void FormulaManager::generateImages(const char *path,Format format,HighDPI hd) const { - int x1,y1,x2,y2; QDir d(path); // store the original directory if (!d.exists()) @@ -70,10 +163,7 @@ void FormulaList::generateBitmaps(const char *path) QDir thisDir; // generate a latex file containing one formula per page. QCString texName="_formulas.tex"; - QList<int> pagesToGenerate; - pagesToGenerate.setAutoDelete(TRUE); - FormulaListIterator fli(*this); - Formula *formula; + std::vector<int> formulasToGenerate; QFile f(texName); bool formulaError=FALSE; if (f.open(IO_WriteOnly)) @@ -93,32 +183,32 @@ void FormulaList::generateBitmaps(const char *path) } t << "\\pagestyle{empty}" << endl; t << "\\begin{document}" << endl; - int page=0; - for (fli.toFirst();(formula=fli.current());++fli) + for (int i=0; i<(int)p->formulas.size(); i++) { QCString resultName; - resultName.sprintf("form_%d.png",formula->getId()); + resultName.sprintf("form_%d.%s",i,format==Format::Vector?"svg":"png"); // 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)); + t << p->formulas[i].c_str() << endl << "\\pagebreak\n\n"; + formulasToGenerate.push_back(i); } Doxygen::indexList->addImageFile(resultName); - page++; } t << "\\end{document}" << endl; f.close(); } - if (pagesToGenerate.count()>0) // there are new formulas + if (!formulasToGenerate.empty()) // there are new formulas { //printf("Running latex...\n"); //system("latex _formulas.tex </dev/null >/dev/null"); QCString latexCmd = "latex"; Portable::sysTimerStart(); - if (Portable::system(latexCmd,"_formulas.tex")!=0) + char args[4096]; + sprintf(args,"-interaction=batchmode _formulas.tex >%s",Portable::devNull()); + if (Portable::system(latexCmd,args)!=0) { err("Problems running latex. Check your installation or look " "for typos in _formulas.tex and check _formulas.log!\n"); @@ -127,23 +217,18 @@ void FormulaList::generateBitmaps(const char *path) } Portable::sysTimerStop(); //printf("Running dvips...\n"); - QListIterator<int> pli(pagesToGenerate); - int *pagePtr; int pageIndex=1; - for (;(pagePtr=pli.current());++pli,++pageIndex) + for (int pageNum : formulasToGenerate) { - int pageNum=*pagePtr; msg("Generating image form_%d.png for formula\n",pageNum); - char dviArgs[4096]; - char psArgs[4096]; QCString formBase; formBase.sprintf("_form%d",pageNum); // run dvips to convert the page with number pageIndex to an // postscript file. - sprintf(dviArgs,"-q -D 600 -n 1 -p %d -o %s_tmp.ps _formulas.dvi", + sprintf(args,"-q -D 600 -n 1 -p %d -o %s_tmp.ps _formulas.dvi", pageIndex,formBase.data()); Portable::sysTimerStart(); - if (Portable::system("dvips",dviArgs)!=0) + if (Portable::system("dvips",args)!=0) { err("Problems running dvips. Check your installation!\n"); Portable::sysTimerStop(); @@ -151,179 +236,171 @@ void FormulaList::generateBitmaps(const char *path) return; } Portable::sysTimerStop(); - // run eps2eps to convert to an encapsulated postscript file with - // boundingbox (dvips with -E has some problems here). - sprintf(psArgs,"%s_tmp.ps %s.eps",formBase.data(),formBase.data()); + + // extract the bounding box for the postscript file + sprintf(args,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=bbox %s_tmp.ps 2>%s_tmp.epsi", + formBase.data(),formBase.data()); Portable::sysTimerStart(); - if (Portable::system("eps2eps",psArgs)!=0) + if (Portable::system(Portable::ghostScriptCommand(),args)!=0) { - err("Problems running eps2eps. Check your installation!\n"); + err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand()); Portable::sysTimerStop(); QDir::setCurrent(oldDir); return; } Portable::sysTimerStop(); - // now we read the generated postscript file to extract the bounding box - QFileInfo fi(formBase+".eps"); + + // extract the bounding box info from the generate .epsi file + int x1=0,y1=0,x2=0,y2=0; + QFileInfo fi(formBase+"_tmp.epsi"); if (fi.exists()) { - QCString eps = fileToString(formBase+".eps"); - int i=eps.find("%%BoundingBox:"); + QString eps = fileToString(formBase+"_tmp.epsi"); + int i = eps.find("%%BoundingBox:"); if (i!=-1) { sscanf(eps.data()+i,"%%%%BoundingBox:%d %d %d %d",&x1,&y1,&x2,&y2); } else { - err("Couldn't extract bounding box!\n"); + err("Couldn't extract bounding box from %s_tmp.epsi",formBase.data()); } - } - // 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)) - { - FTextStream t(&f); - t << "1 1 1 setrgbcolor" << endl; // anti-alias to white background - t << "newpath" << endl; - t << "-1 -1 moveto" << endl; - t << (x2-x1+2) << " -1 lineto" << endl; - t << (x2-x1+2) << " " << (y2-y1+2) << " lineto" << endl; - t << "-1 " << (y2-y1+2) << " lineto" <<endl; - 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. - double scaleFactor = 16.0/3.0; + //printf("Bounding box [%d %d %d %d]\n",x1,y1,x2,y2); + + // convert the corrected EPS to a bitmap + double scaleFactor = 1.25; int zoomFactor = Config_getInt(FORMULA_FONTSIZE); if (zoomFactor<8 || zoomFactor>50) zoomFactor=10; scaleFactor *= zoomFactor/10.0; - int gx = (((int)((x2-x1)*scaleFactor))+3)&~1; - int gy = (((int)((y2-y1)*scaleFactor))+3)&~1; - // Then we run ghostscript to convert the postscript to a pixmap - // The pixmap is a truecolor image, where only black and white are - // used. - char gsArgs[4096]; - sprintf(gsArgs,"-q -g%dx%d -r%dx%d -sDEVICE=ppmraw " - "-sOutputFile=%s.pnm -dNOPAUSE -dBATCH -dNOSAFER %s.ps", - gx,gy,(int)(scaleFactor*72),(int)(scaleFactor*72), - formBase.data(),formBase.data() - ); - Portable::sysTimerStart(); - if (Portable::system(Portable::ghostScriptCommand(),gsArgs)!=0) + int width = (int)((x2-x1)*scaleFactor+0.5f); + int height = (int)((y2-y1)*scaleFactor+0.5f); + p->storeDisplaySize(pageNum,width,height); + + if (format==Format::Vector) { - err("Problem running ghostscript %s %s. Check your installation!\n",Portable::ghostScriptCommand(),gsArgs); + // crop the image to its bounding box + sprintf(args,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=pdfwrite" + " -o %s_tmp.pdf -c \"[/CropBox [%d %d %d %d] /PAGES pdfmark\" -f %s_tmp.ps", + formBase.data(),x1,y1,x2,y2,formBase.data()); + Portable::sysTimerStart(); + if (Portable::system(Portable::ghostScriptCommand(),args)!=0) + { + err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand()); + Portable::sysTimerStop(); + QDir::setCurrent(oldDir); + return; + } Portable::sysTimerStop(); - QDir::setCurrent(oldDir); - return; + + sprintf(args,"%s_tmp.pdf form_%d.svg",formBase.data(),pageNum); + Portable::sysTimerStart(); + if (Portable::system("pdf2svg",args)!=0) + { + err("Problems running pdf2svg. Check your installation!\n"); + Portable::sysTimerStop(); + QDir::setCurrent(oldDir); + return; + } + Portable::sysTimerStop(); + if (RM_TMP_FILES) thisDir.remove(formBase+"_tmp.pdf"); } - Portable::sysTimerStop(); - 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)) + else // format==Format::Bitmap { - QTextStream t(&f); - QCString s; - if (!t.eof()) - s=t.readLine().utf8(); - if (s.length()<2 || s.left(2)!="P6") - err("ghostscript produced an illegal image format!"); - else + // crop the image to its bounding box + sprintf(args,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=eps2write" + " -o %s_tmp.eps -f %s_tmp.ps",formBase.data(),formBase.data()); + Portable::sysTimerStart(); + if (Portable::system(Portable::ghostScriptCommand(),args)!=0) { - // assume the size is after the first line that does not start with - // # excluding the first line of the file. - while (!t.eof() && (s=t.readLine().utf8()) && !s.isEmpty() && s.at(0)=='#') { } - sscanf(s,"%d %d",&imageX,&imageY); + err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand()); + Portable::sysTimerStop(); + QDir::setCurrent(oldDir); + return; } - if (imageX>0 && imageY>0) + + // read back %s_tmp.eps and replace + // bounding box values with x1,y1,x2,y2 and remove the HiResBoundingBox + QFile epsIn(formBase+"_tmp.eps"); + QFile epsOut(formBase+"_tmp_corr.eps"); + if (epsIn.open(IO_ReadOnly) && epsOut.open(IO_WriteOnly)) { - //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++) + int maxLineLen=100*1024; + while (!epsIn.atEnd()) { - for (x=0;x<srcImage.getWidth();x++) + QCString buf(maxLineLen); + FTextStream t(&epsOut); + int numBytes = epsIn.readLine(buf.rawData(),maxLineLen); + if (numBytes>0) { - int s=0; - for (iy=0;iy<2;iy++) + buf.resize(numBytes+1); + if (buf.startsWith("%%BoundingBox")) { - for (ix=0;ix<2;ix++) - { - s+=srcImage.getPixel(x+ix-1,y+iy-1)*filterMask[iy*3+ix]; - } + t << "%%BoundingBox: " << x1 << " " << y1 << " " << x2 << " " << y2 << endl; + } + else if (buf.startsWith("%%HiResBoundingBox")) // skip this one + { + } + else + { + t << buf; } - filteredImage.setPixel(x,y,s); - } - } - // down-sample the image to 1/16th of the area using 16 gray scale - // colors. - // TODO: optimize this code. - 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))); } } - // save the result as a bitmap - QCString resultName; - resultName.sprintf("form_%d.png",pageNum); - // the option parameter 1 is used here as a temporary hack - // to select the right color palette! - dstImage.save(resultName,1); - delete[] data; + epsIn.close(); + epsOut.close(); + } + else + { + err("Problems correcting the eps files from %s_tmp.eps to %s_tmp_corr.eps\n", + formBase.data(),formBase.data()); + QDir::setCurrent(oldDir); + return; + } + + if (hd==HighDPI::On) // for high DPI display it looks much better if the + // image resolution is higher than the display resolution + { + scaleFactor*=2; + } + + Portable::sysTimerStop(); + sprintf(args,"-q -dNOSAFER -dBATCH -dNOPAUSE -dEPSCrop -sDEVICE=pnggray -dGraphicsAlphaBits=4 -dTextAlphaBits=4 " + "-r%d -sOutputFile=form_%d.png %s_tmp_corr.eps",(int)(scaleFactor*72),pageNum,formBase.data()); + Portable::sysTimerStart(); + if (Portable::system(Portable::ghostScriptCommand(),args)!=0) + { + err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand()); + Portable::sysTimerStop(); + QDir::setCurrent(oldDir); + return; } - f.close(); - } + Portable::sysTimerStop(); + + thisDir.remove(formBase+"_tmp.eps"); + thisDir.remove(formBase+"_tmp_corr.eps"); + } + // remove intermediate image files - thisDir.remove(formBase+"_tmp.ps"); - thisDir.remove(formBase+".eps"); - thisDir.remove(formBase+".pnm"); - thisDir.remove(formBase+".ps"); + if (RM_TMP_FILES) + { + thisDir.remove(formBase+"_tmp.ps"); + thisDir.remove(formBase+"_tmp.epsi"); + } + pageIndex++; } // remove intermediate files produced by latex - thisDir.remove("_formulas.dvi"); - if (!formulaError) thisDir.remove("_formulas.log"); // keep file in case of errors - thisDir.remove("_formulas.aux"); + if (RM_TMP_FILES) + { + thisDir.remove("_formulas.dvi"); + if (!formulaError) thisDir.remove("_formulas.log"); // keep file in case of errors + thisDir.remove("_formulas.aux"); + } } // remove the latex file itself - if (!formulaError) thisDir.remove("_formulas.tex"); + if (RM_TMP_FILES && !formulaError) thisDir.remove("_formulas.tex"); + // write/update the formula repository so we know what text the // 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 @@ -332,9 +409,15 @@ void FormulaList::generateBitmaps(const char *path) if (f.open(IO_WriteOnly)) { FTextStream t(&f); - for (fli.toFirst();(formula=fli.current());++fli) + for (int i=0; i<(int)p->formulas.size(); i++) { - t << "\\_form#" << formula->getId() << ":" << formula->getFormulaText() << endl; + DisplaySize size = p->getDisplaySize(i); + t << "\\_form#" << i; + if (size.width!=-1 && size.height!=-1) + { + t << "=" << size.width << "x" << size.height; + } + t << ":" << p->formulas[i].c_str() << endl; } f.close(); } @@ -342,15 +425,43 @@ void FormulaList::generateBitmaps(const char *path) QDir::setCurrent(oldDir); } +void FormulaManager::clear() +{ + p->formulas.clear(); + p->formulaMap.clear(); +} + +int FormulaManager::addFormula(const char *formulaText) +{ + std::string key = std::string(formulaText); + auto it = p->formulaMap.find(key); + if (it!=p->formulaMap.end()) // already stored + { + return it->second; + } + // store new formula + int id = p->formulas.size(); + p->formulaMap.insert(std::pair<std::string,int>(key,id)); + p->formulas.push_back(key); + return id; +} + +QCString FormulaManager::findFormula(int formulaId) const +{ + if (formulaId>=0 && formulaId<(int)p->formulas.size()) + { + return p->formulas[formulaId].c_str(); + } + return QCString(); +} + +bool FormulaManager::hasFormulas() const +{ + return !p->formulas.empty(); +} -#ifdef FORMULA_TEST -int main() +FormulaManager::DisplaySize FormulaManager::displaySize(int formulaId) const { - 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; + return p->getDisplaySize(formulaId); } -#endif + |