summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/commentscan.l21
-rw-r--r--src/config.xml11
-rw-r--r--src/docparser.cpp12
-rw-r--r--src/doxygen.cpp136
-rw-r--r--src/doxygen.h3
-rw-r--r--src/formula.cpp483
-rw-r--r--src/formula.h58
-rw-r--r--src/htmldocvisitor.cpp24
-rw-r--r--src/portable.cpp9
-rw-r--r--src/portable.h1
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();
}