/** * @file llfontregistry.cpp * @author Brad Payne * @brief Storage for fonts. * * $LicenseInfo:firstyear=2008&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "llgl.h" #include "llfontfreetype.h" #include "llfontgl.h" #include "llfontregistry.h" #include #include "llcontrol.h" #include "lldir.h" #include "llwindow.h" #include "llxmlnode.h" extern LLControlGroup gSavedSettings; using std::string; using std::map; bool font_desc_init_from_xml(LLXMLNodePtr node, LLFontDescriptor& desc); bool init_from_xml(LLFontRegistry* registry, LLXMLNodePtr node); const std::string MACOSX_FONT_PATH_LIBRARY = "/Library/Fonts/"; const std::string MACOSX_FONT_SUPPLEMENTAL = "Supplemental/"; LLFontDescriptor::char_functor_map_t LLFontDescriptor::mCharFunctors({ { "is_emoji", LLStringOps::isEmoji } }); LLFontDescriptor::LLFontDescriptor(): mStyle(0) { } LLFontDescriptor::LLFontDescriptor(const std::string& name, const std::string& size, const U8 style, const font_file_info_vec_t& font_files): mName(name), mSize(size), mStyle(style), mFontFiles(font_files) { } LLFontDescriptor::LLFontDescriptor(const std::string& name, const std::string& size, const U8 style, const font_file_info_vec_t& font_list, const font_file_info_vec_t& font_collection_files) : LLFontDescriptor(name, size, style, font_list) { mFontCollectionFiles = font_collection_files; } LLFontDescriptor::LLFontDescriptor(const std::string& name, const std::string& size, const U8 style): mName(name), mSize(size), mStyle(style) { } bool LLFontDescriptor::operator<(const LLFontDescriptor& b) const { if (mName < b.mName) return true; else if (mName > b.mName) return false; if (mStyle < b.mStyle) return true; else if (mStyle > b.mStyle) return false; if (mSize < b.mSize) return true; else return false; } static const std::string s_template_string("TEMPLATE"); bool LLFontDescriptor::isTemplate() const { return getSize() == s_template_string; } // Look for substring match and remove substring if matched. bool removeSubString(std::string& str, const std::string& substr) { size_t pos = str.find(substr); if (pos != string::npos) { str.erase(pos, substr.size()); return true; } return false; } // Check for substring match without modifying the source string. bool findSubString(std::string& str, const std::string& substr) { size_t pos = str.find(substr); if (pos != string::npos) { return true; } return false; } // Normal form is // - raw name // - bold, italic style info reflected in both style and font name. // - other style info removed. // - size info moved to mSize, defaults to Medium // For example, // - "SansSerifHuge" would normalize to { "SansSerif", "Huge", 0 } // - "SansSerifBold" would normalize to { "SansSerifBold", "Medium", BOLD } LLFontDescriptor LLFontDescriptor::normalize() const { std::string new_name(mName); std::string new_size(mSize); U8 new_style(mStyle); // Only care about style to extent it can be picked up by font. new_style &= (LLFontGL::BOLD | LLFontGL::ITALIC); // All these transformations are to support old-style font specifications. if (removeSubString(new_name,"Small")) new_size = "Small"; if (removeSubString(new_name,"Big")) new_size = "Large"; if (removeSubString(new_name,"Medium")) new_size = "Medium"; if (removeSubString(new_name,"Large")) new_size = "Large"; if (removeSubString(new_name,"Huge")) new_size = "Huge"; // HACK - Monospace is the only one we don't remove, so // name "Monospace" doesn't get taken down to "" // For other fonts, there's no ambiguity between font name and size specifier. if (new_size != s_template_string && new_size.empty() && findSubString(new_name,"Monospace")) new_size = "Monospace"; if (new_size.empty()) new_size = "Medium"; if (removeSubString(new_name,"Bold")) new_style |= LLFontGL::BOLD; if (removeSubString(new_name,"Italic")) new_style |= LLFontGL::ITALIC; return LLFontDescriptor(new_name,new_size,new_style, getFontFiles(), getFontCollectionFiles()); } void LLFontDescriptor::addFontFile(const std::string& file_name, const std::string& char_functor) { char_functor_map_t::const_iterator it = mCharFunctors.find(char_functor); mFontFiles.push_back(LLFontFileInfo(file_name, (mCharFunctors.end() != it) ? it->second : nullptr)); } void LLFontDescriptor::addFontCollectionFile(const std::string& file_name, const std::string& char_functor) { char_functor_map_t::const_iterator it = mCharFunctors.find(char_functor); mFontCollectionFiles.push_back(LLFontFileInfo(file_name, (mCharFunctors.end() != it) ? it->second : nullptr)); } LLFontRegistry::LLFontRegistry(bool create_gl_textures) : mCreateGLTextures(create_gl_textures) { // This is potentially a slow directory traversal, so we want to // cache the result. mUltimateFallbackList = LLWindow::getDynamicFallbackFontList(); } LLFontRegistry::~LLFontRegistry() { clear(); } bool LLFontRegistry::parseFontInfo(const std::string& xml_filename) { bool success = false; // Succeed if we find and read at least one XUI file const string_vec_t xml_paths = gDirUtilp->findSkinnedFilenames(LLDir::XUI, xml_filename); if (xml_paths.empty()) { // We didn't even find one single XUI file return false; } for (string_vec_t::const_iterator path_it = xml_paths.begin(); path_it != xml_paths.end(); ++path_it) { LLXMLNodePtr root; bool parsed_file = LLXMLNode::parseFile(*path_it, root, NULL); if (!parsed_file) continue; if ( root.isNull() || ! root->hasName( "fonts" ) ) { LL_WARNS() << "Bad font info file: " << *path_it << LL_ENDL; continue; } std::string root_name; root->getAttributeString("name",root_name); if (root->hasName("fonts")) { // Expect a collection of children consisting of "font" or "font_size" entries bool init_succ = init_from_xml(this, root); success = success || init_succ; } } //if (success) // dump(); return success; } std::string currentOsName() { #if LL_WINDOWS return "Windows"; #elif LL_DARWIN return "Mac"; #elif LL_SDL || LL_MESA_HEADLESS return "Linux"; #else return ""; #endif } bool font_desc_init_from_xml(LLXMLNodePtr node, LLFontDescriptor& desc) { if (node->hasName("font")) { std::string attr_name; if (node->getAttributeString("name",attr_name)) { desc.setName(attr_name); } std::string attr_style; if (node->getAttributeString("font_style",attr_style)) { desc.setStyle(LLFontGL::getStyleFromString(attr_style)); } desc.setSize(s_template_string); } LLXMLNodePtr child; for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) { std::string child_name; child->getAttributeString("name",child_name); if (child->hasName("file")) { std::string font_file_name = child->getTextContents(); std::string char_functor; if (child->hasAttribute("functor")) { child->getAttributeString("functor", char_functor); } if (child->hasAttribute("load_collection")) { BOOL col = FALSE; child->getAttributeBOOL("load_collection", col); if (col) { desc.addFontCollectionFile(font_file_name, char_functor); } } desc.addFontFile(font_file_name, char_functor); } else if (child->hasName("os")) { if (child_name == currentOsName()) { font_desc_init_from_xml(child, desc); } } } return true; } bool init_from_xml(LLFontRegistry* registry, LLXMLNodePtr node) { LLXMLNodePtr child; for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) { std::string child_name; child->getAttributeString("name",child_name); if (child->hasName("font")) { LLFontDescriptor desc; bool font_succ = font_desc_init_from_xml(child, desc); LLFontDescriptor norm_desc = desc.normalize(); if (font_succ) { // if this is the first time we've seen this font name, // create a new template map entry for it. const LLFontDescriptor *match_desc = registry->getMatchingFontDesc(desc); if (match_desc == NULL) { // Create a new entry (with no corresponding font). registry->mFontMap[norm_desc] = NULL; } // otherwise, find the existing entry and combine data. else { // Prepend files from desc. // A little roundabout because the map key is const, // so we have to fetch it, make a new map key, and // replace the old entry. font_file_info_vec_t font_files = match_desc->getFontFiles(); font_files.insert(font_files.begin(), desc.getFontFiles().begin(), desc.getFontFiles().end()); font_file_info_vec_t font_collection_files = match_desc->getFontCollectionFiles(); font_collection_files.insert(font_collection_files.begin(), desc.getFontCollectionFiles().begin(), desc.getFontCollectionFiles().end()); LLFontDescriptor new_desc = *match_desc; new_desc.setFontFiles(font_files); new_desc.setFontCollectionFiles(font_collection_files); registry->mFontMap.erase(*match_desc); registry->mFontMap[new_desc] = NULL; } } } else if (child->hasName("font_size")) { std::string size_name; F32 size_value; if (child->getAttributeString("name",size_name) && child->getAttributeF32("size",size_value)) { registry->mFontSizes[size_name] = size_value; } } } return true; } bool LLFontRegistry::nameToSize(const std::string& size_name, F32& size) { font_size_map_t::iterator it = mFontSizes.find(size_name); if (it != mFontSizes.end()) { size = it->second; return true; } return false; } LLFontGL *LLFontRegistry::createFont(const LLFontDescriptor& desc) { // Name should hold a font name recognized as a setting; the value // of the setting should be a list of font files. // Size should be a recognized string value // Style should be a set of flags including any implied by the font name. // First decipher the requested size. LLFontDescriptor norm_desc = desc.normalize(); F32 point_size; bool found_size = nameToSize(norm_desc.getSize(),point_size); if (!found_size) { LL_WARNS() << "createFont unrecognized size " << norm_desc.getSize() << LL_ENDL; return NULL; } LL_INFOS() << "createFont " << norm_desc.getName() << " size " << norm_desc.getSize() << " style " << ((S32) norm_desc.getStyle()) << LL_ENDL; F32 fallback_scale = 1.0; // Find corresponding font template (based on same descriptor with no size specified) LLFontDescriptor template_desc(norm_desc); template_desc.setSize(s_template_string); const LLFontDescriptor *match_desc = getClosestFontTemplate(template_desc); if (!match_desc) { LL_WARNS() << "createFont failed, no template found for " << norm_desc.getName() << " style [" << ((S32)norm_desc.getStyle()) << "]" << LL_ENDL; return NULL; } // See whether this best-match font has already been instantiated in the requested size. LLFontDescriptor nearest_exact_desc = *match_desc; nearest_exact_desc.setSize(norm_desc.getSize()); font_reg_map_t::iterator it = mFontMap.find(nearest_exact_desc); // If we fail to find a font in the fonts directory, it->second might be NULL. // We shouldn't construcnt a font with a NULL mFontFreetype. // This may not be the best solution, but it at least prevents a crash. if (it != mFontMap.end() && it->second != NULL) { LL_INFOS() << "-- matching font exists: " << nearest_exact_desc.getName() << " size " << nearest_exact_desc.getSize() << " style " << ((S32) nearest_exact_desc.getStyle()) << LL_ENDL; // copying underlying Freetype font, and storing in LLFontGL with requested font descriptor LLFontGL *font = new LLFontGL; font->mFontDescriptor = desc; font->mFontFreetype = it->second->mFontFreetype; mFontMap[desc] = font; return font; } // Build list of font names to look for. // Files specified for this font come first, followed by those from the default descriptor. font_file_info_vec_t font_files = match_desc->getFontFiles(); font_file_info_vec_t font_collection_files = match_desc->getFontCollectionFiles(); LLFontDescriptor default_desc("default",s_template_string,0); const LLFontDescriptor *match_default_desc = getMatchingFontDesc(default_desc); if (match_default_desc) { font_files.insert(font_files.end(), match_default_desc->getFontFiles().begin(), match_default_desc->getFontFiles().end()); font_collection_files.insert(font_collection_files.end(), match_default_desc->getFontCollectionFiles().begin(), match_default_desc->getFontCollectionFiles().end()); } // Add ultimate fallback list - generated dynamically on linux, // null elsewhere. std::transform(getUltimateFallbackList().begin(), getUltimateFallbackList().end(), std::back_inserter(font_files), [](const std::string& file_name) { return LLFontFileInfo(file_name); }); // Load fonts based on names. if (font_files.empty()) { LL_WARNS() << "createFont failed, no file names specified" << LL_ENDL; return NULL; } LLFontGL *result = NULL; // The first font will get pulled will be the "head" font, set to non-fallback. // Rest will consitute the fallback list. BOOL is_first_found = TRUE; string_vec_t font_search_paths; font_search_paths.push_back(LLFontGL::getFontPathLocal()); font_search_paths.push_back(LLFontGL::getFontPathSystem()); #if LL_DARWIN font_search_paths.push_back(MACOSX_FONT_PATH_LIBRARY); #endif // The fontname string may contain multiple font file names separated by semicolons. // Break it apart and try loading each one, in order. for(font_file_info_vec_t::iterator font_file_it = font_files.begin(); font_file_it != font_files.end(); ++font_file_it) { LLFontGL *fontp = NULL; font_paths.push_back(MACOSX_FONT_PATH_LIBRARY + MACOSX_FONT_SUPPLEMENTAL + *file_name_it); font_paths.push_back(sys_path + MACOSX_FONT_SUPPLEMENTAL + *file_name_it); bool is_ft_collection = (std::find_if(font_collection_files.begin(), font_collection_files.end(), [&font_file_it](const LLFontFileInfo& ffi) { return font_file_it->FileName == ffi.FileName; }) != font_collection_files.end()); // *HACK: Fallback fonts don't render, so we can use that to suppress // creation of OpenGL textures for test apps. JC BOOL is_fallback = !is_first_found || !mCreateGLTextures; F32 extra_scale = (is_fallback)?fallback_scale:1.0; F32 point_size_scale = extra_scale * point_size; bool is_font_loaded = false; for(string_vec_t::iterator font_search_path_it = font_search_paths.begin(); font_search_path_it != font_search_paths.end(); ++font_search_path_it) { const std::string font_path = *font_search_path_it + font_file_it->FileName; fontp = new LLFontGL; S32 num_faces = is_ft_collection ? fontp->getNumFaces(font_path) : 1; for (S32 i = 0; i < num_faces; i++) { if (fontp == NULL) { fontp = new LLFontGL; } if (fontp->loadFace(font_path, point_size_scale, LLFontGL::sVertDPI, LLFontGL::sHorizDPI, is_fallback, i)) { is_font_loaded = true; if (is_first_found) { result = fontp; is_first_found = false; } else { result->mFontFreetype->addFallbackFont(fontp->mFontFreetype, font_file_it->CharFunctor); delete fontp; fontp = NULL; } } else { delete fontp; fontp = NULL; } } if (is_font_loaded) break; } if(!is_font_loaded) { LL_INFOS_ONCE("LLFontRegistry") << "Couldn't load font " << font_file_it->FileName << LL_ENDL; delete fontp; fontp = NULL; } } if (result) { result->mFontDescriptor = desc; } else { LL_WARNS() << "createFont failed in some way" << LL_ENDL; } mFontMap[desc] = result; return result; } void LLFontRegistry::reset() { for (font_reg_map_t::iterator it = mFontMap.begin(); it != mFontMap.end(); ++it) { // Reset the corresponding font but preserve the entry. if (it->second) it->second->reset(); } } void LLFontRegistry::clear() { for (font_reg_map_t::iterator it = mFontMap.begin(); it != mFontMap.end(); ++it) { LLFontGL *fontp = it->second; delete fontp; } mFontMap.clear(); } void LLFontRegistry::destroyGL() { for (font_reg_map_t::iterator it = mFontMap.begin(); it != mFontMap.end(); ++it) { // Reset the corresponding font but preserve the entry. if (it->second) it->second->destroyGL(); } } LLFontGL *LLFontRegistry::getFont(const LLFontDescriptor& desc) { font_reg_map_t::iterator it = mFontMap.find(desc); if (it != mFontMap.end()) return it->second; else { LLFontGL *fontp = createFont(desc); if (!fontp) { LL_WARNS() << "getFont failed, name " << desc.getName() <<" style=[" << ((S32) desc.getStyle()) << "]" << " size=[" << desc.getSize() << "]" << LL_ENDL; } return fontp; } } const LLFontDescriptor *LLFontRegistry::getMatchingFontDesc(const LLFontDescriptor& desc) { LLFontDescriptor norm_desc = desc.normalize(); font_reg_map_t::iterator it = mFontMap.find(norm_desc); if (it != mFontMap.end()) return &(it->first); else return NULL; } static U32 bitCount(U8 c) { U32 count = 0; if (c & 1) count++; if (c & 2) count++; if (c & 4) count++; if (c & 8) count++; if (c & 16) count++; if (c & 32) count++; if (c & 64) count++; if (c & 128) count++; return count; } // Find nearest match for the requested descriptor. const LLFontDescriptor *LLFontRegistry::getClosestFontTemplate(const LLFontDescriptor& desc) { const LLFontDescriptor *exact_match_desc = getMatchingFontDesc(desc); if (exact_match_desc) { return exact_match_desc; } LLFontDescriptor norm_desc = desc.normalize(); const LLFontDescriptor *best_match_desc = NULL; for (font_reg_map_t::iterator it = mFontMap.begin(); it != mFontMap.end(); ++it) { const LLFontDescriptor* curr_desc = &(it->first); // Ignore if not a template. if (!curr_desc->isTemplate()) continue; // Ignore if font name is wrong. if (curr_desc->getName() != norm_desc.getName()) continue; // Reject font if it matches any bits we don't want if (curr_desc->getStyle() & ~norm_desc.getStyle()) { continue; } // Take if it's the first plausible candidate we've found. if (!best_match_desc) { best_match_desc = curr_desc; continue; } // Take if it matches more bits than anything before. U8 best_style_match_bits = norm_desc.getStyle() & best_match_desc->getStyle(); U8 curr_style_match_bits = norm_desc.getStyle() & curr_desc->getStyle(); if (bitCount(curr_style_match_bits) > bitCount(best_style_match_bits)) { best_match_desc = curr_desc; continue; } // Tie-breaker: take if it matches bold. if (curr_style_match_bits & LLFontGL::BOLD) // Bold is requested and this descriptor matches it. { best_match_desc = curr_desc; continue; } } // Nothing matched. return best_match_desc; } void LLFontRegistry::dump() { LL_INFOS() << "LLFontRegistry dump: " << LL_ENDL; for (font_size_map_t::iterator size_it = mFontSizes.begin(); size_it != mFontSizes.end(); ++size_it) { LL_INFOS() << "Size: " << size_it->first << " => " << size_it->second << LL_ENDL; } for (font_reg_map_t::iterator font_it = mFontMap.begin(); font_it != mFontMap.end(); ++font_it) { const LLFontDescriptor& desc = font_it->first; LL_INFOS() << "Font: name=" << desc.getName() << " style=[" << ((S32)desc.getStyle()) << "]" << " size=[" << desc.getSize() << "]" << " fileNames=" << LL_ENDL; for (font_file_info_vec_t::const_iterator file_it=desc.getFontFiles().begin(); file_it != desc.getFontFiles().end(); ++file_it) { LL_INFOS() << " file: " << file_it->FileName << LL_ENDL; } } } void LLFontRegistry::dumpTextures() { for (const auto& fontEntry : mFontMap) { if (fontEntry.second) { fontEntry.second->dumpTextures(); } } } const string_vec_t& LLFontRegistry::getUltimateFallbackList() const { return mUltimateFallbackList; }