Logo Search packages:      
Sourcecode: lcdf-typetools version File versions

automatic.cc

/* automatic.{cc,hh} -- code for automatic mode and interfacing with kpathsea
 *
 * Copyright (c) 2003-2005 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" }
};

#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 (verbose)
      errh->message("trying to create dotless-j font for %s", ps_fontname.c_str());
    if (!ps_fontname)
      return String();
    
    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();
}

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();
}

Generated by  Doxygen 1.6.0   Back to index