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