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

amfm.cc

// -*- related-file-name: "../include/efont/amfm.hh" -*-

/* amfm.{cc,hh} -- Adobe Multiple-Master Font Metrics
 *
 * Copyright (c) 1998-2004 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.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <efont/amfm.hh>
#include <efont/afm.hh>
#include <efont/afmparse.hh>
#include <lcdf/error.hh>
#include <efont/findmet.hh>
#include <efont/t1cs.hh>
#include <lcdf/straccum.hh>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
namespace Efont {

AmfmMetrics::AmfmMetrics(MetricsFinder *finder)
    : _finder(finder),
      _fdv(fdLast, UNKDOUBLE),
      _nmasters(-1), _naxes(-1), _masters(0), _mmspace(0),
      _primary_fonts(0), _sanity_afm(0), _uses(0)
{
}

AmfmMetrics::~AmfmMetrics()
{
    assert(_uses == 0);
    for (int m = 0; m < _nmasters; m++)
      if (_masters[m].afm)
          _masters[m].afm->unuse();
    delete[] _masters;
    delete _mmspace;
    while (_primary_fonts) {
      AmfmPrimaryFont *pf = _primary_fonts;
      _primary_fonts = _primary_fonts->next;
      delete pf;
    }
}


bool
AmfmMetrics::sanity(ErrorHandler *errh) const
{
    if (!_mmspace) {
      errh->error("AMFM sanity: no multiple master interpolation information");
      return false;
    }
  
    bool ok = true;
    for (int m = 0; m < _nmasters; m++)
      if (!_masters[m].font_name
          || _masters[m].weight_vector.size() != _nmasters) {
          errh->error("AMFM sanity: no information for master %d", m);
          ok = false;
      }
  
    if (!_mmspace->check(errh))
      ok = false;
  
    return ok;
}


int
AmfmMetrics::primary_label_value(int ax, PermString label) const
{
    assert(ax >= 0 && ax < _naxes);
    for (AmfmPrimaryFont *pf = _primary_fonts; pf; pf = pf->next) {
      if (pf->labels[ax] == label)
          return pf->design_vector[ax];
    }
    return -1;  
}


inline static bool
strcompat(PermString a, PermString b)
{
    return !a || !b || a == b;
}

Metrics *
AmfmMetrics::master(int m, ErrorHandler *errh)
{
    AmfmMaster &master = _masters[m];
  
    if (!master.loaded) {
      master.loaded = true;
      DirectoryMetricsFinder directory_finder(_directory);
      _finder->add_finder(&directory_finder);
      Metrics *afm = _finder->find_metrics(master.font_name);
    
      if (!afm) {
          if (errh)
            errh->error("%s: can't find AFM file for master `%s'",
                      _font_name.c_str(), master.font_name.c_str());
      
      } else if (!strcompat(afm->font_name(), master.font_name)
               || !strcompat(afm->family(), master.family)
               || !strcompat(afm->full_name(), master.full_name)
               || !strcompat(afm->version(), master.version)) {
          if (errh)
            errh->error("%s: AFM for master `%s' doesn't match AMFM",
                      _font_name.c_str(), master.font_name.c_str());
    
      } else if (!_sanity_afm) {
          master.afm = afm;
          _sanity_afm = afm;
          afm->use();
      
      } else {
          PairProgram *sanity_pairp = _sanity_afm->pair_program();
          PairProgram *pairp = afm->pair_program();
          char buf[1024];
          buf[0] = 0;
      
          if (afm->nglyphs() != _sanity_afm->nglyphs())
            sprintf(buf, "glyph count (%d vs. %d)", afm->nglyphs(), _sanity_afm->nglyphs());
          if (afm->nfd() != _sanity_afm->nfd())
            sprintf(buf, "fd count (%d vs. %d)", afm->nfd(), _sanity_afm->nfd());
          if (afm->nkv() != _sanity_afm->nkv())
            sprintf(buf, "kv count (%d vs. %d)", afm->nkv(), _sanity_afm->nkv());
          if (pairp->op_count() != sanity_pairp->op_count())
            sprintf(buf, "pair op count (%d vs. %d)", pairp->op_count(), sanity_pairp->op_count());

          if (!buf[0]) {
            master.afm = afm;
            afm->use();
          } else if (errh)
            errh->error("%s: AFM for master `%s' failed sanity checks (%s)", _font_name.c_str(), master.font_name.c_str(), buf);
      }
    }
  
    return master.afm;
}


AmfmPrimaryFont *
AmfmMetrics::find_primary_font(const Vector<double> &design_vector) const
{
    assert(design_vector.size() == _naxes);
    for (AmfmPrimaryFont *pf = _primary_fonts; pf; pf = pf->next) {
    
      for (int a = 0; a < _naxes; a++)
          if ((int)design_vector[a] != pf->design_vector[a])
            goto loser;
      return pf;
    
      loser: ;
    }
    return 0;
}


Metrics *
AmfmMetrics::interpolate(const Vector<double> &design_vector,
                   const Vector<double> &weight_vector,
                   ErrorHandler *errh)
{
    assert(design_vector.size() == _naxes);
    assert(weight_vector.size() == _nmasters);
  
    // FIXME: check masters for correspondence.
  
    /* 0.
     * Make sure all necessary AFMs have been loaded. */
    int m;
    for (m = 0; m < _nmasters; m++)
      if (weight_vector[m])
          if (!master(m, errh))
            return 0;
  
    /* 1.
     * Use the design vector to generate new FontName and FullName. */
  
    AmfmPrimaryFont *pf = find_primary_font(design_vector);
    // The primary font is useless to us if it doesn't have axis labels.
    if (pf && !pf->labels.size())
      pf = 0;

    StringAccum font_name_sa, full_name_sa;
    font_name_sa << _font_name;
    full_name_sa << _full_name;
    for (int a = 0; a < _naxes; a++) {
      double dv = design_vector[a];
      font_name_sa << '_' << dv;
      full_name_sa << (a == 0 ? '_' : ' ') << dv;
    
      PermString label;
      if (pf)
          label = pf->labels[a];
      if (!label)
          label = _mmspace->axis_abbreviation(a);
      if (label)
          full_name_sa << ' ' << label;
    }
    // Multiple master fonts require an underscore AFTER the font name too
    font_name_sa << '_';

    /* 2.
     * Set up the new AFM with the special constructor. */
  
    // Find the first master with a non-zero component.
    for (m = 0; m < _nmasters && weight_vector[m] == 0; m++)
      ;
    Metrics *afm = new Metrics(font_name_sa.c_str(), full_name_sa.c_str(), *_masters[m].afm);
    if (MetricsXt *xt = _masters[m].afm->find_xt("AFM")) {
      AfmMetricsXt *new_xt = new AfmMetricsXt((AfmMetricsXt &)*xt);
      afm->add_xt(new_xt);
    }
  
    /* 2.
     * Interpolate the old AFM data into the new. */
  
    afm->interpolate_dimens(*_masters[m].afm, weight_vector[m], false);
  
    for (m++; m < _nmasters; m++)
      if (weight_vector[m])
          afm->interpolate_dimens(*_masters[m].afm, weight_vector[m], true);
  
    return afm;
}


