1 /*************************************************************************
2 ** FileFinder.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 
23 #ifdef MIKTEX
24 	#include "MessageException.h"
25 	#include "MiKTeXCom.h"
26 	static MiKTeXCom *miktex=0;
27 #else
28 	#ifdef KPSE_CXX_UNSAFE
29 	extern "C" {
30 	#endif
31 	#include <kpathsea/kpathsea.h>
32 	#ifdef KPSE_CXX_UNSAFE
33 	}
34 	#endif
35 #endif
36 
37 #include <sys/time.h>
38 #include <cstdlib>
39 #include <fstream>
40 #include <map>
41 #include <string>
42 #include "FileFinder.h"
43 #include "FileSystem.h"
44 #include "FontMap.h"
45 #include "Message.h"
46 #include "Process.h"
47 
48 // ---------------------------------------------------
49 
50 static bool _initialized = false;
51 static bool _mktex_enabled = false;
52 
53 // ---------------------------------------------------
54 
55 static const char* find_file (const std::string &fname, const char *ftype);
56 static const char* find_mapped_file (std::string fname);
57 static const char* mktex (const std::string &fname);
58 
59 
60 /** Initializes the file finder. This function must be called before any other
61  *  FileFinder function.
62  *  @param[in] argv0 argv[0] of main() function
63  *  @param[in] progname name of application using the FileFinder
64  *  @param[in] enable_mktexmf if true, tfm and mf file generation is activated */
init(const char * argv0,const char * progname,bool enable_mktexmf)65 void FileFinder::init (const char *argv0, const char *progname, bool enable_mktexmf) {
66 	if (_initialized)
67 		return;
68 
69 	_mktex_enabled = enable_mktexmf;
70 #ifdef MIKTEX
71 	miktex = new MiKTeXCom;
72 #else
73 	kpse_set_program_name(argv0, progname);
74 	// enable tfm and mf generation (actually invoked by calls of kpse_make_tex)
75 	kpse_set_program_enabled(kpse_tfm_format, 1, kpse_src_env);
76 	kpse_set_program_enabled(kpse_mf_format, 1, kpse_src_env);
77 	kpse_make_tex_discard_errors = true;  // suppress messages from mktexFOO tools
78 #ifdef TEXLIVEWIN32
79 	texlive_gs_init();
80 #endif
81 #endif
82 	_initialized = true;
83 }
84 
85 
86 /** Cleans up the FileFinder. This function must be called before leaving the
87  *  application's main() function. */
finish()88 void FileFinder::finish () {
89 #ifdef MIKTEX
90 	if (miktex) {
91 		delete miktex;
92 		miktex = 0;
93 	}
94 #endif
95 	_initialized = false;
96 }
97 
98 
99 /** Returns the version string of the underlying file searching library (kpathsea, MiKTeX) */
version()100 std::string FileFinder::version () {
101 #ifdef MIKTEX
102 	bool autoinit=false;
103 	try {
104 		if (!_initialized) {
105 			init("", "", false);
106 			autoinit = true;
107 		}
108 		std::string ret = miktex->getVersion();
109 		if (autoinit)
110 			finish();
111 		return ret;
112 	}
113 	catch (MessageException &e) {
114 		if (autoinit)
115 			finish();
116 	}
117 #else
118 	if (const char *v = strrchr(KPSEVERSION, ' '))
119 		return v+1;
120 #endif
121 	return "unknown";
122 }
123 
124 
125 /** Determines filetype by the filename extension and calls kpse_find_file
126  *  to actually look up the file.
127  *  @param[in] fname name of file to look up
128  *  @param[in] ftype expected file format of file fname; if 0, it's derived from the filename suffix
129  *  @return file path on success, 0 otherwise */
find_file(const std::string & fname,const char * ftype)130 static const char* find_file (const std::string &fname, const char *ftype) {
131 	if (!_initialized || fname.empty())
132 		return 0;
133 
134 	std::string ext;
135 	if (ftype)
136 		ext = ftype;
137 	else {
138 		size_t pos = fname.rfind('.');
139 		if (pos == std::string::npos)
140 			return 0;  // no extension and no file type => no search
141 		ext = fname.substr(pos+1);
142 	}
143 
144 	static std::string buf;
145 #ifdef MIKTEX
146 	if (ext == "dll" || ext == "exe") {
147 		// lookup dll and exe files in the MiKTeX bin directory first
148 		buf = miktex->getBinDir() + "/" + fname;
149 		if (FileSystem::exists(buf.c_str()))
150 			return buf.c_str();
151 	}
152 	else if (ext == "cmap") {
153 		// The MiKTeX SDK doesn't support the lookup of files without suffix (yet), thus
154 		// it's not possible to find cmap files which usually don't have a suffix. In order
155 		// to work around this, we try to lookup the files by calling kpsewhich.
156 		Process process("kpsewhich", std::string("-format=cmap ")+fname);
157 		process.run(&buf);
158 		return buf.empty() ? 0 : buf.c_str();
159 	}
160 	try {
161 		return miktex->findFile(fname.c_str());
162 	}
163 	catch (const MessageException &e) {
164 		return 0;
165 	}
166 #else
167 #ifdef TEXLIVEWIN32
168 	if (ext == "exe") {
169 		// lookup exe files in directory where dvisvgm is located
170 		if (const char *path = kpse_var_value("SELFAUTOLOC")) {
171 			buf = std::string(path) + "/" + fname;
172 			return FileSystem::exists(buf.c_str()) ? buf.c_str() : 0;
173 		}
174 		return 0;
175 	}
176 #endif
177 	static std::map<std::string, kpse_file_format_type> types;
178 	if (types.empty()) {
179 		types["tfm"]  = kpse_tfm_format;
180 		types["pfb"]  = kpse_type1_format;
181 		types["vf"]   = kpse_vf_format;
182 		types["mf"]   = kpse_mf_format;
183 		types["ttc"]  = kpse_truetype_format;
184 		types["ttf"]  = kpse_truetype_format;
185 		types["otf"]  = kpse_opentype_format;
186 		types["map"]  = kpse_fontmap_format;
187 		types["cmap"] = kpse_cmap_format;
188 		types["sty"]  = kpse_tex_format;
189 		types["enc"]  = kpse_enc_format;
190 		types["pro"]  = kpse_tex_ps_header_format;
191 		types["sfd"]  = kpse_sfd_format;
192 	}
193 	std::map<std::string, kpse_file_format_type>::iterator it = types.find(ext.c_str());
194 	if (it == types.end())
195 		return 0;
196 
197 	if (char *path = kpse_find_file(fname.c_str(), it->second, 0)) {
198 		// In the current version of libkpathsea, each call of kpse_find_file produces
199 		// a memory leak since the path buffer is not freed. I don't think we can do
200 		// anything against it here...
201 		buf = path;
202 		std::free(path);
203 		return buf.c_str();
204 	}
205 	return 0;
206 #endif
207 }
208 
209 
210 /** Checks whether the given file is mapped to a different name and if the
211  *  file can be found under this name.
212  *  @param[in] fname name of file to look up
213  *  @return file path on success, 0 otherwise */
find_mapped_file(std::string fname)214 static const char* find_mapped_file (std::string fname) {
215 	size_t pos = fname.rfind('.');
216 	if (pos == std::string::npos)
217 		return 0;
218 	const std::string ext  = fname.substr(pos+1);  // file extension
219 	const std::string base = fname.substr(0, pos);
220 	if (const FontMap::Entry *entry = FontMap::instance().lookup(base)) {
221 		const char *path=0;
222 		if (entry->fontname.find('.') != std::string::npos)  // does the mapped filename has an extension?
223 			path = find_file(entry->fontname, 0);             // look for that file
224 		else {                             // otherwise, use extension of unmapped file
225 			fname = entry->fontname + "." + ext;
226 			(path = find_file(fname, 0)) || (path = mktex(fname));
227 		}
228 		return path;
229 	}
230 	return 0;
231 }
232 
233 
234 /** Runs external mktexFOO tool to create missing tfm or mf file.
235  *  @param[in] fname name of file to build
236  *  @return file path on success, 0 otherwise */
mktex(const std::string & fname)237 static const char* mktex (const std::string &fname) {
238 	if (!_initialized)
239 		return 0;
240 
241 	size_t pos = fname.rfind('.');
242 	if (!_mktex_enabled || pos == std::string::npos)
243 		return 0;
244 
245 	std::string ext  = fname.substr(pos+1);  // file extension
246 	if (ext != "tfm" && ext != "mf")
247 		return 0;
248 
249 	const char *path = 0;
250 #ifdef MIKTEX
251 	// maketfm and makemf are located in miktex/bin which is in the search PATH
252 	std::string toolname = (ext == "tfm" ? "miktex-maketfm" : "miktex-makemf");
253 	system((toolname+".exe "+fname).c_str());
254 	path = find_file(fname, 0);
255 #else
256 	kpse_file_format_type type = (ext == "tfm" ? kpse_tfm_format : kpse_mf_format);
257 	path = kpse_make_tex(type, fname.c_str());
258 #endif
259 	return path;
260 }
261 
262 
263 /** Searches a file in the TeX directory tree.
264  *  If the file doesn't exist, maximal two further steps are applied
265  *  (if "extended" is true):
266  *  - checks whether the filename is mapped to a different name and returns
267  *    the path to that name
268  *  - in case of tfm or mf files: invokes the external mktextfm/mktexmf tool
269  *    to create the missing file
270  *  @param[in] fname name of file to look up
271  *  @param[in] ftype type/format of file to look up
272  *  @param[in] extended if true, use fontmap lookup and mktexFOO calls
273  *  @return path to file on success, 0 otherwise */
lookup(const std::string & fname,const char * ftype,bool extended)274 const char* FileFinder::lookup (const std::string &fname, const char *ftype, bool extended) {
275 	const char *path;
276 	if ((path = find_file(fname, ftype)) || (extended  && ((path = find_mapped_file(fname)) || (path = mktex(fname)))))
277 		return path;
278 	return 0;
279 }
280