PageRenderTime 70ms CodeModel.GetById 24ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llrender/llfontregistry.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 670 lines | 502 code | 79 blank | 89 comment | 106 complexity | 2cb217345cea814c84c66ee6e21b6dfd MD5 | raw file
  1/** 
  2 * @file llfontregistry.cpp
  3 * @author Brad Payne
  4 * @brief Storage for fonts.
  5 *
  6 * $LicenseInfo:firstyear=2008&license=viewerlgpl$
  7 * Second Life Viewer Source Code
  8 * Copyright (C) 2010, Linden Research, Inc.
  9 * 
 10 * This library is free software; you can redistribute it and/or
 11 * modify it under the terms of the GNU Lesser General Public
 12 * License as published by the Free Software Foundation;
 13 * version 2.1 of the License only.
 14 * 
 15 * This library is distributed in the hope that it will be useful,
 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 18 * Lesser General Public License for more details.
 19 * 
 20 * You should have received a copy of the GNU Lesser General Public
 21 * License along with this library; if not, write to the Free Software
 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 23 * 
 24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 25 * $/LicenseInfo$
 26 */
 27
 28#include "linden_common.h"
 29#include "llgl.h"
 30#include "llfontfreetype.h"
 31#include "llfontgl.h"
 32#include "llfontregistry.h"
 33#include <boost/tokenizer.hpp>
 34#include "llcontrol.h"
 35#include "lldir.h"
 36#include "llwindow.h"
 37
 38extern LLControlGroup gSavedSettings;
 39
 40using std::string;
 41using std::map;
 42
 43bool fontDescInitFromXML(LLXMLNodePtr node, LLFontDescriptor& desc);
 44
 45LLFontDescriptor::LLFontDescriptor():
 46	mStyle(0)
 47{
 48}
 49
 50LLFontDescriptor::LLFontDescriptor(const std::string& name,
 51								   const std::string& size, 
 52								   const U8 style,
 53								   const string_vec_t& file_names):
 54	mName(name),
 55	mSize(size),
 56	mStyle(style),
 57	mFileNames(file_names)
 58{
 59}
 60
 61LLFontDescriptor::LLFontDescriptor(const std::string& name,
 62								   const std::string& size, 
 63								   const U8 style):
 64	mName(name),
 65	mSize(size),
 66	mStyle(style)
 67{
 68}
 69
 70
 71bool LLFontDescriptor::operator<(const LLFontDescriptor& b) const
 72{
 73	if (mName < b.mName)
 74		return true;
 75	else if (mName > b.mName)
 76		return false;
 77	
 78	if (mStyle < b.mStyle)
 79		return true;
 80	else if (mStyle > b.mStyle)
 81		return false;
 82
 83	if (mSize < b.mSize)
 84		return true;
 85	else 
 86		return false;
 87}
 88
 89static const std::string s_template_string("TEMPLATE");
 90
 91bool LLFontDescriptor::isTemplate() const
 92{
 93	return getSize() == s_template_string;
 94}
 95
 96// Look for substring match and remove substring if matched.
 97bool removeSubString(std::string& str, const std::string& substr)
 98{
 99	size_t pos = str.find(substr);
100	if (pos != string::npos)
101	{
102		str.erase(pos, substr.size());
103		return true;
104	}
105	return false;
106}
107
108// Check for substring match without modifying the source string.
109bool findSubString(std::string& str, const std::string& substr)
110{
111	size_t pos = str.find(substr);
112	if (pos != string::npos)
113	{
114		return true;
115	}
116	return false;
117}
118
119
120// Normal form is
121// - raw name
122// - bold, italic style info reflected in both style and font name.
123// - other style info removed.
124// - size info moved to mSize, defaults to Medium
125// For example,
126// - "SansSerifHuge" would normalize to { "SansSerif", "Huge", 0 }
127// - "SansSerifBold" would normalize to { "SansSerifBold", "Medium", BOLD }
128LLFontDescriptor LLFontDescriptor::normalize() const
129{
130	std::string new_name(mName);
131	std::string new_size(mSize);
132	U8 new_style(mStyle);
133
134	// Only care about style to extent it can be picked up by font.
135	new_style &= (LLFontGL::BOLD | LLFontGL::ITALIC);
136
137	// All these transformations are to support old-style font specifications.
138	if (removeSubString(new_name,"Small"))
139		new_size = "Small";
140	if (removeSubString(new_name,"Big"))
141		new_size = "Large";
142	if (removeSubString(new_name,"Medium"))
143		new_size = "Medium";
144	if (removeSubString(new_name,"Large"))
145		new_size = "Large";
146	if (removeSubString(new_name,"Huge"))
147		new_size = "Huge";
148
149	// HACK - Monospace is the only one we don't remove, so
150	// name "Monospace" doesn't get taken down to ""
151	// For other fonts, there's no ambiguity between font name and size specifier.
152	if (new_size != s_template_string && new_size.empty() && findSubString(new_name,"Monospace"))
153		new_size = "Monospace";
154	if (new_size.empty())
155		new_size = "Medium";
156
157	if (removeSubString(new_name,"Bold"))
158		new_style |= LLFontGL::BOLD;
159
160	if (removeSubString(new_name,"Italic"))
161		new_style |= LLFontGL::ITALIC;
162
163	return LLFontDescriptor(new_name,new_size,new_style,getFileNames());
164}
165
166LLFontRegistry::LLFontRegistry(const string_vec_t& xui_paths,
167							   bool create_gl_textures)
168:	mCreateGLTextures(create_gl_textures)
169{
170	// Propagate this down from LLUICtrlFactory so LLRender doesn't
171	// need an upstream dependency on LLUI.
172	mXUIPaths = xui_paths;
173	
174	// This is potentially a slow directory traversal, so we want to
175	// cache the result.
176	mUltimateFallbackList = LLWindow::getDynamicFallbackFontList();
177}
178
179LLFontRegistry::~LLFontRegistry()
180{
181	clear();
182}
183
184bool LLFontRegistry::parseFontInfo(const std::string& xml_filename)
185{
186	bool success = false;  // Succeed if we find at least one XUI file
187	const string_vec_t& xml_paths = mXUIPaths;
188	for (string_vec_t::const_iterator path_it = xml_paths.begin();
189		 path_it != xml_paths.end();
190		 ++path_it)
191	{
192	
193		LLXMLNodePtr root;
194		std::string full_filename = gDirUtilp->findSkinnedFilename(*path_it, xml_filename);
195		bool parsed_file = LLXMLNode::parseFile(full_filename, root, NULL);
196
197		if (!parsed_file)
198			continue;
199		
200		if ( root.isNull() || ! root->hasName( "fonts" ) )
201		{
202			llwarns << "Bad font info file: "
203					<< full_filename << llendl;
204			continue;
205		}
206		
207		std::string root_name;
208		root->getAttributeString("name",root_name);
209		if (root->hasName("fonts"))
210		{
211			// Expect a collection of children consisting of "font" or "font_size" entries
212			bool init_succ = initFromXML(root);
213			success = success || init_succ;
214		}
215	}
216	//if (success)
217	//	dump();
218	
219	return success;
220}
221
222std::string currentOsName()
223{
224#if LL_WINDOWS
225	return "Windows";
226#elif LL_DARWIN
227	return "Mac";
228#elif LL_SDL
229	return "Linux";
230#else
231	return "";
232#endif
233}
234
235bool fontDescInitFromXML(LLXMLNodePtr node, LLFontDescriptor& desc)
236{
237	if (node->hasName("font"))
238	{
239		std::string attr_name;
240		if (node->getAttributeString("name",attr_name))
241		{
242			desc.setName(attr_name);
243		}
244
245		std::string attr_style;
246		if (node->getAttributeString("font_style",attr_style))
247		{
248			desc.setStyle(LLFontGL::getStyleFromString(attr_style));
249		}
250
251		desc.setSize(s_template_string);
252	}
253
254	LLXMLNodePtr child;	
255	for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
256	{
257		std::string child_name;
258		child->getAttributeString("name",child_name);
259		if (child->hasName("file"))
260		{
261			std::string font_file_name = child->getTextContents();
262			desc.getFileNames().push_back(font_file_name);
263		}
264		else if (child->hasName("os"))
265		{
266			if (child_name == currentOsName())
267			{
268				fontDescInitFromXML(child, desc);
269			}
270		}
271	}
272	return true;
273}
274
275bool LLFontRegistry::initFromXML(LLXMLNodePtr node)
276{
277	LLXMLNodePtr child;
278	
279	for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
280	{
281		std::string child_name;
282		child->getAttributeString("name",child_name);
283		if (child->hasName("font"))
284		{
285			LLFontDescriptor desc;
286			bool font_succ = fontDescInitFromXML(child, desc);
287			LLFontDescriptor norm_desc = desc.normalize();
288			if (font_succ)
289			{
290				// if this is the first time we've seen this font name,
291				// create a new template map entry for it.
292				const LLFontDescriptor *match_desc = getMatchingFontDesc(desc);
293				if (match_desc == NULL)
294				{
295					// Create a new entry (with no corresponding font).
296					mFontMap[norm_desc] = NULL;
297				}
298				// otherwise, find the existing entry and combine data. 
299				else
300				{
301					// Prepend files from desc.
302					// A little roundabout because the map key is const,
303					// so we have to fetch it, make a new map key, and
304					// replace the old entry.
305					string_vec_t match_file_names = match_desc->getFileNames();
306					match_file_names.insert(match_file_names.begin(),
307											desc.getFileNames().begin(),
308											desc.getFileNames().end());
309					LLFontDescriptor new_desc = *match_desc;
310					new_desc.getFileNames() = match_file_names;
311					mFontMap.erase(*match_desc);
312					mFontMap[new_desc] = NULL;
313				}
314			}
315		}
316		else if (child->hasName("font_size"))
317		{
318			std::string size_name;
319			F32 size_value;
320			if (child->getAttributeString("name",size_name) &&
321				child->getAttributeF32("size",size_value))
322			{
323				mFontSizes[size_name] = size_value;
324			}
325
326		}
327	}
328	return true;
329}
330
331bool LLFontRegistry::nameToSize(const std::string& size_name, F32& size)
332{
333	font_size_map_t::iterator it = mFontSizes.find(size_name);
334	if (it != mFontSizes.end())
335	{
336		size = it->second;
337		return true;
338	}
339	return false;
340}
341
342
343LLFontGL *LLFontRegistry::createFont(const LLFontDescriptor& desc)
344{
345	// Name should hold a font name recognized as a setting; the value
346	// of the setting should be a list of font files.
347	// Size should be a recognized string value
348	// Style should be a set of flags including any implied by the font name.
349
350	// First decipher the requested size.
351	LLFontDescriptor norm_desc = desc.normalize();
352	F32 point_size;
353	bool found_size = nameToSize(norm_desc.getSize(),point_size);
354	if (!found_size)
355	{
356		llwarns << "createFont unrecognized size " << norm_desc.getSize() << llendl;
357		return NULL;
358	}
359	llinfos << "createFont " << norm_desc.getName() << " size " << norm_desc.getSize() << " style " << ((S32) norm_desc.getStyle()) << llendl;
360	F32 fallback_scale = 1.0;
361
362	// Find corresponding font template (based on same descriptor with no size specified)
363	LLFontDescriptor template_desc(norm_desc);
364	template_desc.setSize(s_template_string);
365	const LLFontDescriptor *match_desc = getClosestFontTemplate(template_desc);
366	if (!match_desc)
367	{
368		llwarns << "createFont failed, no template found for "
369				<< norm_desc.getName() << " style [" << ((S32)norm_desc.getStyle()) << "]" << llendl;
370		return NULL;
371	}
372
373	// See whether this best-match font has already been instantiated in the requested size.
374	LLFontDescriptor nearest_exact_desc = *match_desc;
375	nearest_exact_desc.setSize(norm_desc.getSize());
376	font_reg_map_t::iterator it = mFontMap.find(nearest_exact_desc);
377	// If we fail to find a font in the fonts directory, it->second might be NULL.
378	// We shouldn't construcnt a font with a NULL mFontFreetype.
379	// This may not be the best solution, but it at least prevents a crash.
380	if (it != mFontMap.end() && it->second != NULL)
381	{
382		llinfos << "-- matching font exists: " << nearest_exact_desc.getName() << " size " << nearest_exact_desc.getSize() << " style " << ((S32) nearest_exact_desc.getStyle()) << llendl;
383		
384		// copying underlying Freetype font, and storing in LLFontGL with requested font descriptor
385		LLFontGL *font = new LLFontGL;
386		font->mFontDescriptor = desc;
387		font->mFontFreetype = it->second->mFontFreetype;
388		mFontMap[desc] = font;
389
390		return font;
391	}
392
393	// Build list of font names to look for.
394	// Files specified for this font come first, followed by those from the default descriptor.
395	string_vec_t file_names = match_desc->getFileNames();
396	string_vec_t default_file_names;
397	LLFontDescriptor default_desc("default",s_template_string,0);
398	const LLFontDescriptor *match_default_desc = getMatchingFontDesc(default_desc);
399	if (match_default_desc)
400	{
401		file_names.insert(file_names.end(),
402						  match_default_desc->getFileNames().begin(),
403						  match_default_desc->getFileNames().end());
404	}
405
406	// Add ultimate fallback list - generated dynamically on linux,
407	// null elsewhere.
408	file_names.insert(file_names.end(),
409					  getUltimateFallbackList().begin(),
410					  getUltimateFallbackList().end());
411
412	// Load fonts based on names.
413	if (file_names.empty())
414	{
415		llwarns << "createFont failed, no file names specified" << llendl;
416		return NULL;
417	}
418
419	LLFontFreetype::font_vector_t fontlist;
420	LLFontGL *result = NULL;
421
422	// Snarf all fonts we can into fontlist.  First will get pulled
423	// off the list and become the "head" font, set to non-fallback.
424	// Rest will consitute the fallback list.
425	BOOL is_first_found = TRUE;
426	
427	std::string local_path = LLFontGL::getFontPathLocal();
428	std::string sys_path = LLFontGL::getFontPathSystem();
429	
430	// The fontname string may contain multiple font file names separated by semicolons.
431	// Break it apart and try loading each one, in order.
432	for(string_vec_t::iterator file_name_it = file_names.begin();
433		file_name_it != file_names.end(); 
434		++file_name_it)
435	{
436		LLFontGL *fontp = new LLFontGL;
437		std::string font_path = local_path + *file_name_it;
438		// *HACK: Fallback fonts don't render, so we can use that to suppress
439		// creation of OpenGL textures for test apps. JC
440		BOOL is_fallback = !is_first_found || !mCreateGLTextures;
441		F32 extra_scale = (is_fallback)?fallback_scale:1.0;
442		if (!fontp->loadFace(font_path, extra_scale * point_size,
443							 LLFontGL::sVertDPI, LLFontGL::sHorizDPI, 2, is_fallback))
444		{
445			font_path = sys_path + *file_name_it;
446
447			if (!fontp->loadFace(font_path, extra_scale * point_size,
448								 LLFontGL::sVertDPI, LLFontGL::sHorizDPI, 2, is_fallback))
449			{
450				LL_INFOS_ONCE("LLFontRegistry") << "Couldn't load font " << *file_name_it << LL_ENDL;
451				delete fontp;
452				fontp = NULL;
453			}
454		}
455		
456		if(fontp)
457		{
458			if (is_first_found)
459			{
460				result = fontp;
461				is_first_found = false;
462			}
463			else
464			{
465				fontlist.push_back(fontp->mFontFreetype);
466				delete fontp;
467				fontp = NULL;
468			}
469		}
470	}
471
472	if (result && !fontlist.empty())
473	{
474		result->mFontFreetype->setFallbackFonts(fontlist);
475	}
476
477	if (result)
478	{
479		result->mFontDescriptor = desc;
480	}
481	else
482	{
483		llwarns << "createFont failed in some way" << llendl;
484	}
485
486	mFontMap[desc] = result;
487	return result;
488}
489
490void LLFontRegistry::reset()
491{
492	for (font_reg_map_t::iterator it = mFontMap.begin();
493		 it != mFontMap.end();
494		 ++it)
495	{
496		// Reset the corresponding font but preserve the entry.
497		if (it->second)
498			it->second->reset();
499	}
500}
501
502void LLFontRegistry::clear()
503{
504	for (font_reg_map_t::iterator it = mFontMap.begin();
505		 it != mFontMap.end();
506		 ++it)
507	{
508		LLFontGL *fontp = it->second;
509		delete fontp;
510	}
511	mFontMap.clear();
512}
513
514void LLFontRegistry::destroyGL()
515{
516	for (font_reg_map_t::iterator it = mFontMap.begin();
517		 it != mFontMap.end();
518		 ++it)
519	{
520		// Reset the corresponding font but preserve the entry.
521		if (it->second)
522			it->second->destroyGL();
523	}
524}
525
526LLFontGL *LLFontRegistry::getFont(const LLFontDescriptor& desc)
527{
528	font_reg_map_t::iterator it = mFontMap.find(desc);
529	if (it != mFontMap.end())
530		return it->second;
531	else
532	{
533		LLFontGL *fontp = createFont(desc);
534		if (!fontp)
535		{
536			llwarns << "getFont failed, name " << desc.getName()
537					<<" style=[" << ((S32) desc.getStyle()) << "]"
538					<< " size=[" << desc.getSize() << "]" << llendl;
539		}
540		return fontp;
541	}
542}
543
544const LLFontDescriptor *LLFontRegistry::getMatchingFontDesc(const LLFontDescriptor& desc)
545{
546	LLFontDescriptor norm_desc = desc.normalize();
547
548	font_reg_map_t::iterator it = mFontMap.find(norm_desc);
549	if (it != mFontMap.end())
550		return &(it->first);
551	else
552		return NULL;
553}
554
555static U32 bitCount(U8 c)
556{
557	U32 count = 0;
558	if (c & 1) 
559		count++;
560	if (c & 2)
561		count++;
562	if (c & 4)
563		count++;
564	if (c & 8)
565		count++;
566	if (c & 16)
567		count++;
568	if (c & 32)
569		count++;
570	if (c & 64)
571		count++;
572	if (c & 128)
573		count++;
574	return count;
575}
576
577// Find nearest match for the requested descriptor.
578const LLFontDescriptor *LLFontRegistry::getClosestFontTemplate(const LLFontDescriptor& desc)
579{
580	const LLFontDescriptor *exact_match_desc = getMatchingFontDesc(desc);
581	if (exact_match_desc)
582	{
583		return exact_match_desc;
584	}
585
586	LLFontDescriptor norm_desc = desc.normalize();
587
588	const LLFontDescriptor *best_match_desc = NULL;
589	for (font_reg_map_t::iterator it = mFontMap.begin();
590		 it != mFontMap.end();
591		 ++it)
592	{
593		const LLFontDescriptor* curr_desc = &(it->first);
594
595		// Ignore if not a template.
596		if (!curr_desc->isTemplate())
597			continue;
598
599		// Ignore if font name is wrong.
600		if (curr_desc->getName() != norm_desc.getName())
601			continue;
602
603		// Reject font if it matches any bits we don't want
604		if (curr_desc->getStyle() & ~norm_desc.getStyle())
605		{
606			continue;
607		}
608
609		// Take if it's the first plausible candidate we've found.
610		if (!best_match_desc)
611		{
612			best_match_desc = curr_desc;
613			continue;
614		}
615
616		// Take if it matches more bits than anything before.
617		U8 best_style_match_bits =
618			norm_desc.getStyle() & best_match_desc->getStyle();
619		U8 curr_style_match_bits =
620			norm_desc.getStyle() & curr_desc->getStyle();
621		if (bitCount(curr_style_match_bits) > bitCount(best_style_match_bits))
622		{
623			best_match_desc = curr_desc;
624			continue;
625		}
626
627		// Tie-breaker: take if it matches bold.
628		if (curr_style_match_bits & LLFontGL::BOLD)  // Bold is requested and this descriptor matches it.
629		{
630			best_match_desc = curr_desc;
631			continue;
632		}
633	}
634
635	// Nothing matched.
636	return best_match_desc;
637}
638
639void LLFontRegistry::dump()
640{
641	llinfos << "LLFontRegistry dump: " << llendl;
642	for (font_size_map_t::iterator size_it = mFontSizes.begin();
643		 size_it != mFontSizes.end();
644		 ++size_it)
645	{
646		llinfos << "Size: " << size_it->first << " => " << size_it->second << llendl;
647	}
648	for (font_reg_map_t::iterator font_it = mFontMap.begin();
649		 font_it != mFontMap.end();
650		 ++font_it)
651	{
652		const LLFontDescriptor& desc = font_it->first;
653		llinfos << "Font: name=" << desc.getName()
654				<< " style=[" << ((S32)desc.getStyle()) << "]"
655				<< " size=[" << desc.getSize() << "]"
656				<< " fileNames="
657				<< llendl;
658		for (string_vec_t::const_iterator file_it=desc.getFileNames().begin();
659			 file_it != desc.getFileNames().end();
660			 ++file_it)
661		{
662			llinfos << "  file: " << *file_it <<llendl;
663		}
664	}
665}
666
667const string_vec_t& LLFontRegistry::getUltimateFallbackList() const 
668{ 
669	return mUltimateFallbackList;
670}