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