/*****
 * AmfmReader
 **/

AmfmReader::AmfmReader(AfmParser &afmp, AmfmMetrics *amfm, ErrorHandler *errh)
    : _amfm(amfm), _finder(amfm->_finder), _l(afmp),
      _mmspace(amfm->_mmspace)
{
    _errh = errh ? errh : ErrorHandler::silent_handler();
}

AmfmMetrics *
AmfmReader::read(Slurper &slurper, MetricsFinder *finder, ErrorHandler *errh)
{
    AfmParser parser(slurper);
    if (!parser.ok()) return 0;
    AmfmMetrics *amfm = new AmfmMetrics(finder);
    AmfmReader reader(parser, amfm, errh);
    if (!reader.read()) {
      delete amfm;
      return 0;
    } else
      return amfm;
}

AmfmMetrics *
AmfmReader::read(const Filename &fn, MetricsFinder *finder, ErrorHandler *errh)
{
    Slurper slurper(fn);
    return read(slurper, finder, errh);
}

void
AmfmReader::add_amcp_file(Slurper &slurper, AmfmMetrics *amfm, ErrorHandler *errh)
{
    AfmParser parser(slurper);
    if (!parser.ok()) return;
    AmfmReader reader(parser, amfm, errh);
    reader.read_amcp_file();
}


void
AmfmReader::lwarning(const char *format, ...) const
{
    va_list val;
    va_start(val, format);
    _errh->verror(ErrorHandler::ERR_WARNING, _l.landmark(), format, val);
    va_end(val);
}

void
AmfmReader::lerror(const char *format, ...) const
{
    va_list val;
    va_start(val, format);
    _errh->verror(ErrorHandler::ERR_ERROR, _l.landmark(), format, val);
    va_end(val);
}

