diff options
Diffstat (limited to 'Build/source/texk/lcdf-typetools/lcdf-typetools-src/libefont/amfm.cc')
-rw-r--r-- | Build/source/texk/lcdf-typetools/lcdf-typetools-src/libefont/amfm.cc | 850 |
1 files changed, 850 insertions, 0 deletions
diff --git a/Build/source/texk/lcdf-typetools/lcdf-typetools-src/libefont/amfm.cc b/Build/source/texk/lcdf-typetools/lcdf-typetools-src/libefont/amfm.cc new file mode 100644 index 00000000000..0ee4f66e6b0 --- /dev/null +++ b/Build/source/texk/lcdf-typetools/lcdf-typetools-src/libefont/amfm.cc @@ -0,0 +1,850 @@ +// -*- related-file-name: "../include/efont/amfm.hh" -*- + +/* amfm.{cc,hh} -- Adobe Multiple-Master Font Metrics + * + * Copyright (c) 1998-2019 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->xmessage(_l.landmark(), ErrorHandler::e_warning, format, val); + va_end(val); +} + +void +AmfmReader::lerror(const char *format, ...) const +{ + va_list val; + va_start(val, format); + _errh->xmessage(_l.landmark(), ErrorHandler::e_error, 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)); + } +} + +} |