diff options
Diffstat (limited to 'Build/source/texk/lcdf-typetools/otftotfm/automatic.cc')
-rw-r--r-- | Build/source/texk/lcdf-typetools/otftotfm/automatic.cc | 692 |
1 files changed, 692 insertions, 0 deletions
diff --git a/Build/source/texk/lcdf-typetools/otftotfm/automatic.cc b/Build/source/texk/lcdf-typetools/otftotfm/automatic.cc new file mode 100644 index 00000000000..5796ee281af --- /dev/null +++ b/Build/source/texk/lcdf-typetools/otftotfm/automatic.cc @@ -0,0 +1,692 @@ +/* automatic.{cc,hh} -- code for automatic mode and interfacing with kpathsea + * + * Copyright (c) 2003-2006 Eddie Kohler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. This program is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + */ + +#include <config.h> +#include "automatic.hh" +#include "kpseinterface.h" +#include "util.hh" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include <sys/stat.h> +#include <sys/types.h> +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#include <lcdf/error.hh> +#include <lcdf/straccum.hh> +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif +#include <algorithm> + +#ifdef WIN32 +# define mkdir(dir, access) mkdir(dir) +#endif + +#if HAVE_AUTO_T1DOTLESSJ +enum { T1DOTLESSJ_EXIT_J_NODOT = 2 }; +#endif + +static String::Initializer initializer; +static String odir[NUMODIR]; +static String typeface; +static String vendor; +static String map_file; +#define DEFAULT_VENDOR "lcdftools" +#define DEFAULT_TYPEFACE "unknown" + +static const struct { + const char *name; + const char *envvar; + const char *texdir; +} odir_info[] = { + { "encoding", "ENCODINGDESTDIR", "#fonts/enc/dvips/@#dvips/@" }, + { "TFM", "TFMDESTDIR", "fonts/tfm/%" }, + { "PL", "PLDESTDIR", "fonts/pl/%" }, + { "VF", "VFDESTDIR", "fonts/vf/%" }, + { "VPL", "VPLDESTDIR", "fonts/vpl/%" }, + { "Type 1", "T1DESTDIR", "fonts/type1/%" }, + { "DVIPS map", "DVIPS directory", "#fonts/map/dvips/@#dvips/@" }, + { "DVIPS updmap", "DVIPS directory", "dvips" }, + { "TrueType", "TTFDESTDIR", "fonts/truetype/%" }, + { "OpenType", "OPENTYPEDESTDIR", "fonts/opentype/%" }, + { "Type 42", "T42DESTDIR", "fonts/type42/%" } +}; + +#if HAVE_KPATHSEA +static String odir_kpathsea[NUMODIR]; + +static bool writable_texdir_tried = false; +static String writable_texdir; // always ends with directory separator +static int tds_1_1 = -1; + +static bool mktexupd_tried = false; +static String mktexupd; + +static String +kpsei_string(char* x) +{ + String s(x); + free((void*)x); + return s; +} + +static void +look_for_writable_texdir(const char *path_variable, bool create) +{ + String path = kpsei_string(kpsei_path_expand(path_variable)); + while (path && !writable_texdir) { + const char* colon = std::find(path.begin(), path.end(), kpsei_env_sep_char); + String texdir = path.substring(path.begin(), colon); + path = path.substring(colon + 1, path.end()); + if (access(texdir.c_str(), W_OK) >= 0) + writable_texdir = texdir; + else if (create && errno != EACCES && mkdir(texdir.c_str(), 0777) >= 0) + // create file if it doesn't exist already + writable_texdir = texdir; + } + if (writable_texdir && writable_texdir.back() != '/') + writable_texdir += "/"; +} + +static void +find_writable_texdir(ErrorHandler *errh, const char *) +{ + look_for_writable_texdir("$TEXMFVAR", true); + if (!writable_texdir) + look_for_writable_texdir("$VARTEXMF", false); + if (!writable_texdir) + look_for_writable_texdir("$TEXMF", false); + if (!writable_texdir) { + errh->warning("no writable directory found in $TEXMFVAR or $TEXMF"); + errh->message("(You probably need to set your TEXMF environment variable; see\n\ +the manual for more information. The current TEXMF path is\n\ +'%s'.)", kpsei_string(kpsei_path_expand("$TEXMF")).c_str()); + } + writable_texdir_tried = true; +} + +static String +get_vendor() +{ + return (vendor ? vendor : String(DEFAULT_VENDOR)); +} + +static String +get_typeface() +{ + return (typeface ? typeface : String(DEFAULT_TYPEFACE)); +} +#endif + +bool +set_vendor(const String &s) +{ + bool had = (bool) vendor; + vendor = s; + return !had; +} + +bool +set_typeface(const String &s, bool override) +{ + bool had = (bool) typeface; + if (!had || override) + typeface = s; + return !had; +} + +String +getodir(int o, ErrorHandler *errh) +{ + assert(o >= 0 && o < NUMODIR); + + if (!odir[o] && automatic && odir_info[o].envvar) + odir[o] = getenv(odir_info[o].envvar); + +#ifdef HAVE_KPATHSEA + if (!odir[o] && automatic && !writable_texdir_tried) + find_writable_texdir(errh, odir_info[o].name); + + if (!odir[o] && automatic && writable_texdir) { + String suffix = odir_info[o].texdir; + + // May need to behave differently on TDS 1.1 rather than TDS 1.0. + if (suffix[0] == '#') { + // check type of TDS + if (tds_1_1 < 0) { + // using a procedure suggested by Olaf Weber + String encfonts = kpsei_string(kpsei_path_expand("$TEXMFMAIN/fonts/enc")); + if (!encfonts) + encfonts = kpsei_string(kpsei_path_expand("$TEXMFDIST/fonts/enc")); + tds_1_1 = (encfonts != String()); + } + if (tds_1_1 == 0) + suffix = suffix.substring(std::find(suffix.begin() + 1, suffix.end(), '#') + 1, suffix.end()); + else + suffix = suffix.substring(suffix.begin() + 1, std::find(suffix.begin() + 1, suffix.end(), '#')); + } + + String dir = writable_texdir + suffix; + + if (dir.back() == '%') + dir = dir.substring(0, -1) + get_vendor() + "/" + get_typeface(); + else if (dir.back() == '@') + dir = dir.substring(0, -1) + get_vendor(); + + // create parent directories as appropriate + int slash = writable_texdir.length() - 1; + while (access(dir.c_str(), F_OK) < 0 && slash < dir.length()) { + if ((slash = dir.find_left('/', slash + 1)) < 0) + slash = dir.length(); + String subdir = dir.substring(0, slash); + if (access(subdir.c_str(), F_OK) < 0 + && !no_create + && mkdir(subdir.c_str(), 0777) < 0) + goto kpathsea_done; + } + + // that's our answer + odir[o] = dir; + odir_kpathsea[o] = dir; + } + kpathsea_done: +#endif + + if (!odir[o]) { + if (automatic) + errh->warning("%s not specified, placing %s files in '.'", odir_info[o].envvar, odir_info[o].name); + odir[o] = "."; + } + + while (odir[o].length() && odir[o].back() == '/') + odir[o] = odir[o].substring(0, -1); + + if (verbose) + errh->message("placing %s files in '%s'", odir_info[o].name, odir[o].c_str()); + return odir[o]; +} + +bool +setodir(int o, const String &value) +{ + assert(o >= 0 && o < NUMODIR); + bool had = (bool) odir[o]; + odir[o] = value; + return !had; +} + +const char * +odirname(int o) +{ + assert(o >= 0 && o < NUMODIR); + return odir_info[o].name; +} + +#if HAVE_KPATHSEA +static bool +file_in_kpathsea_odir(int o, const String &file) +{ + return odir_kpathsea[o] + && file.length() > odir[o].length() + && memcmp(file.data(), odir[o].data(), odir[o].length()) == 0 + && file[odir[o].length()] == '/'; +} +#endif + +void +update_odir(int o, String file, ErrorHandler *errh) +{ + assert(o >= 0 && o < NUMODIR); +#if HAVE_KPATHSEA + if (file.find_left('/') < 0) + file = odir[o] + "/" + file; + + // exit if this directory was not found via kpathsea, or the file is not + // in the kpathsea directory + if (!file_in_kpathsea_odir(o, file)) + return; + + assert(writable_texdir && writable_texdir.length() <= odir[o].length() + && memcmp(file.data(), writable_texdir.data(), writable_texdir.length()) == 0); + + // divide the filename into portions + // file == writable_texdir + directory + file + file = file.substring(writable_texdir.length()); + while (file && file[0] == '/') + file = file.substring(1); + int last_slash = file.find_right('/'); + String directory = (last_slash >= 0 ? file.substring(0, last_slash) : String()); + file = file.substring(last_slash >= 0 ? last_slash + 1 : 0); + if (!file) // no filename to update + return; + + // return if nocreate + if (no_create) { + errh->message("would update %sls-R for %s/%s", writable_texdir.c_str(), directory.c_str(), file.c_str()); + return; + } else if (verbose) + errh->message("updating %sls-R for %s/%s", writable_texdir.c_str(), directory.c_str(), file.c_str()); + + // try to update ls-R ourselves, rather than running mktexupd -- + // mktexupd's runtime is painful: a half second to update a file + String ls_r = writable_texdir + "ls-R"; + bool success = false; + if (access(ls_r.c_str(), R_OK) >= 0) // make sure it already exists + if (FILE *f = fopen(ls_r.c_str(), "a")) { + fprintf(f, "./%s:\n%s\n", directory.c_str(), file.c_str()); + success = true; + fclose(f); + } + + // otherwise, run mktexupd + if (!success && writable_texdir.find_left('\'') < 0 && directory.find_left('\'') < 0 && file.find_left('\'') < 0) { + // look for mktexupd script + if (!mktexupd_tried) { + mktexupd = kpsei_string(kpsei_find_file("mktexupd", KPSEI_FMT_WEB2C)); + mktexupd_tried = true; + } + + // run script + if (mktexupd) { + String command = mktexupd + " " + shell_quote(writable_texdir + directory) + " " + shell_quote(file); + int retval = system(command.c_str()); + if (retval == 127) + errh->error("could not run '%s'", command.c_str()); + else if (retval < 0) + errh->error("could not run '%s': %s", command.c_str(), strerror(errno)); + else if (retval != 0) + errh->error("'%s' failed", command.c_str()); + } + } +#else + (void) file, (void) errh; +#endif +} + +bool +set_map_file(const String &s) +{ + bool had = (bool) map_file; + map_file = s; + return !had; +} + +String +installed_type1(const String &otf_filename, const String &ps_fontname, bool allow_generate, ErrorHandler *errh) +{ + if (!ps_fontname) + return String(); + +#if HAVE_KPATHSEA +# if HAVE_AUTO_CFFTOT1 + if (!(force && allow_generate && otf_filename && otf_filename != "-" && getodir(O_TYPE1, errh))) { +# endif + // look for .pfb and .pfa + String file, path; + if ((file = ps_fontname + ".pfb", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE1))) + || (file = ps_fontname + ".pfa", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE1)))) { + if (verbose) + errh->message("Type 1 file %s found with kpathsea at %s", file.c_str(), path.c_str()); + return path; + } +# if HAVE_AUTO_CFFTOT1 + } +# endif +#endif + +#if HAVE_AUTO_CFFTOT1 + // if not found, and can generate on the fly, run cfftot1 + if (allow_generate && otf_filename && otf_filename != "-" && getodir(O_TYPE1, errh)) { + String pfb_filename = odir[O_TYPE1] + "/" + ps_fontname + ".pfb"; + if (pfb_filename.find_left('\'') >= 0 || otf_filename.find_left('\'') >= 0) + return String(); + String command = "cfftot1 " + shell_quote(otf_filename) + " -n " + shell_quote(ps_fontname) + " " + shell_quote(pfb_filename); + int retval = mysystem(command.c_str(), errh); + if (retval == 127) + errh->error("could not run '%s'", command.c_str()); + else if (retval < 0) + errh->error("could not run '%s': %s", command.c_str(), strerror(errno)); + else if (retval != 0) + errh->error("'%s' failed", command.c_str()); + if (retval == 0) { + update_odir(O_TYPE1, pfb_filename, errh); + return pfb_filename; + } + } +#endif + + return String(); +} + +String +installed_type1_dotlessj(const String &otf_filename, const String &ps_fontname, bool allow_generate, ErrorHandler *errh) +{ + if (!ps_fontname) + return String(); + if (verbose) + errh->message("searching for dotless-j font for %s", ps_fontname.c_str()); + + String j_ps_fontname = ps_fontname + "LCDFJ"; + +#if HAVE_KPATHSEA +# if HAVE_AUTO_T1DOTLESSJ + if (!(force && allow_generate && getodir(O_TYPE1, errh))) { +# endif + // look for existing .pfb or .pfa + String file, path; + if ((file = j_ps_fontname + ".pfb", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE1))) + || (file = j_ps_fontname + ".pfa", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE1)))) { + if (verbose) + errh->message("Type 1 file %s found with kpathsea at %s", file.c_str(), path.c_str()); + return path; + } +# if HAVE_AUTO_T1DOTLESSJ + } +# endif +#endif + +#if HAVE_AUTO_T1DOTLESSJ + // if not found, and can generate on the fly, try running t1dotlessj + if (allow_generate && getodir(O_TYPE1, errh)) { + if (String base_filename = installed_type1(otf_filename, ps_fontname, allow_generate, errh)) { + String pfb_filename = odir[O_TYPE1] + "/" + j_ps_fontname + ".pfb"; + if (pfb_filename.find_left('\'') >= 0 || base_filename.find_left('\'') >= 0) + return String(); + String command = "t1dotlessj " + shell_quote(base_filename) + " -n " + shell_quote(j_ps_fontname) + " " + shell_quote(pfb_filename); + int retval = mysystem(command.c_str(), errh); + if (retval == 127) + errh->warning("could not run '%s'", command.c_str()); + else if (retval < 0) + errh->warning("could not run '%s': %s", command.c_str(), strerror(errno)); + else if (WEXITSTATUS(retval) == T1DOTLESSJ_EXIT_J_NODOT) + return String("\0", 1); + else if (retval != 0) + errh->warning("'%s' failed (%d)", command.c_str(), retval); + if (retval == 0) { + update_odir(O_TYPE1, pfb_filename, errh); + return pfb_filename; + } else + errh->warning("output font will not contain a dotless-j character"); + } + } +#endif + + return String(); +} + +String +installed_truetype(const String &otf_filename, bool allow_generate, ErrorHandler *errh) +{ + String file = pathname_filename(otf_filename); + +#if HAVE_KPATHSEA + if (String path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TRUETYPE))) { + if (verbose) + errh->message("TrueType file %s found with kpathsea at %s", file.c_str(), path.c_str()); + return path; + } +#endif + +#if HAVE_AUTO_CFFTOT1 + // if not found, and can generate on the fly, run cfftot1 + if (allow_generate && otf_filename && otf_filename != "-" && getodir(O_TRUETYPE, errh)) { + String ttf_filename = odir[O_TRUETYPE] + "/" + file; + if (ttf_filename.find_left('\'') >= 0 || ttf_filename.find_left('\'') >= 0) + return String(); + String command = "cp " + shell_quote(otf_filename) + " " + shell_quote(ttf_filename); + int retval = mysystem(command.c_str(), errh); + if (retval == 127) + errh->error("could not run '%s'", command.c_str()); + else if (retval < 0) + errh->error("could not run '%s': %s", command.c_str(), strerror(errno)); + else if (retval != 0) + errh->error("'%s' failed", command.c_str()); + if (retval == 0) { + update_odir(O_TRUETYPE, ttf_filename, errh); + return ttf_filename; + } + } +#endif + + return String(); +} + +int +update_autofont_map(const String &fontname, String mapline, ErrorHandler *errh) +{ +#if HAVE_KPATHSEA + if (automatic && !map_file && getodir(O_MAP, errh)) + map_file = odir[O_MAP] + "/" + get_vendor() + ".map"; +#endif + + if (map_file == "" || map_file == "-") + fputs(mapline.c_str(), stdout); + else { + // report no_create/verbose + if (no_create) { + errh->message("would update %s for %s", map_file.c_str(), String(fontname).c_str()); + return 0; + } else if (verbose) + errh->message("updating %s for %s", map_file.c_str(), String(fontname).c_str()); + + int fd = open(map_file.c_str(), O_RDWR | O_CREAT, 0666); + if (fd < 0) + return errh->error("%s: %s", map_file.c_str(), strerror(errno)); + FILE *f = fdopen(fd, "r+"); + // NB: also change encoding logic if you change this code + +#if defined(F_SETLKW) && defined(HAVE_FTRUNCATE) + { + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + int result; + while ((result = fcntl(fd, F_SETLKW, &lock)) < 0 && errno == EINTR) + /* try again */; + if (result < 0) { + result = errno; + fclose(f); + return errh->error("locking %s: %s", map_file.c_str(), strerror(result)); + } + } +#endif + + // read old data from map file + StringAccum sa; + while (!feof(f)) + if (char *x = sa.reserve(8192)) { + int amt = fread(x, 1, 8192, f); + sa.forward(amt); + } else { + fclose(f); + return errh->error("Out of memory!"); + } + String text = sa.take_string(); + + // add comment if necessary + bool created = (!text); + if (created) + text = "% Automatically maintained by otftotfm or other programs. Do not edit.\n\n"; + if (text.back() != '\n') + text += "\n"; + + // append old encodings + int fl = 0; + int nl = text.find_left('\n') + 1; + bool changed = created; + while (fl < text.length()) { + if (fl + fontname.length() + 1 < nl + && memcmp(text.data() + fl, fontname.data(), fontname.length()) == 0 + && text[fl + fontname.length()] == ' ') { + // found the old name + if (text.substring(fl, nl - fl) == mapline) { + // duplicate of old name, don't change it + fclose(f); + if (verbose) + errh->message("%s unchanged", map_file.c_str()); + return 0; + } else { + text = text.substring(0, fl) + text.substring(nl); + nl = fl; + changed = true; + } + } + fl = nl; + nl = text.find_left('\n', fl) + 1; + } + + // special case: empty mapline, unchanged file + if (!mapline && !changed) { + fclose(f); + if (verbose) + errh->message("%s unchanged", map_file.c_str()); + return 0; + } + + // add our text + text += mapline; + + // rewind file +#ifdef HAVE_FTRUNCATE + rewind(f); + ftruncate(fd, 0); +#else + fclose(f); + f = fopen(map_file.c_str(), "w"); +#endif + + // write data + fwrite(text.data(), 1, text.length(), f); + + fclose(f); + + // inform about the new file if necessary + if (created) + update_odir(O_MAP, map_file, errh); + +#if HAVE_KPATHSEA && !WIN32 + // run 'updmap' if present + String updmap_dir, updmap_file; + if (automatic && (output_flags & G_UPDMAP)) + updmap_dir = getodir(O_MAP_PARENT, errh); + if (updmap_dir && (updmap_file = updmap_dir + "/updmap") + && access(updmap_file.c_str(), X_OK) >= 0) { + // want to run 'updmap' from its directory, can't use system() + if (verbose) + errh->message("running %s", updmap_file.c_str()); + + pid_t child = fork(); + if (child < 0) + errh->fatal("%s during fork", strerror(errno)); + else if (child == 0) { + // change to updmap directory, run it + if (chdir(updmap_dir.c_str()) < 0) + errh->fatal("%s: %s during chdir", updmap_dir.c_str(), strerror(errno)); + if (execl("./updmap", updmap_file.c_str(), (const char*) 0) < 0) + errh->fatal("%s: %s during exec", updmap_file.c_str(), strerror(errno)); + exit(1); // should never get here + } + +# if HAVE_WAITPID + // wait for updmap to finish + int status; + while (1) { + pid_t answer = waitpid(child, &status, 0); + if (answer >= 0) + break; + else if (errno != EINTR) + errh->fatal("%s during wait", strerror(errno)); + } + if (!WIFEXITED(status)) + errh->warning("%s exited abnormally", updmap_file.c_str()); + else if (WEXITSTATUS(status) != 0) + errh->warning("%s exited with status %d", updmap_file.c_str(), WEXITSTATUS(status)); +# else +# error "need waitpid() support: report this bug to the maintainer" +# endif + goto ran_updmap; + } + +# if HAVE_AUTO_UPDMAP + // run system updmap + if (output_flags & G_UPDMAP) { + String filename = map_file; + int slash = filename.find_right('\''); + if (slash >= 0) + filename = filename.substring(slash + 1); + String command = "updmap --nomkmap --enable Map " + shell_quote(filename) + "; updmap"; + if (verbose) + command += " 1>&2"; + else + command += " >/dev/null 2>&1"; + int retval = mysystem(command.c_str(), errh); + if (retval == 127) + errh->warning("could not run '%s'", command.c_str()); + else if (retval < 0) + errh->warning("could not run '%s': %s", command.c_str(), strerror(errno)); + else if (retval != 0) + errh->warning("'%s' exited with status %d;\nrun it manually to check for errors", command.c_str(), WEXITSTATUS(retval)); + goto ran_updmap; + } +# endif + + if (verbose) + errh->message("not running updmap"); + + ran_updmap: ; +#endif + } + + return 0; +} + +String +locate_encoding(String encfile, ErrorHandler *errh, bool literal) +{ + if (!encfile || encfile == "-") + return encfile; + + if (!literal) { + int slash = encfile.find_right('/'); + int dot = encfile.find_left('.', slash >= 0 ? slash : 0); + if (dot < 0) + if (String file = locate_encoding(encfile + ".enc", errh, true)) + return file; + } + +#if HAVE_KPATHSEA + if (String file = kpsei_string(kpsei_find_file(encfile.c_str(), KPSEI_FMT_ENCODING))) { + if (verbose) + errh->message("encoding file %s found with kpathsea at %s", encfile.c_str(), file.c_str()); + return file; + } else if (verbose) + errh->message("encoding file %s not found with kpathsea", encfile.c_str()); +#endif + + if (access(encfile.c_str(), R_OK) >= 0) + return encfile; + else + return String(); +} |