rem: const OpenType::Font &family_otf, const Cff::Font *family_cff, bool vpl, FILE *f)
add: const FontInfo &finfo, bool vpl, FILE *f)
context: output_pl(const Metrics &metrics, const String &ps_name, int boundary_char,	  const OpenType::Font &family_otf, const Cff::Font *family_cff,	  bool vpl, FILE *f){    // XXX check DESIGNSIZE and DESIGNUNITS for correctness    fprintf(f, "(COMMENT Created by '%s'%s)\n", invocation.c_str(), current_time.c_str());    // calculate a TeX FAMILY name using afm2tfm's algorithm    String family_name = String("TeX-") + ps_name;    if (family_name.length() > 19)	family_name = family_name.substring(0, 9) + family_name.substring(-10);    fprintf(f, "(FAMILY %s)\n", family_name.c_str());    if (metrics.coding_scheme())	fprintf(f, "(CODINGSCHEME %.39s)\n", String(metrics.coding_scheme()).c_str());    int design_units = metrics.design_units();    if (design_size <= 0)	design_size = get_design_size(family_otf);    fprintf(f, "(DESIGNSIZE R %.1f)\n"	    "(DESIGNUNITS R %d.0)\n"	    "(COMMENT DESIGNSIZE (1 em) IS IN POINTS)\n"	    "(COMMENT OTHER DIMENSIONS ARE MULTIPLES OF DESIGNSIZE/%d)\n"	    "(FONTDIMEN\n", design_size, design_units, design_units);    // figure out font dimensions    Transform font_xform;    if (extend)	font_xform.scale(extend, 1);    if (slant)	font_xform.shear(slant);    int bounds[4], width;    double du = (design_units == 1000 ? 1. : design_units / 1000.);        double actual_slant = font_slant(family_cff);    if (actual_slant)	fprintf(f, "   (SLANT R %g)\n", actual_slant);    OpenType::Cmap cmap(family_otf.table("cmap"));    double val;    if (char_bounds(bounds, width, family_cff, cmap, ' ', font_xform)) {	// advance space width by letterspacing, scale by space_factor	double space_width = (width + letterspace) * space_factor;	fprint_real(f, "   (SPACE", space_width, du);	if (family_cff->dict_value(Efont::Cff::oIsFixedPitch, &val) && val) {	    // fixed-pitch: no space stretch or shrink	    fprint_real(f, "   (STRETCH", 0, du);	    fprint_real(f, "   (SHRINK", 0, du);	    fprint_real(f, "   (EXTRASPACE", space_width, du);	} else {	    fprint_real(f, "   (STRETCH", space_width / 2., du);	    fprint_real(f, "   (SHRINK", space_width / 3., du);	    fprint_real(f, "   (EXTRASPACE", space_width / 6., du);	}    }    int xheight = font_xheight(family_cff, cmap, font_xform);    if (xheight < 1000)	fprint_real(f, "   (XHEIGHT", xheight, du);        fprint_real(f, "   (QUAD", 1000, du);    fprintf(f, "   )\n");    if (boundary_char >= 0)	fprintf(f, "(BOUNDARYCHAR D %d)\n", boundary_char);    // write MAPFONT    if (vpl)	for (int i = 0; i < metrics.n_mapped_fonts(); i++) {	    String name = metrics.mapped_font_name(i);	    if (!name)		name = make_base_font_name(font_name);	    fprintf(f, "(MAPFONT D %d\n   (FONTNAME %s)\n   (FONTDSIZE R %.1f)\n   )\n", i, name.c_str(), design_size);	}        // figure out the proper names and numbers for glyphs    Vector<String> glyph_ids;    Vector<String> glyph_comments(257, String());    Vector<String> glyph_base_comments(257, String());    for (int i = 0; i < 256; i++) {	if (metrics.glyph(i)) {	    PermString name = metrics.code_name(i), expected_name;	    if (i >= '0' && i <= '9')		expected_name = digit_names[i - '0'];	    else if ((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z'))		expected_name = PermString((char)i);	    	    if (expected_name		&& name.length() >= expected_name.length()		&& memcmp(name.c_str(), expected_name.c_str(), expected_name.length()) == 0)		glyph_ids.push_back("C " + String((char)i));	    else		glyph_ids.push_back("D " + String(i));	    	    if (name != expected_name)		glyph_comments[i] = " (COMMENT " + String(name) + ")";	    int base = metrics.base_code(i);	    if (base != i)		glyph_base_comments[i] = " (COMMENT " + String(metrics.code_name(base)) + ")";	    else		glyph_base_comments[i] = glyph_comments[i];	    	} else	    glyph_ids.push_back("X");    }    // finally, BOUNDARYCHAR    glyph_ids.push_back("BOUNDARYCHAR");    // LIGTABLE    fprintf(f, "(LIGTABLE\n");    Vector<int> lig_code2, lig_outcode, lig_context, kern_code2, kern_amt;    // don't print KRN x after printing LIG x    uint32_t used[8];    for (int i = 0; i <= 256; i++)	if (metrics.glyph(i) && minimum_kern < 10000) {	    int any_lig = metrics.ligatures(i, lig_code2, lig_outcode, lig_context);	    int any_kern = metrics.kerns(i, kern_code2, kern_amt);	    if (any_lig || any_kern) {		StringAccum kern_sa;		memset(&used[0], 0, 32);		for (int j = 0; j < lig_code2.size(); j++) {		    kern_sa << "   (" << lig_context_str(lig_context[j])			    << ' ' << glyph_ids[lig_code2[j]]			    << ' ' << glyph_ids[lig_outcode[j]]			    << ')' << glyph_comments[lig_code2[j]]			    << glyph_comments[lig_outcode[j]] << '\n';		    used[lig_code2[j] >> 5] |= (1 << (lig_code2[j] & 0x1F));		}		for (Vector<int>::const_iterator k2 = kern_code2.begin(); k2 < kern_code2.end(); k2++)		    if (!(used[*k2 >> 5] & (1 << (*k2 & 0x1F)))) {			double this_kern = kern_amt[k2 - kern_code2.begin()];			if (fabs(this_kern) >= minimum_kern)			    kern_sa << "   (KRN " << glyph_ids[*k2]				    << " R " << real_string(this_kern, du)				    << ')' << glyph_comments[*k2] << '\n';		    }		if (kern_sa)		    fprintf(f, "   (LABEL %s)%s\n%s   (STOP)\n", glyph_ids[i].c_str(), glyph_comments[i].c_str(), kern_sa.c_str());	    }	}    fprintf(f, "   )\n");        // CHARACTERs    Vector<Setting> settings;    StringAccum sa;        for (int i = 0; i < 256; i++)	if (metrics.setting(i, settings)) {	    fprintf(f, "(CHARACTER %s%s\n", glyph_ids[i].c_str(), glyph_comments[i].c_str());	    // unparse settings into DVI commands	    sa.clear();	    CharstringBounds boundser(font_xform);	    const CharstringProgram *program = family_cff;	    for (int j = 0; j < settings.size(); j++) {		Setting &s = settings[j];		if (s.op == Setting::SHOW) {		    boundser.char_bounds(program->glyph_context(s.y));		    // 3.Aug.2004 -- reported by Marco Kuhlmann: Don't use		    // glyph_ids[] array when looking at a different font.		    if (program == family_cff)			sa << "      (SETCHAR " << glyph_ids[s.x] << ')' << glyph_base_comments[s.x] << "\n";		    else			sa << "      (SETCHAR D " << s.x << ")\n";		} else if (s.op == Setting::MOVE && vpl) {		    boundser.translate(s.x, s.y);		    if (s.x)			sa << "      (MOVERIGHT R " << real_string(s.x, du) << ")\n";		    if (s.y)			sa << "      (MOVEUP R " << real_string(s.y, du) << ")\n";		} else if (s.op == Setting::RULE && vpl) {		    boundser.mark(Point(0, 0));		    boundser.mark(Point(s.x, s.y));		    boundser.translate(s.x, 0);		    sa << "      (SETRULE R " << real_string(s.y, du) << " R " << real_string(s.x, du) << ")\n";		} else if (s.op == Setting::FONT && vpl) {		    sa << "      (SELECTFONT D " << (int) s.x << ")\n";		    program = metrics.mapped_font((int) s.x);		}	    }	    // output information	    boundser.output(bounds, width);	    fprint_real(f, "   (CHARWD", width, du);	    if (bounds[3] > 0)		fprint_real(f, "   (CHARHT", bounds[3], du);	    if (bounds[1] < 0)		fprint_real(f, "   (CHARDP", -bounds[1], du);	    if (bounds[2] > width)		fprint_real(f, "   (CHARIC", bounds[2] - width, du);	    if (vpl && (settings.size() > 1 || settings[0].op != Setting::SHOW))		fprintf(f, "   (MAP\n%s      )\n", sa.c_str());	    fprintf(f, "   )\n");	}}