1 /*************************************************************************
2 ** dvisvgm.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 <config.h>
22 #include <clipper.hpp>
23 #include <fstream>
24 #include <iostream>
25 #include <sstream>
26 #include <string>
27 #include "gzstream.h"
28 #include "CommandLine.h"
29 #include "DVIToSVG.h"
30 #include "DVIToSVGActions.h"
31 #include "EPSToSVG.h"
32 #include "FileFinder.h"
33 #include "FilePath.h"
34 #include "FileSystem.h"
35 #include "Font.h"
36 #include "FontCache.h"
37 #include "FontEngine.h"
38 #include "FontMap.h"
39 #include "Ghostscript.h"
40 #include "HtmlSpecialHandler.h"
41 #include "InputReader.h"
42 #include "Message.h"
43 #include "PageSize.h"
44 #include "PSInterpreter.h"
45 #include "PsSpecialHandler.h"
46 #include "SignalHandler.h"
47 #include "SpecialManager.h"
48 #include "SVGOutput.h"
49 #include "System.h"
50 
51 #ifdef __MSVC__
52 #include <potracelib.h>
53 #else
54 extern "C" {
55 #include <potracelib.h>
56 }
57 #endif
58 
59 using namespace std;
60 
61 
62 ////////////////////////////////////////////////////////////////////////////////
63 
show_help(const CommandLine & cmd)64 static void show_help (const CommandLine &cmd) {
65 	cout << PACKAGE_STRING "\n\n";
66 	cmd.help(cmd.help_arg());
67 	cout << "\nCopyright (C) 2005-2015 Martin Gieseking <martin.gieseking@uos.de> \n\n";
68 }
69 
70 
remove_path(string fname)71 static string remove_path (string fname) {
72 	fname = FileSystem::adaptPathSeperators(fname);
73 	size_t slashpos = fname.rfind('/');
74 	if (slashpos == string::npos)
75 		return fname;
76 	return fname.substr(slashpos+1);
77 }
78 
79 
ensure_suffix(string fname,bool eps)80 static string ensure_suffix (string fname, bool eps) {
81 	size_t dotpos = remove_path(fname).rfind('.');
82 	if (dotpos == string::npos)
83 		fname += (eps ? ".eps" : ".dvi");
84 	return fname;
85 }
86 
87 
get_transformation_string(const CommandLine & args)88 static string get_transformation_string (const CommandLine &args) {
89 	ostringstream oss;
90 	if (args.rotate_given())
91 		oss << 'R' << args.rotate_arg() << ",w/2,h/2";
92 	if (args.translate_given())
93 		oss << 'T' << args.translate_arg();
94 	if (args.scale_given())
95 		oss << 'S' << args.scale_arg();
96 	if (args.transform_given())
97 		oss << args.transform_arg();
98 	return oss.str();
99 }
100 
101 
set_libgs(CommandLine & args)102 static void set_libgs (CommandLine &args) {
103 #if !defined(DISABLE_GS) && !defined(HAVE_LIBGS)
104 	if (args.libgs_given())
105 		Ghostscript::LIBGS_NAME = args.libgs_arg();
106 	else if (getenv("LIBGS"))
107 		Ghostscript::LIBGS_NAME = getenv("LIBGS");
108 #endif
109 }
110 
111 
set_cache_dir(const CommandLine & args)112 static bool set_cache_dir (const CommandLine &args) {
113 	if (args.cache_given() && !args.cache_arg().empty()) {
114 		if (args.cache_arg() == "none")
115 			PhysicalFont::CACHE_PATH = 0;
116 		else if (FileSystem::exists(args.cache_arg().c_str()))
117 			PhysicalFont::CACHE_PATH = args.cache_arg().c_str();
118 		else
119 			Message::wstream(true) << "cache directory '" << args.cache_arg() << "' does not exist (caching disabled)\n";
120 	}
121 	else if (const char *userdir = FileSystem::userdir()) {
122 		static string cachepath = userdir + string("/.dvisvgm/cache");
123 		if (!FileSystem::exists(cachepath.c_str()))
124 			FileSystem::mkdir(cachepath.c_str());
125 		PhysicalFont::CACHE_PATH = cachepath.c_str();
126 	}
127 	if (args.cache_given() && args.cache_arg().empty()) {
128 		cout << "cache directory: " << (PhysicalFont::CACHE_PATH ? PhysicalFont::CACHE_PATH : "(none)") << '\n';
129 		FontCache::fontinfo(PhysicalFont::CACHE_PATH, cout, true);
130 		return false;
131 	}
132 	return true;
133 }
134 
135 
check_bbox(const string & bboxstr)136 static bool check_bbox (const string &bboxstr) {
137 	const char *formats[] = {"none", "min", "dvi", 0};
138 	for (const char **p=formats; *p; ++p)
139 		if (bboxstr == *p)
140 			return true;
141 	if (isalpha(bboxstr[0])) {
142 		try {
143 			PageSize size(bboxstr);
144 			return true;
145 		}
146 		catch (const PageSizeException &e) {
147 			Message::estream(true) << "invalid bounding box format '" << bboxstr << "'\n";
148 			return false;
149 		}
150 	}
151 	try {
152 		BoundingBox bbox;
153 		bbox.set(bboxstr);
154 		return true;
155 	}
156 	catch (const MessageException &e) {
157 		Message::estream(true) << e.what() << '\n';
158 		return false;
159 	}
160 }
161 
162 
print_version(bool extended)163 static void print_version (bool extended) {
164 	ostringstream oss;
165 	oss << PACKAGE_STRING;
166 	if (extended) {
167 		if (strlen(TARGET_SYSTEM) > 0)
168 			oss << " (" TARGET_SYSTEM ")";
169 		int len = oss.str().length();
170 		oss << "\n" << string(len, '-') << "\n"
171 			"clipper:     " << CLIPPER_VERSION "\n"
172 			"freetype:    " << FontEngine::version() << "\n";
173 
174 		Ghostscript gs;
175 		string gsver = gs.revision(true);
176 		if (!gsver.empty())
177 			oss << "Ghostscript: " << gsver + "\n";
178 		oss <<
179 #ifdef MIKTEX
180 			"MiKTeX:      " << FileFinder::version() << "\n"
181 #else
182 			"kpathsea:    " << FileFinder::version() << "\n"
183 #endif
184 			"potrace:     " << (strchr(potrace_version(), ' ') ? strchr(potrace_version(), ' ')+1 : "unknown") << "\n"
185 			"zlib:        " << zlibVersion();
186 	}
187 	cout << oss.str() << endl;
188 }
189 
190 
init_fontmap(const CommandLine & args)191 static void init_fontmap (const CommandLine &args) {
192 	const char *mapseq = args.fontmap_given() ? args.fontmap_arg().c_str() : 0;
193 	bool additional = mapseq && strchr("+-=", *mapseq);
194 	if (!mapseq || additional) {
195 		const char *mapfiles[] = {"ps2pk.map", "dvipdfm.map", "psfonts.map", 0};
196 		bool found = false;
197 		for (const char **p=mapfiles; *p && !found; p++)
198 			found = FontMap::instance().read(*p);
199 		if (!found)
200 			Message::wstream(true) << "none of the default map files could be found";
201 	}
202 	if (mapseq)
203 		FontMap::instance().read(mapseq);
204 }
205 
206 
main(int argc,char * argv[])207 int main (int argc, char *argv[]) {
208 	CommandLine args;
209 	args.parse(argc, argv);
210 	if (args.error())
211 		return 1;
212 
213 	Message::COLORIZE = args.color_given();
214 
215 	try {
216 		FileFinder::init(argv[0], "dvisvgm", !args.no_mktexmf_given());
217 	}
218 	catch (MessageException &e) {
219 		Message::estream(true) << e.what() << '\n';
220 		return 0;
221 	}
222 
223 	set_libgs(args);
224 	if (args.version_given()) {
225 		print_version(args.version_arg());
226 		return 0;
227 	}
228 	if (args.list_specials_given()) {
229 		DVIToSVG::setProcessSpecials();
230 		SpecialManager::instance().writeHandlerInfo(cout);
231 		return 0;
232 	}
233 
234 	if (!set_cache_dir(args))
235 		return 0;
236 
237 	if (argc == 1 || args.help_given()) {
238 		show_help(args);
239 		return 0;
240 	}
241 
242 	if (argc > 1 && args.numFiles() < 1) {
243 		Message::estream(true) << "no input file given\n";
244 		return 1;
245 	}
246 
247 	if (args.stdout_given() && args.zip_given()) {
248 		Message::estream(true) << "writing SVGZ files to stdout is not supported\n";
249 		return 1;
250 	}
251 
252 	if (!check_bbox(args.bbox_arg()))
253 		return 1;
254 
255 	if (args.progress_given()) {
256 		DVIReader::COMPUTE_PROGRESS = args.progress_given();
257 		SpecialActions::PROGRESSBAR_DELAY = args.progress_arg();
258 	}
259 	SVGTree::CREATE_STYLE = !args.no_styles_given();
260 	SVGTree::USE_FONTS = !args.no_fonts_given();
261 	SVGTree::CREATE_USE_ELEMENTS = args.no_fonts_arg() < 1;
262 	SVGTree::ZOOM_FACTOR = args.zoom_arg();
263 	SVGTree::RELATIVE_PATH_CMDS = args.relative_given();
264 	SVGTree::MERGE_CHARS = !args.no_merge_given();
265 	DVIToSVG::TRACE_MODE = args.trace_all_given() ? (args.trace_all_arg() ? 'a' : 'm') : 0;
266 	Message::LEVEL = args.verbosity_arg();
267 	PhysicalFont::EXACT_BBOX = args.exact_given();
268 	PhysicalFont::KEEP_TEMP_FILES = args.keep_given();
269 	PhysicalFont::METAFONT_MAG = max(1.0, args.mag_arg());
270 	XMLString::DECIMAL_PLACES = max(0, min(6, args.precision_arg()));
271 	if (!HtmlSpecialHandler::setLinkMarker(args.linkmark_arg()))
272 		Message::wstream(true) << "invalid argument '"+args.linkmark_arg()+"' supplied for option --linkmark\n";
273 	double start_time = System::time();
274 	bool eps_given=false;
275 #ifndef DISABLE_GS
276 	eps_given = args.eps_given();
277 	PsSpecialHandler::COMPUTE_CLIPPATHS_INTERSECTIONS = args.clipjoin_given();
278 	PsSpecialHandler::SHADING_SEGMENT_OVERLAP = args.grad_overlap_given();
279 	PsSpecialHandler::SHADING_SEGMENT_SIZE = max(1, args.grad_segments_arg());
280 	PsSpecialHandler::SHADING_SIMPLIFY_DELTA = args.grad_simplify_arg();
281 #endif
282 	string inputfile = ensure_suffix(args.file(0), eps_given);
283 	ifstream ifs(inputfile.c_str(), ios::binary|ios::in);
284 	if (!ifs) {
285 		Message::estream(true) << "can't open file '" << inputfile << "' for reading\n";
286 		return 0;
287 	}
288 	try {
289 		SVGOutput out(args.stdout_given() ? 0 : inputfile.c_str(), args.output_arg(), args.zip_given() ? args.zip_arg() : 0);
290 		SignalHandler::instance().start();
291 #ifndef DISABLE_GS
292 		if (args.eps_given()) {
293 			EPSToSVG eps2svg(inputfile, out);
294 			eps2svg.convert();
295 			Message::mstream().indent(0);
296 			Message::mstream(false, Message::MC_PAGE_NUMBER)
297 				<< "file converted in " << (System::time()-start_time) << " seconds\n";
298 		}
299 		else
300 #endif
301 		{
302 			init_fontmap(args);
303 			DVIToSVG dvi2svg(ifs, out);
304 			const char *ignore_specials = args.no_specials_given() ? (args.no_specials_arg().empty() ? "*" : args.no_specials_arg().c_str()) : 0;
305 			dvi2svg.setProcessSpecials(ignore_specials, true);
306 			dvi2svg.setPageTransformation(get_transformation_string(args));
307 			dvi2svg.setPageSize(args.bbox_arg());
308 
309 			pair<int,int> pageinfo;
310 			dvi2svg.convert(args.page_arg(), &pageinfo);
311 			Message::mstream().indent(0);
312 			Message::mstream(false, Message::MC_PAGE_NUMBER) << "\n" << pageinfo.first << " of " << pageinfo.second << " page";
313 			if (pageinfo.second > 1)
314 				Message::mstream(false, Message::MC_PAGE_NUMBER) << 's';
315 			Message::mstream(false, Message::MC_PAGE_NUMBER) << " converted in " << (System::time()-start_time) << " seconds\n";
316 		}
317 	}
318 	catch (DVIException &e) {
319 		Message::estream() << "\nDVI error: " << e.what() << '\n';
320 	}
321 	catch (PSException &e) {
322 		Message::estream() << "\nPostScript error: " << e.what() << '\n';
323 	}
324 	catch (SignalException &e) {
325 		Message::wstream().clearline();
326 		Message::wstream(true) << "execution interrupted by user\n";
327 	}
328 	catch (MessageException &e) {
329 		Message::estream(true) << e.what() << '\n';
330 	}
331 	FileFinder::finish();
332 	return 0;
333 }
334 
335