void
AmfmReader::no_match_warning(const char *context) const
{
    // keyword() will fail (and a warning won't get printed) only if the string
    // is all whitespace, which the spec allows
    PermString keyword = _l.keyword();
    if (!keyword) return;
    if (_l.key_matched()) {
      lwarning(context ? "bad `%s' command in %s:"
             : "bad `%s' command:", keyword.c_str(), context);
      lwarning("field %d %s", _l.fail_field(), _l.message().c_str());
    } else
      lwarning(context ? "unknown command `%s' in %s"
             : "unknown command `%s'", keyword.c_str(), context);
    _l.clear_message();
}


void
AmfmReader::check_mmspace()
{
    if (!_mmspace && _amfm->_naxes >= 0 && _amfm->_nmasters >= 0
      && _amfm->_font_name) {
      _mmspace = _amfm->_mmspace =
          new MultipleMasterSpace(_amfm->_font_name, _amfm->_naxes, _amfm->_nmasters);
    }
}


bool
AmfmReader::read()
{
    assert(_amfm != 0);
    _mmspace = _amfm->_mmspace;
  
    AfmParser &l = _l;
    _amfm->_directory = l.filename().directory();
  
    // First, read all opening comments into an array so we can print them out
    // later.
    PermString comment;
    while (l.next_line()) {
      if (l.isall("Comment %+s", &comment))
          _amfm->_opening_comments.push_back(comment);
      else if (l.isall("StartMasterFontMetrics %g", (double *)0))
          ;
      else {
          l.save_line();
          break;
      }
    }
  
    int master = 0, axis = 0;
  
    while (l.next_line())
      switch (l[0]) {
      
        case 'A':
          if (l.isall("Ascender %g", &fd( fdAscender )))
            break;
          if (l.isall("Axes %d", &_amfm->_naxes)) {
            check_mmspace();
            break;
          }
          goto invalid;
      
        case 'B':
          if (l.is("BlendDesignPositions")) {
            read_positions();
            break;
          }
          if (l.is("BlendDesignMap")) {
            read_normalize();
            break;
          }
          if (l.is("BlendAxisTypes")) {
            read_axis_types();
            break;
          }
          goto invalid;
      
        case 'C':
          if (l.isall("CapHeight %g", &fd( fdCapHeight )))
            break;
          if (l.is("Comment"))
            break;
          goto invalid;
      
        case 'D':
          if (l.isall("Descender %g", &fd( fdDescender )))
            break;
          goto invalid;
      
        case 'E':
          if (l.isall("EncodingScheme %+s", &_amfm->_encoding_scheme))
            break;
          if (l.isall("EndMasterFontMetrics"))
            goto done;
          goto invalid;
      
        case 'F':
          if (l.isall("FontName %+s", &_amfm->_font_name)) {
            check_mmspace();
            break;
          }
          if (l.isall("FullName %+s", &_amfm->_full_name))
            break;
          if (l.isall("FamilyName %+s", &_amfm->_family))
            break;
          if (l.isall("FontBBox %g %g %g %g",
                  &fd( fdFontBBllx ), &fd( fdFontBBlly ),
                  &fd( fdFontBBurx ), &fd( fdFontBBury )))
            break;
          goto invalid;
      
        case 'I':
          if (l.isall("IsFixedPitch %b", (bool *)0))
            break;
          if (l.isall("ItalicAngle %g", &fd( fdItalicAngle )))
            break;
          goto invalid;
      
        case 'M':
          if (l.isall("Masters %d", &_amfm->_nmasters)) {
            check_mmspace();
            break;
          }
          goto invalid;
      
        case 'N':
          if (l.isall("Notice %+s", &_amfm->_notice))
            break;
          goto invalid;
      
        case 'S':
          if (l.isall("StartAxis")) {
            read_axis(axis++);
            break;
          }
          if (l.isall("StartMaster")) {
            read_master(master++);
            break;
          }
          if (l.isall("StartPrimaryFonts %d", (int *)0)) {
            read_primary_fonts();
            break;
          }
          if (l.isall("StartConversionPrograms %d %d", (int *)0, (int *)0)) {
            read_conversion_programs();
            break;
          }
          if (l.isall("StartMasterFontMetrics %g", (double *)0))
            break;
          goto invalid;
      
        case 'U':
          if (l.isall("UnderlinePosition %g", &fd( fdUnderlinePosition )))
            break;
          else if (l.isall("UnderlineThickness %g", &fd( fdUnderlineThickness )))
            break;
          goto invalid;
      
        case 'V':
          if (l.isall("Version %+s", &_amfm->_version))
            break;
          goto invalid;
      
        case 'W':
          if (l.isall("Weight %+s", &_amfm->_weight))
            break;
          if (l.is("WeightVector")) {
            Vector<double> wv;
            if (!read_simple_array(wv) || !_mmspace)
                lerror("bad WeightVector");
            else
                _mmspace->set_weight_vector(wv);
            break;
          }
          goto invalid;
      
        case 'X':
          if (l.isall("XHeight %g", &fd( fdXHeight )))
            break;
          goto invalid;
      
        default:
        invalid:
          no_match_warning();
      
      }
  
  done:
    if (!_mmspace) {
      _errh->error("`%s' is not an AMFM file", String(_l.landmark().file()).c_str());
      return false;
    }
  
    LandmarkErrorHandler pin_errh(_errh, _l.landmark());
    if (!_amfm->sanity(&pin_errh)) {
      _errh->lerror(_l.landmark().whole_file(),
                  "bad AMFM file (missing or inconsistent information)");
      return false;
    }
  
    if (!_mmspace->check_intermediate() && _l.filename().directory()) {
      String name = l.filename().base() + ".amcp";
      Slurper slurp(_l.filename().from_directory(name));
      add_amcp_file(slurp, _amfm, _errh);
    }
  
    return true;
}


