diff options
-rw-r--r-- | src/commentscan.l | 21 | ||||
-rw-r--r-- | src/config.xml | 11 | ||||
-rw-r--r-- | src/docparser.cpp | 12 | ||||
-rw-r--r-- | src/doxygen.cpp | 136 | ||||
-rw-r--r-- | src/doxygen.h | 3 | ||||
-rw-r--r-- | src/formula.cpp | 483 | ||||
-rw-r--r-- | src/formula.h | 58 | ||||
-rw-r--r-- | src/htmldocvisitor.cpp | 24 | ||||
-rw-r--r-- | src/portable.cpp | 9 | ||||
-rw-r--r-- | src/portable.h | 1 |
10 files changed, 406 insertions, 352 deletions
diff --git a/src/commentscan.l b/src/commentscan.l index 411f5a7..597246e 100644 --- a/src/commentscan.l +++ b/src/commentscan.l @@ -17,6 +17,9 @@ %option prefix="commentscanYY" %option reentrant %option extra-type="struct commentscanYY_state *" +%top{ +#include <stdint.h> +} %{ @@ -2862,21 +2865,9 @@ static QCString addFormula(yyscan_t yyscanner) struct yyguts_t *yyg = (struct yyguts_t*)yyscanner; QCString formLabel; QCString fText=yyextra->formulaText.simplifyWhiteSpace(); - Formula *f=0; - if ((f=Doxygen::formulaDict->find(fText))==0) - { - f = new Formula(fText); - Doxygen::formulaList->append(f); - Doxygen::formulaDict->insert(fText,f); - formLabel.sprintf("\\_form#%d",f->getId()); - Doxygen::formulaNameDict->insert(formLabel,f); - } - else - { - formLabel.sprintf("\\_form#%d",f->getId()); - } - int i; - for (i=0;i<yyextra->formulaNewLines;i++) formLabel+="@_fakenl"; // add fake newlines to + int id = FormulaManager::instance().addFormula(fText); + formLabel.sprintf("\\_form#%d",id); + for (int i=0;i<yyextra->formulaNewLines;i++) formLabel+="@_fakenl"; // add fake newlines to // keep the warnings // correctly aligned. return formLabel; diff --git a/src/config.xml b/src/config.xml index f40744d..fec21b6 100644 --- a/src/config.xml +++ b/src/config.xml @@ -2357,6 +2357,17 @@ The \c DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. ]]> </docs> </option> + <option type='enum' id='HTML_FORMULA_FORMAT' defval='PNG' depends='GENERATE_HTML'> + <docs> +<![CDATA[ + If the \c HTML_FORMULA_FORMAT option is set to \c SVG, doxygen will use the pdf2svg + tool (see https://github.com/dawbarton/pdf2svg) to generate formulas as SVG images instead of + PNGs for the HTML output. These images will generally look nicer at scaled resolutions. +]]> + </docs> + <value name="PNG" desc="The default"/> + <value name="SVG" desc="Looks nicer but requires the pdf2svg tool"/> + </option> <option type='int' id='FORMULA_FONTSIZE' minval='8' maxval='50' defval='10' depends='GENERATE_HTML'> <docs> <![CDATA[ diff --git a/src/docparser.cpp b/src/docparser.cpp index 9348810..b3ae6bd 100644 --- a/src/docparser.cpp +++ b/src/docparser.cpp @@ -2223,15 +2223,13 @@ bool DocXRefItem::parse() DocFormula::DocFormula(DocNode *parent,int id) : m_relPath(g_relPath) { - m_parent = parent; - QCString formCmd; - formCmd.sprintf("\\_form#%d",id); - Formula *formula=Doxygen::formulaNameDict->find(formCmd); - if (formula) + m_parent = parent; + QCString text = FormulaManager::instance().findFormula(id); + if (!text.isEmpty()) { - m_id = formula->getId(); + m_id = id; m_name.sprintf("form_%d",m_id); - m_text = formula->getFormulaText(); + m_text = text; } else // wrong \_form#<n> command { diff --git a/src/doxygen.cpp b/src/doxygen.cpp index 49c4d5a..a36bf06 100644 --- a/src/doxygen.cpp +++ b/src/doxygen.cpp @@ -126,9 +126,6 @@ MemberNameSDict *Doxygen::functionNameSDict = 0; FileNameList *Doxygen::inputNameList = 0; // all input files FileNameDict *Doxygen::inputNameDict = 0; GroupSDict *Doxygen::groupSDict = 0; -FormulaList *Doxygen::formulaList = 0; // all formulas -FormulaDict *Doxygen::formulaDict = 0; // all formulas -FormulaDict *Doxygen::formulaNameDict = 0; // the label name of all formulas PageSDict *Doxygen::pageSDict = 0; PageSDict *Doxygen::exampleSDict = 0; SectionDict *Doxygen::sectionDict = 0; // all page sections @@ -197,7 +194,6 @@ void clearAll() Doxygen::pageSDict->clear(); Doxygen::exampleSDict->clear(); Doxygen::inputNameList->clear(); - Doxygen::formulaList->clear(); Doxygen::sectionDict->clear(); Doxygen::inputNameDict->clear(); Doxygen::includeNameDict->clear(); @@ -206,11 +202,10 @@ void clearAll() Doxygen::dotFileNameDict->clear(); Doxygen::mscFileNameDict->clear(); Doxygen::diaFileNameDict->clear(); - Doxygen::formulaDict->clear(); - Doxygen::formulaNameDict->clear(); Doxygen::tagDestinationDict.clear(); delete Doxygen::citeDict; delete Doxygen::mainPage; Doxygen::mainPage=0; + FormulaManager::instance().clear(); } class Statistics @@ -281,10 +276,6 @@ void statistics() fprintf(stderr,"--- typedefDict stats ----\n"); fprintf(stderr,"--- namespaceAliasDict stats ----\n"); Doxygen::namespaceAliasDict.statistics(); - fprintf(stderr,"--- formulaDict stats ----\n"); - Doxygen::formulaDict->statistics(); - fprintf(stderr,"--- formulaNameDict stats ----\n"); - Doxygen::formulaNameDict->statistics(); fprintf(stderr,"--- tagDestinationDict stats ----\n"); Doxygen::tagDestinationDict.statistics(); fprintf(stderr,"--- g_compoundKeywordDict stats ----\n"); @@ -9576,66 +9567,6 @@ int readFileOrDirectory(const char *s, //---------------------------------------------------------------------------- -void readFormulaRepository(QCString dir, bool cmp) -{ - static int current_repository = 0; - int new_repository = 0; - QFile f(dir+"/formula.repository"); - if (f.open(IO_ReadOnly)) // open repository - { - msg("Reading formula repository...\n"); - QTextStream t(&f); - QCString line; - Formula *f; - while (!t.eof()) - { - line=t.readLine().utf8(); - int se=line.find(':'); // find name and text separator. - if (se==-1) - { - warn_uncond("formula.repository is corrupted!\n"); - break; - } - else - { - QCString formName = line.left(se); - QCString formText = line.right(line.length()-se-1); - if (cmp) - { - if ((f=Doxygen::formulaDict->find(formText))==0) - { - term("discrepancy between formula repositories! Remove " - "formula.repository and from_* files from output directories."); - } - QCString formLabel; - formLabel.sprintf("\\_form#%d",f->getId()); - if (formLabel != formName) - { - term("discrepancy between formula repositories! Remove " - "formula.repository and from_* files from output directories."); - } - new_repository++; - } - else - { - f=new Formula(formText); - Doxygen::formulaList->append(f); - Doxygen::formulaDict->insert(formText,f); - Doxygen::formulaNameDict->insert(formName,f); - current_repository++; - } - } - } - } - if (cmp && (current_repository != new_repository)) - { - term("size discrepancy between formula repositories! Remove " - "formula.repository and from_* files from output directories."); - } -} - -//---------------------------------------------------------------------------- - static void expandAliases() { QDictIterator<QCString> adi(Doxygen::aliasDict); @@ -9935,10 +9866,6 @@ void initDoxygen() Doxygen::citeDict = new CiteDict(257); Doxygen::genericsDict = new GenericsSDict; Doxygen::indexList = new IndexList; - Doxygen::formulaList = new FormulaList; - Doxygen::formulaList->setAutoDelete(TRUE); - Doxygen::formulaDict = new FormulaDict(1009); - Doxygen::formulaNameDict = new FormulaDict(1009); Doxygen::sectionDict = new SectionDict(257); Doxygen::sectionDict->setAutoDelete(TRUE); @@ -9968,10 +9895,9 @@ void initDoxygen() void cleanUpDoxygen() { + FormulaManager::instance().clear(); + delete Doxygen::sectionDict; - delete Doxygen::formulaNameDict; - delete Doxygen::formulaDict; - delete Doxygen::formulaList; delete Doxygen::indexList; delete Doxygen::genericsDict; delete Doxygen::inputNameDict; @@ -11036,18 +10962,22 @@ void parseInput() if (Config_getBool(GENERATE_HTML) && !Config_getBool(USE_MATHJAX)) { - readFormulaRepository(Config_getString(HTML_OUTPUT)); + FormulaManager::instance().readFormulas(Config_getString(HTML_OUTPUT)); } if (Config_getBool(GENERATE_RTF)) { // in case GENERRATE_HTML is set we just have to compare, both repositories should be identical - readFormulaRepository(Config_getString(RTF_OUTPUT),Config_getBool(GENERATE_HTML) && !Config_getBool(USE_MATHJAX)); + FormulaManager::instance().readFormulas(Config_getString(RTF_OUTPUT), + Config_getBool(GENERATE_HTML) && + !Config_getBool(USE_MATHJAX)); } if (Config_getBool(GENERATE_DOCBOOK)) { // in case GENERRATE_HTML is set we just have to compare, both repositories should be identical - readFormulaRepository(Config_getString(DOCBOOK_OUTPUT), - (Config_getBool(GENERATE_HTML) && !Config_getBool(USE_MATHJAX)) || Config_getBool(GENERATE_RTF)); + FormulaManager::instance().readFormulas(Config_getString(DOCBOOK_OUTPUT), + (Config_getBool(GENERATE_HTML) && + !Config_getBool(USE_MATHJAX)) || + Config_getBool(GENERATE_RTF)); } /************************************************************************** @@ -11477,6 +11407,29 @@ void generateOutput() } g_s.end(); + const FormulaManager &fm = FormulaManager::instance(); + if (fm.hasFormulas() && generateHtml + && !Config_getBool(USE_MATHJAX)) + { + g_s.begin("Generating images for formulas in HTML...\n"); + fm.generateImages(Config_getString(HTML_OUTPUT), Config_getEnum(HTML_FORMULA_FORMAT)=="SVG" ? + FormulaManager::Format::Vector : FormulaManager::Format::Bitmap, FormulaManager::HighDPI::On); + g_s.end(); + } + if (fm.hasFormulas() && generateRtf) + { + g_s.begin("Generating images for formulas in RTF...\n"); + fm.generateImages(Config_getString(RTF_OUTPUT),FormulaManager::Format::Bitmap); + g_s.end(); + } + + if (fm.hasFormulas() && generateDocbook) + { + g_s.begin("Generating images for formulas in Docbook...\n"); + fm.generateImages(Config_getString(DOCBOOK_OUTPUT),FormulaManager::Format::Bitmap); + g_s.end(); + } + g_s.begin("Generating example documentation...\n"); generateExampleDocs(); g_s.end(); @@ -11516,27 +11469,6 @@ void generateOutput() generateDirDocs(*g_outputList); g_s.end(); - if (Doxygen::formulaList->count()>0 && generateHtml - && !Config_getBool(USE_MATHJAX)) - { - g_s.begin("Generating bitmaps for formulas in HTML...\n"); - Doxygen::formulaList->generateBitmaps(Config_getString(HTML_OUTPUT)); - g_s.end(); - } - if (Doxygen::formulaList->count()>0 && generateRtf) - { - g_s.begin("Generating bitmaps for formulas in RTF...\n"); - Doxygen::formulaList->generateBitmaps(Config_getString(RTF_OUTPUT)); - g_s.end(); - } - - if (Doxygen::formulaList->count()>0 && generateDocbook) - { - g_s.begin("Generating bitmaps for formulas in Docbook...\n"); - Doxygen::formulaList->generateBitmaps(Config_getString(DOCBOOK_OUTPUT)); - g_s.end(); - } - if (Config_getBool(SORT_GROUP_NAMES)) { Doxygen::groupSDict->sort(); diff --git a/src/doxygen.h b/src/doxygen.h index b93ab27..19136c3 100644 --- a/src/doxygen.h +++ b/src/doxygen.h @@ -116,9 +116,6 @@ class Doxygen static StringDict namespaceAliasDict; static GroupSDict *groupSDict; static NamespaceSDict *namespaceSDict; - static FormulaList *formulaList; - static FormulaDict *formulaDict; - static FormulaDict *formulaNameDict; static StringDict tagDestinationDict; static StringDict aliasDict; static QIntDict<MemberGroupInfo> memGrpInfoDict; 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 + diff --git a/src/formula.h b/src/formula.h index 422030c..4bd90af 100644 --- a/src/formula.h +++ b/src/formula.h @@ -18,45 +18,33 @@ #ifndef FORMULA_H #define FORMULA_H -#include <qlist.h> -#include <qdict.h> +#include <memory> +#include <qcstring.h> -/** Class representing a formula in the output. */ -class Formula +/*! Manager class to handle formulas */ +class FormulaManager { public: - Formula(const char *text); - ~Formula(); - int getId(); - QCString getFormulaText() const { return form; } - + struct DisplaySize + { + DisplaySize(int w,int h) : width(w), height(h) {} + int width; + int height; + }; + enum class Format { Bitmap, Vector }; + enum class HighDPI { On, Off }; + static FormulaManager &instance(); + void readFormulas(const char *dir,bool doCompare=false); + void clear(); + int addFormula(const char *formulaText); + void generateImages(const char *outputDir,Format format,HighDPI hd = HighDPI::Off) const; + QCString findFormula(int formulaId) const; + bool hasFormulas() const; + DisplaySize displaySize(int formulaId) const; private: - int number; - QCString form; -}; - -/** A list of Formula objects. */ -class FormulaList : public QList<Formula> -{ - public: - void generateBitmaps(const char *path); -}; - -/** Iterator for Formula objects in a FormulaList. */ -class FormulaListIterator : public QListIterator<Formula> -{ - public: - FormulaListIterator(const FormulaList &l) : - QListIterator<Formula>(l) {} -}; - -/** Unsorted dictionary of Formula objects. */ -class FormulaDict : public QDict<Formula> -{ - public: - FormulaDict(uint size) : - QDict<Formula>(size) {} - ~FormulaDict() {} + FormulaManager(); + struct Private; + std::unique_ptr<Private> p; }; #endif diff --git a/src/htmldocvisitor.cpp b/src/htmldocvisitor.cpp index aff838e..5c17198 100644 --- a/src/htmldocvisitor.cpp +++ b/src/htmldocvisitor.cpp @@ -36,6 +36,7 @@ #include "htmlentity.h" #include "emoji.h" #include "plantuml.h" +#include "formula.h" static const int NUM_HTML_LIST_TYPES = 4; static const char types[][NUM_HTML_LIST_TYPES] = {"1", "a", "i", "A"}; @@ -888,10 +889,25 @@ void HtmlDocVisitor::visit(DocFormula *f) m_t << "\" alt=\""; filterQuotedCdataAttr(f->text()); m_t << "\""; - // TODO: cache image dimensions on formula generation and give height/width - // for faster preloading and better rendering of the page - m_t << " src=\"" << f->relPath() << f->name() << ".png\"/>"; - + m_t << " src=\"" << f->relPath() << f->name(); + if (Config_getEnum(HTML_FORMULA_FORMAT)=="SVG") + { + m_t << ".svg"; + } + else + { + m_t << ".png"; + } + FormulaManager::DisplaySize size = FormulaManager::instance().displaySize(f->id()); + if (size.width!=-1) + { + m_t << "\" width=\"" << size.width; + } + if (size.height!=-1) + { + m_t << "\" height=\"" << size.height; + } + m_t << "\"/>"; } if (bDisplay) { diff --git a/src/portable.cpp b/src/portable.cpp index 403b0ea..8ceb7d6 100644 --- a/src/portable.cpp +++ b/src/portable.cpp @@ -567,3 +567,12 @@ const char *Portable::strnstr(const char *haystack, const char *needle, size_t h } return 0; } + +const char *Portable::devNull() +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + return "NUL"; +#else + return "/dev/null"; +#endif +} diff --git a/src/portable.h b/src/portable.h index 771108e..956ae41 100644 --- a/src/portable.h +++ b/src/portable.h @@ -43,6 +43,7 @@ namespace Portable void correct_path(void); void setShortDir(void); const char * strnstr(const char *haystack, const char *needle, size_t haystack_len); + const char * devNull(); } |