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