void
AmfmReader::read_amcp_file()
{
    int lines_read = 0;
  
    while (_l.next_line()) {
      lines_read++;
      switch (_l[0]) {
      
        case 'C':
          if (_l.is("Comment"))
            break;
          goto invalid;
      
        case 'S':
          if (_l.isall("StartConversionPrograms %d %d", (int *)0, (int *)0)) {
            read_conversion_programs();
            break;
          }
          goto invalid;
      
        default:
        invalid:
          no_match_warning("AMCP file");
      
      }
    }
  
    if (_mmspace && !_mmspace->ndv() && !_mmspace->cdv() && lines_read)
      lwarning("no conversion programs in .amcp file");
}


bool
AmfmReader::read_simple_array(Vector<double> &vec) const
{
    if (!_l.is("[")) return false;
  
    vec.clear();
    double d;
    while (_l.is("%g", &d))
      vec.push_back(d);
  
    return _l.is("]");
}


void
AmfmReader::read_positions() const
{
    if (nmasters() < 2 || naxes() < 1) return;
    Vector<NumVector> positions;
    if (!_l.is("[") || !_mmspace) goto error;
  
    for (int i = 0; i < nmasters(); i++) {
      positions.push_back(NumVector());
      if (!read_simple_array(positions.back()))
          goto error;
    }
  
    if (!_l.is("]")) goto error;
    _mmspace->set_master_positions(positions);
    return;
  
  error:
    lerror("bad BlendDesignPositions");
}


void
AmfmReader::read_normalize() const
{
    if (naxes() < 1) return;
    Vector<NumVector> normalize_in, normalize_out;
    if (!_l.is("[") || !_mmspace) goto error;
  
    for (int a = 0; a < naxes(); a++) {
      if (!_l.is("[")) goto error;
      normalize_in.push_back(NumVector());
      normalize_out.push_back(NumVector());
      double v1, v2;
      while (_l.is("[-%g %g-]", &v1, &v2)) {
          normalize_in[a].push_back(v1);
          normalize_out[a].push_back(v2);
      }
      if (!_l.is("]")) goto error;
    }
  
    if (!_l.is("]")) goto error;
    _mmspace->set_normalize(normalize_in, normalize_out);
    return;
  
  error:
    lerror("bad BlendDesignPositions");
}


void
AmfmReader::read_axis_types() const
{
    PermString s;
    int ax = 0;
    Vector<PermString> types;
    if (naxes() < 1) return;
    if (!_l.is("[") || !_mmspace) goto error;
    _mmspace->check();

    while (_l.is("/%/s", &s))
      _mmspace->set_axis_type(ax++, s);
  
    if (!_l.is("]")) goto error;
    return;
  
  error:
    lerror("bad BlendAxisTypes");
}


