Doxygen
Loading...
Searching...
No Matches
formula.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Copyright (C) 1997-2022 by Dimitri van Heesch.
4 *
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation under the terms of the GNU General Public License is hereby
7 * granted. No representations are made about the suitability of this software
8 * for any purpose. It is provided "as is" without express or implied warranty.
9 * See the GNU General Public License for more details.
10 *
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
13 *
14 */
15
16#include <map>
17#include <vector>
18#include <string>
19#include <utility>
20
21#include "formula.h"
22#include "message.h"
23#include "config.h"
24#include "util.h"
25#include "portable.h"
26#include "image.h"
27#include "fileinfo.h"
28#include "dir.h"
29#include "regex.h"
30#include "linkedmap.h"
31#include "threadpool.h"
32#include "portable.h"
33#include "latexgen.h"
34#include "debug.h"
35
36// TODO: remove these dependencies
37#include "doxygen.h" // for Doxygen::indexList
38#include "indexlist.h" // for Doxygen::indexList
39
40static int determineInkscapeVersion(const Dir &thisDir);
41
43{
44 LinkedMap<Formula> formulas;
45 std::map<int,Formula *> formulaIdMap;
46 bool repositoriesValid = true;
48};
49
51{
52}
53
55{
56 static FormulaManager fm;
57 return fm;
58}
59
61{
62 std::ifstream f = Portable::openInputStream(dir+"/formula.repository");
63 if (f.is_open())
64 {
65 uint32_t formulaCount=0;
66 msg("Reading formula repository...\n");
67 std::string readLine;
68 std::string line;
69 std::string prefix("\\_form#");
70 int nextLineNr=1;
71 bool hasNextLine = !getline(f,readLine).fail();
72 while (hasNextLine)
73 {
74 line = readLine;
75 int lineNr = nextLineNr;
76
77 // look ahead a bit because a formula can be spread over several lines
78 while ((hasNextLine = !getline(f,readLine).fail()))
79 {
80 nextLineNr+=1;
81 if (!readLine.compare(0, prefix.size(), prefix)) break;
82 line += "\n" + readLine;
83 }
84
85 // new format: \_form#<digits>=<digits>x<digits>:formula
86 static const reg::Ex re_new(R"(\\_form#(\d+)=(\d+)x(\d+):)");
87 // old format: \_form#<digits>:formula
88 static const reg::Ex re_old(R"(\\_form#(\d+):)");
89
90 reg::Match match;
91 int id = -1;
92 int width = -1;
93 int height = -1;
94 std::string text;
95 if (reg::search(line,match,re_new)) // try new format first
96 {
97 id = std::stoi(match[1].str());
98 width = std::stoi(match[2].str());
99 height = std::stoi(match[3].str());
100 text = line.substr(match.position()+match.length());
101 //printf("new format found id=%d width=%d height=%d text=%s\n",id,width,height,text.c_str());
102 }
103 else if (reg::search(line,match,re_old)) // check for old format
104 {
105 //id = std::stoi(match[1].str());
106 //text = line.substr(match.position()+match.length());
107 //printf("old format found id=%d text=%s\n",id,text.c_str());
108 msg("old formula.repository format detected; forcing upgrade.\n");
109 p->repositoriesValid = false;
110 break;
111 }
112 else // unexpected content
113 {
114 warn_uncond("%s/formula.repository contains invalid content at line %d: found: '%s'\n",qPrint(dir),lineNr,line.c_str());
115 p->repositoriesValid = false;
116 break;
117 }
118
119 auto it = p->formulaIdMap.find(id);
120 Formula *formula=nullptr;
121 if (it!=p->formulaIdMap.end()) // formula already found in a repository for another output format
122 {
123 formula = it->second;
124 if (formula->text().str()!=text) // inconsistency between repositories detected
125 {
126 msg("differences detected between formula.repository files; forcing upgrade.\n");
127 p->repositoriesValid = false;
128 break;
129 }
130 formulaCount++;
131 }
132 else // create new formula from cache
133 {
134 //printf("formula not found adding it under id=%d\n",id);
135 formula = p->formulas.add(text.c_str(),id,width,height);
136 p->formulaIdMap.emplace(id,formula);
137 }
138
139 if (formula) // if an entry in the repository exists also check if there is a generated image
140 {
141 QCString formImgName;
142 formImgName.sprintf("form_%d",formula->id());
143 FileInfo fiPng((dir+"/"+formImgName+".png").str());
144 FileInfo fiSvg((dir+"/"+formImgName+".svg").str());
145 // mark formula as cached, so we do not need to regenerate the images
146 bool isCached = fiPng.exists() || fiSvg.exists();
147 formula->setCached(isCached);
148 //printf("formula %d: cached=%d\n",formula->id(),isCached);
149
150 FileInfo fiPngDark((dir+"/"+formImgName+"_dark.png").str());
151 FileInfo fiSvgDark((dir+"/"+formImgName+"_dark.svg").str());
152 bool isCachedDark = fiPngDark.exists() || fiSvgDark.exists();
153 formula->setCachedDark(isCachedDark);
154 //printf("formula %d: cachedDark=%d\n",formula->id(),isCachedDark);
155 }
156 }
157
158 // For the first repository all formulas should be new (e.g. formulaCount==0).
159 // For the other repositories the same number of formulas should be found
160 // (and number of formulas should be the same for all repositories, content is already check above)
161 if (formulaCount>0 && formulaCount!=p->formulas.size()) // inconsistency between repositories
162 {
163 msg("differences detected between formula.repository files; forcing upgrade.\n");
164 p->repositoriesValid = false;
165 }
166 }
167 else // no repository found for an output format
168 {
169 p->repositoriesValid = false;
170 }
171}
172
174{
175 //printf("checkRepositories valid=%d\n",p->repositoriesValid);
176 if (!p->repositoriesValid)
177 {
178 clear(); // clear cached formulas, so the corresponding images and repository files
179 // are regenerated
180 p->repositoriesValid = true;
181 }
182}
183
184void FormulaManager::createLatexFile(const QCString &fileName,Format format,Mode mode,IntVector &formulasToGenerate)
185{
186 // generate a latex file containing one formula per page.
187 QCString texName=fileName+".tex";
188 std::ofstream f = Portable::openOutputStream(texName);
189 if (f.is_open())
190 {
191 TextStream t(&f);
192 t << "\\documentclass{article}\n";
193 t << "\\usepackage{iftex}\n";
194 t << "\\usepackage{ifthen}\n";
195 t << "\\usepackage{epsfig}\n"; // for those who want to include images
196 t << "\\usepackage[utf8]{inputenc}\n"; // looks like some older distributions with newunicode package 1.1 need this option.
197 t << "\\usepackage{xcolor}\n";
198
199 if (mode==Mode::Dark) // invert page and text colors
200 {
201 t << "\\color{white}\n";
202 t << "\\pagecolor{black}\n";
203 }
204
207
208 QCString macroFile = Config_getString(FORMULA_MACROFILE);
209 if (!macroFile.isEmpty())
210 {
211 FileInfo fi(macroFile.str());
212 QCString stripMacroFile = fi.fileName();
213 t << "\\input{" << stripMacroFile << "}\n";
214 }
215
216 t << "\\pagestyle{empty}\n";
217 t << "\\begin{document}\n";
218 for (const auto &formula : p->formulas)
219 {
220 int id = formula->id();
221 // only formulas for which no image is cached are generated
222 //printf("check formula %d: cached=%d cachedDark=%d\n",formula->id(),formula->isCached(),formula->isCachedDark());
223 if ((mode==Mode::Light && !formula->isCached()) ||
224 (mode==Mode::Dark && !formula->isCachedDark())
225 )
226 {
227 // we force a pagebreak after each formula
228 t << formula->text() << "\n\\pagebreak\n\n";
229 formulasToGenerate.push_back(id);
230 }
231 QCString resultName;
232 resultName.sprintf("form_%d%s.%s",id, mode==Mode::Light?"":"_dark", format==Format::Vector?"svg":"png");
233 Doxygen::indexList->addImageFile(resultName);
234 }
235 t << "\\end{document}\n";
236 t.flush();
237 f.close();
238 }
239}
240
241static bool createDVIFile(const QCString &fileName)
242{
243 QCString latexCmd = "latex";
244 const size_t argsLen = 4096;
245 char args[argsLen];
246 int rerunCount=1;
247 while (rerunCount<8)
248 {
249 //printf("Running latex...\n");
250 qsnprintf(args,argsLen,"-interaction=batchmode %s >%s",qPrint(fileName),Portable::devNull());
251 if ((Portable::system(latexCmd,args)!=0) || (Portable::system(latexCmd,args)!=0))
252 {
253 err("Problems running latex. Check your installation or look "
254 "for typos in %s.tex and check %s.log!\n",qPrint(fileName),qPrint(fileName));
255 return false;
256 }
257 // check the log file if we need to run latex again to resolve references
258 QCString logFile = fileToString(fileName+".log");
259 if (logFile.isEmpty() ||
260 (logFile.find("Rerun to get cross-references right")==-1 && logFile.find("Rerun LaTeX")==-1))
261 {
262 break;
263 }
264 rerunCount++;
265 }
266 return true;
267}
268
269static bool createPostscriptFile(const QCString &fileName,const QCString &formBase,int pageIndex)
270{
271 const size_t argsLen = 4096;
272 char args[argsLen];
273 // run dvips to convert the page with number pageIndex to an
274 // postscript file.
275 qsnprintf(args,argsLen,"-q -D 600 -n 1 -p %d -o %s_tmp.ps %s.dvi",pageIndex,qPrint(formBase),qPrint(fileName));
276 if (Portable::system("dvips",args)!=0)
277 {
278 err("Problems running dvips. Check your installation!\n");
279 return false;
280 }
281 return true;
282}
283
284static bool createEPSbboxFile(const QCString &formBase)
285{
286 const size_t argsLen = 4096;
287 char args[argsLen];
288 // extract the bounding box for the postscript file
289 qsnprintf(args,argsLen,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=bbox %s_tmp.ps 2>%s_tmp.epsi",
290 qPrint(formBase),qPrint(formBase));
292 {
293 err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand());
294 return false;
295 }
296 return true;
297}
298
299static bool extractBoundingBox(const QCString &formBase,
300 int *x1,int *y1,int *x2,int *y2,
301 double *x1hi,double *y1hi,double *x2hi,double *y2hi)
302{
303 FileInfo fi((formBase+"_tmp.epsi").str());
304 if (fi.exists())
305 {
306 QCString eps = fileToString(formBase+"_tmp.epsi");
307 int i = eps.find("%%BoundingBox:");
308 if (i!=-1)
309 {
310 sscanf(eps.data()+i,"%%%%BoundingBox:%d %d %d %d",x1,y1,x2,y2);
311 }
312 else
313 {
314 err("Couldn't extract bounding box from %s_tmp.epsi\n",qPrint(formBase));
315 return false;
316 }
317 i = eps.find("%%HiResBoundingBox:");
318 if (i!=-1)
319 {
320 sscanf(eps.data()+i,"%%%%HiResBoundingBox:%lf %lf %lf %lf",x1hi,y1hi,x2hi,y2hi);
321 }
322 else
323 {
324 err("Couldn't extract high resolution bounding box from %s_tmp.epsi\n",qPrint(formBase));
325 return false;
326 }
327 }
328 //printf("Bounding box [%d %d %d %d]\n",x1,y1,x2,y2);
329 return true;
330}
331
332static std::mutex g_formulaUpdateMutex;
333
334static double updateFormulaSize(Formula *formula,int x1,int y1,int x2,int y2)
335{
336 double scaleFactor = 1.25;
337 int zoomFactor = Config_getInt(FORMULA_FONTSIZE);
338 if (zoomFactor<8 || zoomFactor>50) zoomFactor=10;
339 scaleFactor *= zoomFactor/10.0;
340
341 if (formula)
342 {
343 std::lock_guard<std::mutex> lock(g_formulaUpdateMutex);
344 formula->setWidth(static_cast<int>((x2-x1)*scaleFactor+0.5));
345 formula->setHeight(static_cast<int>((y2-y1)*scaleFactor+0.5));
346 }
347 return scaleFactor;
348}
349
350static bool createCroppedPDF(const QCString &formBase,int x1,int y1,int x2,int y2)
351{
352 const size_t argsLen = 4096;
353 char args[argsLen];
354 // crop the image to its bounding box
355 qsnprintf(args,argsLen,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=pdfwrite"
356 " -o %s_tmp.pdf -c \"[/CropBox [%d %d %d %d] /PAGES pdfmark\" -f %s_tmp.ps",
357 qPrint(formBase),x1,y1,x2,y2,qPrint(formBase));
359 {
360 err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand());
361 return false;
362 }
363 return true;
364}
365
366static bool createCroppedEPS(const QCString &formBase)
367{
368 const size_t argsLen = 4096;
369 char args[argsLen];
370 // crop the image to its bounding box
371 qsnprintf(args,argsLen,"-q -dBATCH -dNOPAUSE -P- -dNOSAFER -sDEVICE=eps2write"
372 " -o %s_tmp.eps -f %s_tmp.ps",qPrint(formBase),qPrint(formBase));
374 {
375 err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand());
376 return false;
377 }
378 return true;
379}
380
381static bool createSVGFromPDF(const QCString &formBase,const QCString &outFile)
382{
383 const size_t argsLen = 4096;
384 char args[argsLen];
385 qsnprintf(args,argsLen,"%s_tmp.pdf %s",qPrint(formBase),qPrint(outFile));
386 if (Portable::system("pdf2svg",args)!=0)
387 {
388 err("Problems running pdf2svg. Check your installation!\n");
389 return false;
390 }
391 return true;
392}
393
394static bool createSVGFromPDFviaInkscape(const Dir &thisDir,const QCString &formBase,const QCString &outFile)
395{
396 const size_t argsLen = 4096;
397 char args[argsLen];
398 int inkscapeVersion = determineInkscapeVersion(thisDir);
399 if (inkscapeVersion == -1)
400 {
401 err("Problems determining the version of inkscape. Check your installation!\n");
402 return false;
403 }
404 else if (inkscapeVersion == 0)
405 {
406 qsnprintf(args,argsLen,"-l %s -z %s_tmp.pdf 2>%s",qPrint(outFile),qPrint(formBase),Portable::devNull());
407 }
408 else // inkscapeVersion >= 1
409 {
410 qsnprintf(args,argsLen,"--export-type=svg --export-filename=%s %s_tmp.pdf 2>%s",qPrint(outFile),qPrint(formBase),Portable::devNull());
411 }
412 if (Portable::system("inkscape",args)!=0)
413 {
414 err("Problems running inkscape. Check your installation!\n");
415 return false;
416 }
417 return true;
418}
419
420
421static bool updateEPSBoundingBox(const QCString &formBase,
422 int x1,int y1,int x2,int y2,
423 double x1hi,double y1hi,double x2hi,double y2hi)
424{
425 // read back %s_tmp.eps and replace
426 // bounding box values with x1,y1,x2,y2 and remove the HiResBoundingBox
427 std::ifstream epsIn = Portable::openInputStream(formBase+"_tmp.eps");
428 std::ofstream epsOut = Portable::openOutputStream(formBase+"_tmp_corr.eps");
429 if (epsIn.is_open() && epsOut.is_open())
430 {
431 std::string line;
432 while (getline(epsIn,line))
433 {
434 if (line.rfind("%%BoundingBox",0)==0)
435 {
436 epsOut << "%%BoundingBox: " << std::max(0,x1-1) << " " << std::max(0,y1-1) << " " << (x2+1) << " " << (y2+1) << "\n";
437 }
438 else if (line.rfind("%%HiResBoundingBox",0)==0)
439 {
440 epsOut << "%%HiResBoundingBox: " << std::max(0.0,x1hi-1.0) << " " << std::max(0.0,y1hi-1.0) << " " << (x2hi+1.0) << " " << (y2hi+1.0) << "\n";
441 }
442 else
443 {
444 epsOut << line << "\n";
445 }
446 }
447 epsIn.close();
448 epsOut.close();
449 }
450 else
451 {
452 err("Problems correcting the eps files from %s_tmp.eps to %s_tmp_corr.eps\n",
453 qPrint(formBase),qPrint(formBase));
454 return false;
455 }
456 return true;
457}
458
459static bool createPNG(const QCString &formBase,const QCString &outFile,double scaleFactor)
460{
461 const size_t argsLen = 4096;
462 char args[argsLen];
463 qsnprintf(args,argsLen,"-q -dNOSAFER -dBATCH -dNOPAUSE -dEPSCrop -sDEVICE=pngalpha -dGraphicsAlphaBits=4 -dTextAlphaBits=4 "
464 "-r%d -sOutputFile=%s %s_tmp_corr.eps",static_cast<int>(scaleFactor*72),qPrint(outFile),qPrint(formBase));
466 {
467 err("Problems running %s. Check your installation!\n",Portable::ghostScriptCommand());
468 return false;
469 }
470 return true;
471}
472
473static StringVector generateFormula(const Dir &thisDir,const QCString &formulaFileName,Formula *formula,int pageNum,int pageIndex,
475{
476 StringVector tempFiles;
477 QCString outputFile;
478 outputFile.sprintf("form_%d%s.%s",pageNum, mode==FormulaManager::Mode::Light?"":"_dark", format==FormulaManager::Format::Vector?"svg":"png");
479 msg("Generating image %s for formula\n",qPrint(outputFile));
480
481 QCString formBase;
482 formBase.sprintf("_form%d%s",pageNum,mode==FormulaManager::Mode::Light?"":"_dark");
483
484 if (!createPostscriptFile(formulaFileName,formBase,pageIndex)) return tempFiles;
485
486 int x1=0,y1=0,x2=0,y2=0;
487 double x1hi=0.0,y1hi=0.0,x2hi=0.0,y2hi=0.0;
489 {
490 if (!createEPSbboxFile(formBase)) return tempFiles;
491 // extract the bounding box info from the generated .epsi file
492 if (!extractBoundingBox(formBase,&x1,&y1,&x2,&y2,&x1hi,&y1hi,&x2hi,&y2hi)) return tempFiles;
493 }
494 else // for dark images the bounding box is wrong (includes the black) so
495 // use the bounding box of the light image instead.
496 {
497 QCString formBaseLight;
498 formBaseLight.sprintf("_form%d",pageNum);
499 if (!extractBoundingBox(formBaseLight,&x1,&y1,&x2,&y2,&x1hi,&y1hi,&x2hi,&y2hi)) return tempFiles;
500 }
501
502 // convert the corrected EPS to a bitmap
503 double scaleFactor = updateFormulaSize(formula,x1,y1,x2,y2);
504
506 {
507 if (!createCroppedPDF(formBase,x1,y1,x2,y2)) return tempFiles;
508
509 // if we have pdf2svg available use it to create a SVG image
510 if (Portable::checkForExecutable("pdf2svg"))
511 {
512 createSVGFromPDF(formBase,outputFile);
513 }
514 else if (Portable::checkForExecutable("inkscape")) // alternative is to use inkscape
515 {
516 createSVGFromPDFviaInkscape(thisDir,formBase,outputFile);
517 }
518 else
519 {
520 err("Neither 'pdf2svg' nor 'inkscape' present for conversion of formula to 'svg'\n");
521 return tempFiles;
522 }
523
524 tempFiles.push_back(formBase.str()+"_tmp.pdf");
525 }
526 else // format==FormulaManager::Format::Bitmap
527 {
528 if (!createCroppedEPS(formBase)) return tempFiles;
529
530 if (!updateEPSBoundingBox(formBase,x1,y1,x2,y2,x1hi,y1hi,x2hi,y2hi)) return tempFiles;
531
532 if (hd==FormulaManager::HighDPI::On) // for high DPI display it looks much better if the
533 // image resolution is higher than the display resolution
534 {
535 scaleFactor*=2;
536 }
537
538 if (!createPNG(formBase,outputFile,scaleFactor)) return tempFiles;
539
540 tempFiles.push_back(formBase.str()+"_tmp.eps");
541 tempFiles.push_back(formBase.str()+"_tmp_corr.eps");
542 }
543
544 // remove intermediate image files
545 tempFiles.push_back(formBase.str()+"_tmp.ps");
547 {
548 tempFiles.push_back(formBase.str()+"_tmp.epsi");
549 }
550 return tempFiles;
551}
552
554{
555 IntVector formulasToGenerate;
556 QCString formulaFileName = mode==Mode::Light ? "_formulas" : "_formulas_dark";
557 createLatexFile(formulaFileName,format,mode,formulasToGenerate);
558
559 if (!formulasToGenerate.empty()) // there are new formulas
560 {
561 if (!createDVIFile(formulaFileName)) return;
562
563 auto getFormula = [this](int pageNum) -> Formula *
564 {
565 auto it = p->formulaIdMap.find(pageNum);
566 if (it!=p->formulaIdMap.end())
567 {
568 return it->second;
569 }
570 return nullptr;
571 };
572
573 int pageIndex=1;
574 std::size_t numThreads = static_cast<std::size_t>(Config_getInt(NUM_PROC_THREADS));
575 if (numThreads>1) // multi-threaded version
576 {
577 ThreadPool threadPool(numThreads);
578 std::vector< std::future< StringVector > > results;
579 for (int pageNum : formulasToGenerate)
580 {
581 // create images for each formula.
582 auto formula = getFormula(pageNum);
583 auto processFormula = [=]() -> StringVector
584 {
585 return generateFormula(thisDir,formulaFileName,formula,pageNum,pageIndex,format,hd,mode);
586 };
587 results.emplace_back(threadPool.queue(processFormula));
588 pageIndex++;
589 }
590 for (auto &f : results)
591 {
592 auto tf = f.get();
593 p->tempFiles.insert(p->tempFiles.end(),tf.begin(),tf.end()); // append tf to p->tempFiles
594 }
595 }
596 else // single threaded version
597 {
598 for (int pageNum : formulasToGenerate)
599 {
600 // create images for each formula.
601 auto formula = getFormula(pageNum);
602 StringVector tf = generateFormula(thisDir,formulaFileName,formula,pageNum,pageIndex,format,hd,mode);
603 p->tempFiles.insert(p->tempFiles.end(),tf.begin(),tf.end()); // append tf to p->tempFiles
604
605 pageIndex++;
606 }
607 }
608 // remove intermediate files produced by latex
609 p->tempFiles.push_back(formulaFileName.str()+".dvi");
610 p->tempFiles.push_back(formulaFileName.str()+".log");
611 p->tempFiles.push_back(formulaFileName.str()+".aux");
612 }
613 // remove the latex file itself
614 p->tempFiles.push_back(formulaFileName.str()+".tex");
615
616 // write/update the formula repository so we know what text the
617 // generated images represent (we use this next time to avoid regeneration
618 // of the images, and to avoid forcing the user to delete all images in order
619 // to let a browser refresh the images).
620 std::ofstream f = Portable::openOutputStream("formula.repository");
621 if (f.is_open())
622 {
623 TextStream t(&f);
624 for (const auto &formula : p->formulas)
625 {
626 t << "\\_form#" << formula->id();
627 if (formula->width()!=-1 && formula->height()!=-1)
628 {
629 t << "=" << formula->width() << "x" << formula->height();
630 }
631 t << ":" << formula->text() << "\n";
632 }
633 }
634}
635
637{
638 Dir d(path.str());
639 // store the original directory
640 if (!d.exists())
641 {
642 term("Output directory '%s' does not exist!\n",qPrint(path));
643 }
644 std::string oldDir = Dir::currentDirPath();
645
646 QCString macroFile = Config_getString(FORMULA_MACROFILE);
647 QCString stripMacroFile;
648 if (!macroFile.isEmpty())
649 {
650 FileInfo fi(macroFile.str());
651 macroFile=fi.absFilePath();
652 stripMacroFile = fi.fileName();
653 }
654
655 // go to the html output directory (i.e. path)
657 Dir thisDir;
658
659 if (!macroFile.isEmpty())
660 {
661 copyFile(macroFile,stripMacroFile);
662 }
663
664 createFormulasTexFile(thisDir,format,hd,Mode::Light);
665 if (Config_getEnum(HTML_COLORSTYLE)!=HTML_COLORSTYLE_t::LIGHT) // all modes other than light need a dark version
666 {
667 // note that the dark version reuses the bounding box of the light version so it needs to be
668 // created after the light version.
669 createFormulasTexFile(thisDir,format,hd,Mode::Dark);
670 }
671
672 // clean up temporary files
674 {
675 for (const auto &file : p->tempFiles)
676 {
677 thisDir.remove(file);
678 }
679 }
680
681 // reset the directory to the original location.
682 Dir::setCurrent(oldDir);
683}
684
686{
687 p->formulas.clear();
688 p->formulaIdMap.clear();
689}
690
691int FormulaManager::addFormula(const std::string &formulaText,int width,int height)
692{
693 Formula *formula = p->formulas.find(formulaText);
694 if (formula) // same formula already stored
695 {
696 return formula->id();
697 }
698 // add new formula
699 int id = static_cast<int>(p->formulas.size());
700 formula = p->formulas.add(formulaText.c_str(),id,width,height);
701 p->formulaIdMap.emplace(id,formula);
702 return id;
703}
704
705const Formula *FormulaManager::findFormula(int formulaId) const
706{
707 auto it = p->formulaIdMap.find(formulaId);
708 return it != p->formulaIdMap.end() ? it->second : nullptr;
709}
710
711#if 0
713{
714 auto it = p->formulaIdMap.find(formulaId);
715 return it != p->formulaIdMap.end() ? it->second : nullptr;
716}
717#endif
718
719
721{
722 return !p->formulas.empty();
723}
724
725static std::mutex g_inkscapeDetectionMutex;
726
727// helper function to detect and return the major version of inkscape.
728// return -1 if the version cannot be determined.
729static int determineInkscapeVersion(const Dir &thisDir)
730{
731 std::lock_guard<std::mutex> lock(g_inkscapeDetectionMutex);
732 // The command line interface (CLI) of Inkscape 1.0 has changed in comparison to
733 // previous versions. In order to invokine Inkscape, the used version is detected
734 // and based on the version the right syntax of the CLI is chosen.
735 static int inkscapeVersion = -2;
736 if (inkscapeVersion == -2) // initial one time version check
737 {
738 QCString inkscapeVersionFile = "inkscape_version" ;
739 inkscapeVersion = -1;
740 QCString args = "-z --version >"+inkscapeVersionFile+" 2>"+Portable::devNull();
741 if (Portable::system("inkscape",args)!=0)
742 {
743 // looks like the old syntax gave problems, lets try the new syntax
744 args = " --version >"+inkscapeVersionFile+" 2>"+Portable::devNull();
745 if (Portable::system("inkscape",args)!=0)
746 {
747 return -1;
748 }
749 }
750 // read version file and determine major version
751 std::ifstream inkscapeVersionIn = Portable::openInputStream(inkscapeVersionFile);
752 if (inkscapeVersionIn.is_open())
753 {
754 std::string line;
755 while (getline(inkscapeVersionIn,line))
756 {
757 size_t dotPos = line.find('.');
758 if (line.rfind("Inkscape ",0)==0 && dotPos>0)
759 {
760 // get major version
761 inkscapeVersion = std::stoi(line.substr(9,dotPos-9));
762 break;
763 }
764 }
765 inkscapeVersionIn.close();
766 }
767 else // failed to open version file
768 {
769 return -1;
770 }
772 {
773 thisDir.remove(inkscapeVersionFile.str());
774 }
775 }
776 return inkscapeVersion;
777}
constexpr auto prefix
Definition anchor.cpp:44
@ Formula
Definition debug.h:32
static bool isFlagSet(const DebugMask mask)
Definition debug.cpp:135
Class representing a directory in the file system.
Definition dir.h:75
static std::string currentDirPath()
Definition dir.cpp:340
std::string absPath() const
Definition dir.cpp:363
bool remove(const std::string &path, bool acceptsAbsPath=true) const
Definition dir.cpp:314
static bool setCurrent(const std::string &path)
Definition dir.cpp:348
bool exists() const
Definition dir.cpp:257
static IndexList * indexList
Definition doxygen.h:134
Minimal replacement for QFileInfo.
Definition fileinfo.h:23
bool exists() const
Definition fileinfo.cpp:30
std::string fileName() const
Definition fileinfo.cpp:118
std::string absFilePath() const
Definition fileinfo.cpp:101
Class representing a LaTeX formula as found in the documentation.
Definition formula.h:29
void setCachedDark(bool cached)
Definition formula.h:46
QCString text() const
Definition formula.h:37
void setHeight(int height)
Definition formula.h:41
void setCached(bool cached)
Definition formula.h:45
void setWidth(int width)
Definition formula.h:40
int id() const
Definition formula.h:36
void createLatexFile(const QCString &fileName, Format format, Mode mode, IntVector &formulasToGenerate)
Definition formula.cpp:184
void initFromRepository(const QCString &dir)
Definition formula.cpp:60
int addFormula(const std::string &formulaText, int width=-1, int height=-1)
Definition formula.cpp:691
bool hasFormulas() const
Definition formula.cpp:720
void checkRepositories()
Definition formula.cpp:173
const Formula * findFormula(int formulaId) const
Definition formula.cpp:705
void createFormulasTexFile(Dir &d, Format format, HighDPI hd, Mode mode)
Definition formula.cpp:553
static FormulaManager & instance()
Definition formula.cpp:54
std::unique_ptr< Private > p
Definition formula.h:90
void generateImages(const QCString &outputDir, Format format, HighDPI hd=HighDPI::Off)
Definition formula.cpp:636
void addImageFile(const QCString &name)
Definition indexlist.h:126
This is an alternative implementation of QCString.
Definition qcstring.h:101
int find(char c, int index=0, bool cs=TRUE) const
Definition qcstring.cpp:43
bool isEmpty() const
Returns TRUE iff the string is empty.
Definition qcstring.h:150
const std::string & str() const
Definition qcstring.h:526
QCString & sprintf(const char *format,...)
Definition qcstring.cpp:29
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
Definition qcstring.h:159
void flush()
Flushes the buffer.
Definition textstream.h:209
auto queue(F &&f, Args &&... args) -> std::future< decltype(f(args...))>
Queue the callable function f for the threads to execute.
Definition threadpool.h:77
#define Config_getInt(name)
Definition config.h:34
#define Config_getString(name)
Definition config.h:32
#define Config_getEnum(name)
Definition config.h:35
std::vector< int > IntVector
Definition containers.h:38
std::vector< std::string > StringVector
Definition containers.h:33
static bool createSVGFromPDFviaInkscape(const Dir &thisDir, const QCString &formBase, const QCString &outFile)
Definition formula.cpp:394
static bool createPNG(const QCString &formBase, const QCString &outFile, double scaleFactor)
Definition formula.cpp:459
static int determineInkscapeVersion(const Dir &thisDir)
Definition formula.cpp:729
static std::mutex g_formulaUpdateMutex
Definition formula.cpp:332
static bool createCroppedEPS(const QCString &formBase)
Definition formula.cpp:366
static std::mutex g_inkscapeDetectionMutex
Definition formula.cpp:725
static bool createPostscriptFile(const QCString &fileName, const QCString &formBase, int pageIndex)
Definition formula.cpp:269
static double updateFormulaSize(Formula *formula, int x1, int y1, int x2, int y2)
Definition formula.cpp:334
static StringVector generateFormula(const Dir &thisDir, const QCString &formulaFileName, Formula *formula, int pageNum, int pageIndex, FormulaManager::Format format, FormulaManager::HighDPI hd, FormulaManager::Mode mode)
Definition formula.cpp:473
static bool createDVIFile(const QCString &fileName)
Definition formula.cpp:241
static bool createCroppedPDF(const QCString &formBase, int x1, int y1, int x2, int y2)
Definition formula.cpp:350
static bool createEPSbboxFile(const QCString &formBase)
Definition formula.cpp:284
static bool extractBoundingBox(const QCString &formBase, int *x1, int *y1, int *x2, int *y2, double *x1hi, double *y1hi, double *x2hi, double *y2hi)
Definition formula.cpp:299
static bool createSVGFromPDF(const QCString &formBase, const QCString &outFile)
Definition formula.cpp:381
static bool updateEPSBoundingBox(const QCString &formBase, int x1, int y1, int x2, int y2, double x1hi, double y1hi, double x2hi, double y2hi)
Definition formula.cpp:421
void writeExtraLatexPackages(TextStream &t)
void writeLatexSpecialFormulaChars(TextStream &t)
void msg(const char *fmt,...)
Definition message.cpp:98
#define warn_uncond(fmt,...)
Definition message.h:79
#define err(fmt,...)
Definition message.h:84
#define term(fmt,...)
Definition message.h:94
std::ifstream openInputStream(const QCString &name, bool binary=false, bool openAtEnd=false)
Definition portable.cpp:676
std::ofstream openOutputStream(const QCString &name, bool append=false)
Definition portable.cpp:665
bool checkForExecutable(const QCString &fileName)
Definition portable.cpp:440
const char * ghostScriptCommand()
Definition portable.cpp:454
int system(const QCString &command, const QCString &args, bool commandHasConsole=true)
Definition portable.cpp:106
const char * devNull()
Definition portable.cpp:631
bool search(std::string_view str, Match &match, const Ex &re, size_t pos)
Search in a given string str starting at position pos for a match against regular expression re.
Definition regex.cpp:748
Portable versions of functions that are platform dependent.
#define qsnprintf
Definition qcstring.h:49
const char * qPrint(const char *s)
Definition qcstring.h:661
LinkedMap< Formula > formulas
Definition formula.cpp:44
std::map< int, Formula * > formulaIdMap
Definition formula.cpp:45
StringVector tempFiles
Definition formula.cpp:47
QCString fileToString(const QCString &name, bool filter, bool isSourceCode)
Definition util.cpp:1414
bool copyFile(const QCString &src, const QCString &dest)
Copies the contents of file with name src to the newly created file with name dest.
Definition util.cpp:6169
A bunch of utility functions.