/// // Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING /// #include "fontmanager.hh" #include #include #include #include "afm.hh" #include "freetype.hh" #include "util/stringutil.h" #include "util/filesys.h" #include "util/warning.h" font::FontManager* font::FontManager::_instance = 0; // StaticMutex can, unlike regular Mutex, be created before the call // to thread_init Glib::StaticMutex font::FontManager::mutex = GLIBMM_STATIC_MUTEX_INIT; font::FontManager& font::FontManager::instance() { // prevent two threads from creating the instance at once Glib::Mutex::Lock lock(mutex); if(!_instance) _instance = new FontManager(); return *_instance; } namespace { /// \note FcConfigGetFontDirs could be used to get a list of paths, /// but it would include paths for fonts formats other than TrueType /// or Type1 void addPath(font::FontManager::FontPaths &fontpaths, const std::string &filename) { fontpaths.insert(path(filename)); } } namespace font { FontManager::FontManager() { // Ghostscript replacements for Adobe fonts: fontAlias("Bookman-Demi", "URWBookmanL-DemiBold"); fontAlias("Bookman-DemiItalic", "URWBookmanL-DemiBoldItal"); fontAlias("Bookman-Light", "URWBookmanL-Ligh"); fontAlias("Bookman-LightItalic", "URWBookmanL-LighItal"); fontAlias("Courier", "NimbusMonL-Regu"); fontAlias("Courier-Oblique", "NimbusMonL-ReguObli"); fontAlias("Courier-Bold", "NimbusMonL-Bold"); fontAlias("Courier-BoldOblique", "NimbusMonL-BoldObli"); fontAlias("Times-Roman", "NimbusRomNo9L-Regu"); fontAlias("Times-Italic", "NimbusRomNo9L-ReguItal"); fontAlias("Times-Bold", "NimbusRomNo9L-Medi"); fontAlias("Times-BoldItalic", "NimbusRomNo9L-MediItal"); fontAlias("Helvetica", "NimbusSanL-Regu"); fontAlias("Helvetica-Oblique", "NimbusSanL-ReguItal"); fontAlias("Helvetica-Bold", "NimbusSanL-Bold"); fontAlias("Helvetica-BoldOblique", "NimbusSanL-Regu"); fontAlias("Helvetica-Narrow", "NimbusSanL-ReguCond"); fontAlias("Helvetica-Narrow-Oblique", "NimbusSanL-ReguCondItal"); fontAlias("Helvetica-Narrow-Bold", "NimbusSanL-BoldCond"); fontAlias("Helvetica-Narrow-BoldOblique", "NimbusSanL-ReguCond"); fontAlias("Palatino-Roman", "URWPalladioL-Roma"); fontAlias("Palatino-Italic", "URWPalladioL-Ital"); fontAlias("Palatino-Bold", "URWPalladioL-Bold"); fontAlias("Palatino-BoldItalic", "URWPalladioL-BoldItal"); fontAlias("AvantGarde-Book", "URWGothicL-Book"); fontAlias("AvantGarde-BookOblique", "URWGothicL-BookObli"); fontAlias("AvantGarde-Demi", "URWGothicL-Demi"); fontAlias("AvantGarde-DemiOblique", "URWGothicL-DemiObli"); fontAlias("NewCenturySchlbk-Roman", "CenturySchL-Roma"); fontAlias("NewCenturySchlbk-Italic", "CenturySchL-Ital"); fontAlias("NewCenturySchlbk-Bold", "CenturySchL-Bold"); fontAlias("NewCenturySchlbk-BoldItalic","CenturySchL-BoldItal"); fontAlias("Symbol", "StandardSymL"); fontAlias("ZapfChancery-MediumItalic", "URWChanceryL-MediItal"); fontAlias("ZapfDingbats", "Dingbats"); // read font list from fontconfig // mostly following the procedure in fc-list.c if(!FcInit()) throw std::runtime_error("Failed to initialize fontconfig"); FcPattern *pat = FcPatternCreate(); FcObjectSet *os = FcObjectSetBuild(FC_FILE, 0); FcFontSet *fs = FcFontList(0, pat, os); if(os) FcObjectSetDestroy(os); if(pat) FcPatternDestroy(pat); if(fs) { for(int i = 0; i < fs->nfont; i++) { FcChar8 *_filename; if(FcPatternGetString(fs->fonts[i], FC_FILE, 0, &_filename) == FcResultMatch) { std::string filename(reinterpret_cast(_filename)); std::string ext = suffix(filename); try { if(ext == "pfa" // || ext == "gsf" || ext == "pfb") { // Type 1 // if there is no AFM file, don't bother std::string afmfilename = no_suffix(filename) + ".afm"; if(std::ifstream(afmfilename.c_str())) { checkOutType1(filename); checkOutAFM(afmfilename); // make sure it is in the path list addPath(fontpaths, filename); } } else if(ext == "ttf") { // TrueType checkOutTrueType(filename); // make sure it is in the path list addPath(fontpaths, filename); } // else ignore } catch(const std::exception &e) { warning << e.what() << std::endl; } } } FcFontSetDestroy(fs); } else { warning << "Didn't get any fonts from fontconfig" << std::endl; } } using std::string; /// \todo should we really allow more than one font to /// have the same alias? string FontManager::unalias(const string &fontname, bool recursive) const { FontAliases::const_iterator i = fontaliases.find(fontname); if(i == fontaliases.end()) return fontname; else if(recursive) return unalias(i->second); // recursive aliases possible else return i->second; } void FontManager::fontAlias(const string &alias, const string &fontname) { fontaliases.insert(std::make_pair(alias, fontname)); } FontManager::FontNames FontManager::getAvailableFonts() const { FontNames fontnames; for(FontFiles::const_iterator i = fontfiles.begin(); i != fontfiles.end(); i++) fontnames.insert(i->first); return fontnames; } void FontManager::reportFonts(std::ostream &out) const { using std::endl; out << "Fonts:" << endl; for(FontFiles::const_iterator i = fontfiles.begin(); i != fontfiles.end(); i++) out << "\t Name: " << i->first << endl << "\tMetrics: " << i->second.first << endl << "\t File: " << i->second.second << endl << endl; out << endl << "Aliases:" << endl; for(FontAliases::const_iterator i = fontaliases.begin(); i != fontaliases.end(); i++) out << "\t" << i->first << ": " << i->second << endl; } namespace { /** A getline that strips whitespace. */ std::istream &getline_nows(std::istream &in, std::string &line) { std::istream &tmp = safe_getline(in, line); line = strip_whitespace(line); return tmp; } } void FontManager::checkOutTrueType(const string &filename) { FTMetrics font(filename); std::string fontname = font.getPostscriptName(); if(fontname.empty()) throw std::runtime_error("The font does not have a PostScript name"); FontFiles::iterator i = fontfiles.find(fontname); // TrueType doesn't have a separate metrics file if(i == fontfiles.end()) fontfiles[fontname] = std::make_pair(filename, filename); } void FontManager::checkOutAFM(const string &filename) { // we could use an AFMetrics object instead, but the constructor is // rather slow std::ifstream in(filename.c_str()); if(!in) throw FontError("Failed to open " + filename); string tmp; getline_nows(in, tmp); if(!starts_with(tmp, "StartFontMetrics")) throw FontError(filename + " is not an AFM"); while(getline_nows(in, tmp)) if(starts_with(tmp, "FontName")) { string fontname(strip_whitespace(tmp.substr(9), true, true)); FontFiles::iterator i = fontfiles.find(fontname); if(i == fontfiles.end()) fontfiles[fontname] = std::make_pair(filename, string()); else if(i->second.first.empty()) // don't overwrite i->second.first = filename; return; } throw FontError(" has no FontName"); } void FontManager::checkOutType1(const string &filename) { std::ifstream in(filename.c_str()); if(!in) throw FontError("Failed to open " + filename); string tmp; getline_nows(in, tmp); while(in && !starts_with(tmp, "/FontName")) getline_nows(in, tmp); if(starts_with(tmp, "/FontName")) { string fontname(tmp.substr(tmp.find('/', 1) + 1)); fontname = fontname.substr(0, fontname.find(' ')); FontFiles::iterator i = fontfiles.find(fontname); if(i == fontfiles.end()) fontfiles[fontname] = std::make_pair(string(), filename); else if(i->second.second.empty() || (suffix(filename) == "pfa" && suffix(i->second.second) == "pfb")) // prefer pfa over pfb i->second.second = filename; } else { throw FontError(filename + " has no FontName"); } } FontManager::FilePair FontManager::getFontFiles(const string &fontname) const { FontFiles::const_iterator i = fontfiles.find(fontname); string filename; if(i != fontfiles.end()) return std::make_pair(i->second.first, i->second.second); else { FontAliases::const_iterator l = fontaliases.lower_bound(fontname); FontAliases::const_iterator u = fontaliases.upper_bound(fontname); while(l != u){ try { return getFontFiles(l->second); } catch(FontError&) {} l++; } } throw FontError("No font files found for " + fontname); } void FontManager::loadFont(const string &fontname) { if(metrics_map.find(fontname) != metrics_map.end()) throw FontError("Font metrics for " + fontname + " have already been loaded"); const string filename(getFontFiles(fontname).first); std::ifstream metricsfile(filename.c_str()); if(!metricsfile) throw FontError("Font metrics file " + filename + " not found for " + fontname); std::auto_ptr metrics; const string suff = suffix(filename); if(suff == "afm") metrics.reset(new font::AFMetrics(filename)); else if(suff == "ttf") metrics.reset(new font::FTMetrics(filename)); else throw FontError("\"" + suff + "\" is not a known font metrics suffix"); metrics_map[fontname] = metrics.release(); } const font::Metrics& FontManager::getFont(const string &fontname){ MetricsMap::const_iterator i = metrics_map.find(fontname); if(i == metrics_map.end()) { loadFont(fontname); i = metrics_map.find(fontname); if(i == metrics_map.end()) // should never happen, but doesn't hurt throw FontError("No font metrics found for " + fontname); } return *i->second; } }