1 /*************************************************************************
2 ** SVGOutput.cpp **
3 ** **
4 ** This file is part of dvisvgm -- the DVI to SVG converter **
5 ** Copyright (C) 2005-2015 Martin Gieseking <martin.gieseking@uos.de> **
6 ** **
7 ** This program is free software; you can redistribute it and/or **
8 ** modify it under the terms of the GNU General Public License as **
9 ** published by the Free Software Foundation; either version 3 of **
10 ** the License, or (at your option) any later version. **
11 ** **
12 ** This program is distributed in the hope that it will be useful, but **
13 ** WITHOUT ANY WARRANTY; without even the implied warranty of **
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
15 ** GNU General Public License for more details. **
16 ** **
17 ** You should have received a copy of the GNU General Public License **
18 ** along with this program; if not, see <http://www.gnu.org/licenses/>. **
19 *************************************************************************/
20
21 #include <algorithm>
22 #include <cmath>
23 #include <iomanip>
24 #include <iostream>
25 #include <sstream>
26 #include "gzstream.h"
27 #include "Calculator.h"
28 #include "FileSystem.h"
29 #include "Message.h"
30 #include "SVGOutput.h"
31
32
33 using namespace std;
34
SVGOutput(const char * base,string pattern,int zipLevel)35 SVGOutput::SVGOutput (const char *base, string pattern, int zipLevel)
36 : _path(base ? base : ""),
37 _pattern(pattern),
38 _stdout(base == 0),
39 _zipLevel(zipLevel),
40 _page(-1),
41 _os(0)
42 {
43 }
44
45
46 /** Returns an output stream for the given page.
47 * @param[in] page number of current page
48 * @param[in] numPages total number of pages in the DVI file
49 * @return output stream for the given page */
getPageStream(int page,int numPages) const50 ostream& SVGOutput::getPageStream (int page, int numPages) const {
51 string fname = filename(page, numPages);
52 if (fname.empty()) {
53 delete _os;
54 _os = 0;
55 return cout;
56 }
57 if (page == _page)
58 return *_os;
59
60 _page = page;
61 delete _os;
62 if (_zipLevel > 0)
63 _os = new ogzstream(fname.c_str(), _zipLevel);
64 else
65 _os = new ofstream(fname.c_str());
66 if (!_os || !*_os) {
67 delete _os;
68 _os = 0;
69 throw MessageException("can't open file "+fname+" for writing");
70 }
71 return *_os;
72 }
73
74
75 /** Returns the name of the SVG file containing the given page.
76 * @param[in] page number of current page
77 * @param[in] numPages total number of pages */
filename(int page,int numPages) const78 string SVGOutput::filename (int page, int numPages) const {
79 if (_stdout)
80 return "";
81 string pattern = _pattern;
82 expandFormatString(pattern, page, numPages);
83 // remove leading and trailing whitespace
84 stringstream trim;
85 trim << pattern;
86 pattern.clear();
87 trim >> pattern;
88 // set and expand default pattern if necessary
89 if (pattern.empty()) {
90 pattern = numPages > 1 ? "%f-%p" : "%f";
91 expandFormatString(pattern, page, numPages);
92 }
93 // append suffix if necessary
94 FilePath outpath(pattern, true);
95 if (outpath.suffix().empty())
96 outpath.suffix(_zipLevel > 0 ? "svgz" : "svg");
97 string abspath = outpath.absolute();
98 string relpath = outpath.relative();
99 return abspath.length() < relpath.length() ? abspath : relpath;
100 }
101
102
ilog10(int n)103 static int ilog10 (int n) {
104 int result = 0;
105 while (n >= 10) {
106 result++;
107 n /= 10;
108 }
109 return result;
110 }
111
112
113 /** Replace expressions in a given string by the corresponing values.
114 * Supported constructs:
115 * %f: basename of the current file (filename without suffix)
116 * %[0-9]?p: current page number
117 * %[0-9]?P: number of pages in DVI file
118 * %[0-9]?(expr): arithmetic expression */
expandFormatString(string & str,int page,int numPages) const119 void SVGOutput::expandFormatString (string &str, int page, int numPages) const {
120 string result;
121 while (!str.empty()) {
122 size_t pos = str.find('%');
123 if (pos == string::npos) {
124 result += str;
125 str.clear();
126 }
127 else {
128 result += str.substr(0, pos);
129 str = str.substr(pos);
130 pos = 1;
131 ostringstream oss;
132 if (isdigit(str[pos])) {
133 oss << setw(str[pos]-'0') << setfill('0');
134 pos++;
135 }
136 else {
137 oss << setw(ilog10(numPages)+1) << setfill('0');
138 }
139 switch (str[pos]) {
140 case 'f':
141 result += _path.basename();
142 break;
143 case 'p':
144 case 'P':
145 oss << (str[pos] == 'p' ? page : numPages);
146 result += oss.str();
147 break;
148 case '(': {
149 size_t endpos = str.find(')', pos);
150 if (endpos == string::npos)
151 throw MessageException("missing ')' in filename pattern");
152 else if (endpos-pos-1 > 1) {
153 try {
154 Calculator calculator;
155 calculator.setVariable("p", page);
156 calculator.setVariable("P", numPages);
157 oss << floor(calculator.eval(str.substr(pos, endpos-pos+1)));
158 result += oss.str();
159 }
160 catch (CalculatorException &e) {
161 oss.str("");
162 oss << "error in filename pattern (" << e.what() << ")";
163 throw MessageException(oss.str());
164 }
165 pos = endpos;
166 }
167 break;
168 }
169 }
170 str = str.substr(pos+1);
171 }
172 }
173 str = result;
174 }
175