void
AmfmReader::read_axis(int ax) const
{
    bool ok = _mmspace && ax < naxes();
    if (!ok)
      lerror("bad axis number %d", ax);
    else
      _mmspace->check();
  
    PermString s;
    while (_l.next_line())
      switch (_l[0]) {
      
        case 'A':
          if (_l.is("AxisType %+s", &s)) {
            if (ok)
                _mmspace->set_axis_type(ax, s);
            break;
          }
          if (_l.is("AxisLabel %+s", &s)) {
            if (ok)
                _mmspace->set_axis_label(ax, s);
            break;
          }
          goto invalid;
      
        case 'C':
          if (_l.is("Comment"))
            break;
          goto invalid;
      
        case 'E':
          if (_l.isall("EndAxis"))
            goto endaxis;
          goto invalid;
      
        default:
        invalid:
          no_match_warning();
      
      }
  
  endaxis: ;
}


void
AmfmReader::read_master(int m) const
{
    AmfmMaster *amfmm;
    AmfmMaster dummy;
    if (m >= nmasters()) {
      lerror("too many masters");
      amfmm = &dummy;
    } else {
      if (!_amfm->_masters)
          _amfm->_masters = new AmfmMaster[ nmasters() ];
      amfmm = &_amfm->_masters[m];
    }
  
    while (_l.next_line())
      // Grok the whole line. Are we on a character metric data line?
      switch (_l[0]) {
      
        case 'C':
          if (_l.is("Comment"))
            break;
          goto invalid;
      
        case 'E':
          if (_l.isall("EndMaster"))
            goto endmaster;
          goto invalid;
      
        case 'F':
          if (_l.isall("FontName %+s", &amfmm->font_name))
            break;
          if (_l.isall("FullName %+s", &amfmm->full_name))
            break;
          if (_l.isall("FamilyName %+s", &amfmm->family))
            break;
          goto invalid;
      
        case 'V':
          if (_l.isall("Version %+s", &amfmm->version))
            break;
          goto invalid;
      
        case 'W':
          if (_l.is("WeightVector")) {
            if (!(read_simple_array(amfmm->weight_vector) &&
                  amfmm->weight_vector.size() == nmasters())) {
                lerror("bad WeightVector");
                amfmm->weight_vector.clear();
            }
            break;
          }
          goto invalid;
      
        default:
        invalid:
          no_match_warning();
      
      }
  
  endmaster: ;
}


void
AmfmReader::read_one_primary_font() const
{
    AmfmPrimaryFont *pf = new AmfmPrimaryFont;
    pf->design_vector.resize(naxes());
    pf->labels.resize(naxes());
  
    while (_l.left()) {
    
      if (_l.is("PC")) {
          for (int a = 0; a < naxes(); a++)
            if (!_l.is("%d", &pf->design_vector[a]))
                goto error;
      } else if (_l.is("PL")) {
          for (int a = 0; a < naxes(); a++)
            if (!_l.is("(-%/s-)", &pf->labels[a]))
                goto error;
      } else if (_l.is("PN %(", &pf->name))
          ;
      else
          no_match_warning("primary font");
    
      _l.is(";"); // get rid of any possible semicolon
    }
  
    pf->next = _amfm->_primary_fonts;
    _amfm->_primary_fonts = pf;
    return;
  
  error:
    delete pf;
}


void
AmfmReader::read_primary_fonts() const
{
    while (_l.next_line())
      switch (_l[0]) {
      
        case 'C':
          if (_l.is("Comment"))
            break;
          goto invalid;
      
        case 'E':
          if (_l.isall("EndPrimaryFonts"))
            goto end_primary_fonts;
          goto invalid;
      
        case 'P':
          if (_l[1] == 'C' && isspace(_l[2])) {
            read_one_primary_font();
            break;
          }
          goto invalid;
      
        default:
        invalid:
          no_match_warning();
      
      }
  
  end_primary_fonts: ;
}


void
AmfmReader::read_conversion_programs() const
{
    String ndv, cdv, s;
  
    while (_l.next_line())
      switch (_l[0]) {
      
        case 'C':
          if (_l.isall("CDV %<", &s)) {
            cdv += s;
            break;
          }
          goto invalid;
      
        case 'E':
          if (_l.isall("EndConversionPrograms"))
            goto end_conversion_programs;
          goto invalid;
      
        case 'N':
          if (_l.isall("NDV %<", &s)) {
            ndv += s;
            break;
          }
          goto invalid;
      
        default:
        invalid:
          no_match_warning();
          break;
      
      }
  
  end_conversion_programs:
    if (_mmspace) {
      _mmspace->set_ndv(Type1Charstring(ndv));
      _mmspace->set_cdv(Type1Charstring(cdv));
    }
}

}

Generated by  Doxygen 1.6.0   Back to index