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