rem: OpenType::Gsub gsub(otf.table("GSUB"), errh);
add: OpenType::Gsub gsub(otf.table("GSUB"), &otf, errh);
context: do_gsub(Metrics& metrics, const OpenType::Font& otf, DvipsEncoding& dvipsenc, bool dvipsenc_literal, HashMap<uint32_t, int>& feature_usage, const Vector<PermString>& glyph_names, ErrorHandler* errh){    // apply activated GSUB features    OpenType::Gsub gsub(otf.table("GSUB"), errh);    Vector<Lookup> lookups(gsub.nlookups(), Lookup());    find_lookups(gsub.script_list(), gsub.feature_list(), lookups, errh);    Vector<OpenType::Substitution> subs;    for (int i = 0; i < lookups.size(); i++)	if (lookups[i].used) {	    OpenType::GsubLookup l = gsub.lookup(i);	    subs.clear();	    bool understood = l.unparse_automatics(gsub, subs);	    // check for -ffina, which should apply only at the ends of words,	    // and -finit, which should apply only at the beginnings.	    OpenType::Tag feature = (lookups[i].features.size() == 1 ? lookups[i].features[0] : OpenType::Tag());	    if (feature == OpenType::Tag("fina") || feature == OpenType::Tag("fin2") || feature == OpenType::Tag("fin3")) {		if (dvipsenc.boundary_char() < 0)		    errh->warning("'-ffina' requires a boundary character\n(The input encoding didn't specify a boundary character, but\nI need one to implement '-ffina' features correctly.  Try\nthe '--boundary-char' option.)");		else {		    int bg = metrics.boundary_glyph();		    for (int j = 0; j < subs.size(); j++)			subs[j].add_outer_right(bg);		}	    } else if (feature == OpenType::Tag("init")) {		int bg = metrics.boundary_glyph();		for (int j = 0; j < subs.size(); j++)		    subs[j].add_outer_left(bg);	    }	    //for (int subno = 0; subno < subs.size(); subno++) fprintf(stderr, "%5d\t%s\n", i, subs[subno].unparse().c_str());	    // figure out which glyph filter to use	    int nunderstood = metrics.apply(subs, !dvipsenc_literal, i, *lookups[i].filter, glyph_names);	    // mark as used	    int d = (understood && nunderstood == subs.size() ? F_GSUB_ALL : (nunderstood ? F_GSUB_PART : 0)) + F_GSUB_TRY;	    for (int j = 0; j < lookups[i].features.size(); j++)		feature_usage.find_force(lookups[i].features[j].value()) |= d;	}    // apply alternate selectors    if (metrics.altselectors() && !dvipsenc_literal) {	// apply default features	if (!altselector_features.size()) {	    altselector_features.push_back(OpenType::Tag("dlig"));	    altselector_feature_filters.insert(OpenType::Tag("dlig"), &null_filter);	    altselector_features.push_back(OpenType::Tag("salt"));	    altselector_feature_filters.insert(OpenType::Tag("salt"), &null_filter);	}	// do lookups	altselector_features.swap(interesting_features);	altselector_feature_filters.swap(feature_filters);	Vector<Lookup> alt_lookups(gsub.nlookups(), Lookup());	find_lookups(gsub.script_list(), gsub.feature_list(), alt_lookups, ErrorHandler::silent_handler());	Vector<OpenType::Substitution> alt_subs;	for (int i = 0; i < alt_lookups.size(); i++)	    if (alt_lookups[i].used) {		OpenType::GsubLookup l = gsub.lookup(i);		alt_subs.clear();		(void) l.unparse_automatics(gsub, alt_subs);		metrics.apply_alternates(alt_subs, i, *alt_lookups[i].filter, glyph_names);	    }	altselector_features.swap(interesting_features);	altselector_feature_filters.swap(feature_filters);    }}