1 /* 2 * Copyright 2011 The Android Open Source Project 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkFontConfigParser_android.h" 9 #include "SkTDArray.h" 10 #include "SkTSearch.h" 11 #include "SkTypeface.h" 12 13 #include <expat.h> 14 #include <dirent.h> 15 #include <stdio.h> 16 17 #include <limits> 18 19 20 21 // From Android version LMP onwards, all font files collapse into 22 // /system/etc/fonts.xml. Instead of trying to detect which version 23 // we're on, try to open fonts.xml; if that fails, fall back to the 24 // older filename. 25 #define LMP_SYSTEM_FONTS_FILE "/system/etc/fonts.xml" 26 #define OLD_SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml" 27 #define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml" 28 #define VENDOR_FONTS_FILE "/vendor/etc/fallback_fonts.xml" 29 30 #define LOCALE_FALLBACK_FONTS_SYSTEM_DIR "/system/etc" 31 #define LOCALE_FALLBACK_FONTS_VENDOR_DIR "/vendor/etc" 32 #define LOCALE_FALLBACK_FONTS_PREFIX "fallback_fonts-" 33 #define LOCALE_FALLBACK_FONTS_SUFFIX ".xml" 34 35 /** 36 * This file contains TWO parsers: one for JB and earlier (system_fonts.xml / 37 * fallback_fonts.xml), one for LMP and later (fonts.xml). 38 * We start with the JB parser, and if we detect a <familyset> tag with 39 * version 21 or higher we switch to the LMP parser. 40 */ 41 42 // These defines are used to determine the kind of tag that we're currently 43 // populating with data. We only care about the sibling tags nameset and fileset 44 // for now. 45 #define NO_TAG 0 46 #define NAMESET_TAG 1 47 #define FILESET_TAG 2 48 49 /** 50 * The FamilyData structure is passed around by the parser so that each handler 51 * can read these variables that are relevant to the current parsing. 52 */ 53 struct FamilyData { 54 FamilyData(XML_Parser* parserRef, SkTDArray<FontFamily*> &familiesRef) : 55 parser(parserRef), 56 families(familiesRef), 57 currentFamily(NULL), 58 currentFontInfo(NULL), 59 currentTag(NO_TAG) {}; 60 61 XML_Parser* parser; // The expat parser doing the work 62 SkTDArray<FontFamily*> &families; // The array that each family is put into as it is parsed 63 FontFamily* currentFamily; // The current family being created 64 FontFileInfo* currentFontInfo; // The current fontInfo being created 65 int currentTag; // A flag to indicate whether we're in nameset/fileset tags 66 }; 67 68 /** http://www.w3.org/TR/html-markup/datatypes.html#common.data.integer.non-negative-def */ 69 template <typename T> static bool parseNonNegativeInteger(const char* s, T* value) { 70 SK_COMPILE_ASSERT(std::numeric_limits<T>::is_integer, T_must_be_integer); 71 const T nMax = std::numeric_limits<T>::max() / 10; 72 const T dMax = std::numeric_limits<T>::max() - (nMax * 10); 73 T n = 0; 74 for (; *s; ++s) { 75 // Check if digit 76 if (*s < '0' || '9' < *s) { 77 return false; 78 } 79 int d = *s - '0'; 80 // Check for overflow 81 if (n > nMax || (n == nMax && d > dMax)) { 82 return false; 83 } 84 n = (n * 10) + d; 85 } 86 *value = n; 87 return true; 88 } 89 90 namespace lmpParser { 91 92 void familyElementHandler(FontFamily* family, const char** attributes) { 93 // A non-fallback <family> tag must have a canonical name attribute. 94 // A fallback <family> tag has no name, and may have lang and variant 95 // attributes. 96 family->fIsFallbackFont = true; 97 for (size_t i = 0; attributes[i] != NULL && 98 attributes[i+1] != NULL; i += 2) { 99 const char* name = attributes[i]; 100 const char* value = attributes[i+1]; 101 size_t nameLen = strlen(name); 102 size_t valueLen = strlen(value); 103 if (nameLen == 4 && !strncmp("name", name, nameLen)) { 104 SkAutoAsciiToLC tolc(value); 105 family->fNames.push_back().set(tolc.lc()); 106 family->fIsFallbackFont = false; 107 } else if (nameLen == 4 && !strncmp("lang", name, nameLen)) { 108 family->fLanguage = SkLanguage (value); 109 } else if (nameLen == 7 && !strncmp("variant", name, nameLen)) { 110 // Value should be either elegant or compact. 111 if (valueLen == 7 && !strncmp("elegant", value, valueLen)) { 112 family->fVariant = kElegant_FontVariant; 113 } else if (valueLen == 7 && !strncmp("compact", value, valueLen)) { 114 family->fVariant = kCompact_FontVariant; 115 } 116 } 117 } 118 } 119 120 void fontFileNameHandler(void* data, const char* s, int len) { 121 FamilyData* familyData = (FamilyData*) data; 122 familyData->currentFontInfo->fFileName.set(s, len); 123 } 124 125 void fontElementHandler(XML_Parser* parser, FontFileInfo* file, const char** attributes) { 126 // A <font> should have weight (integer) and style (normal, italic) attributes. 127 // NOTE: we ignore the style. 128 // The element should contain a filename. 129 for (size_t i = 0; attributes[i] != NULL && 130 attributes[i+1] != NULL; i += 2) { 131 const char* name = attributes[i]; 132 const char* value = attributes[i+1]; 133 size_t nameLen = strlen(name); 134 if (nameLen == 6 && !strncmp("weight", name, nameLen)) { 135 if (!parseNonNegativeInteger(value, &file->fWeight)) { 136 SkDebugf("---- Font weight %s (INVALID)", value); 137 file->fWeight = 0; 138 } 139 } 140 } 141 XML_SetCharacterDataHandler(*parser, fontFileNameHandler); 142 } 143 144 FontFamily* findFamily(FamilyData* familyData, const char* familyName) { 145 size_t nameLen = strlen(familyName); 146 for (int i = 0; i < familyData->families.count(); i++) { 147 FontFamily* candidate = familyData->families[i]; 148 for (int j = 0; j < candidate->fNames.count(); j++) { 149 if (!strncmp(candidate->fNames[j].c_str(), familyName, nameLen) && 150 nameLen == strlen(candidate->fNames[j].c_str())) { 151 return candidate; 152 } 153 } 154 } 155 156 return NULL; 157 } 158 159 void aliasElementHandler(FamilyData* familyData, const char** attributes) { 160 // An <alias> must have name and to attributes. 161 // It may have weight (integer). 162 // If it *does not* have a weight, it is a variant name for a <family>. 163 // If it *does* have a weight, it names the <font>(s) of a specific weight 164 // from a <family>. 165 166 SkString aliasName; 167 SkString to; 168 int weight = 0; 169 for (size_t i = 0; attributes[i] != NULL && 170 attributes[i+1] != NULL; i += 2) { 171 const char* name = attributes[i]; 172 const char* value = attributes[i+1]; 173 size_t nameLen = strlen(name); 174 if (nameLen == 4 && !strncmp("name", name, nameLen)) { 175 SkAutoAsciiToLC tolc(value); 176 aliasName.set(tolc.lc()); 177 } else if (nameLen == 2 && !strncmp("to", name, nameLen)) { 178 to.set(value); 179 } else if (nameLen == 6 && !strncmp("weight", name, nameLen)) { 180 parseNonNegativeInteger(value, &weight); 181 } 182 } 183 184 // Assumes that the named family is already declared 185 FontFamily* targetFamily = findFamily(familyData, to.c_str()); 186 if (!targetFamily) { 187 SkDebugf("---- Font alias target %s (NOT FOUND)", to.c_str()); 188 return; 189 } 190 191 if (weight) { 192 FontFamily* family = new FontFamily(); 193 family->fNames.push_back().set(aliasName); 194 195 for (int i = 0; i < targetFamily->fFonts.count(); i++) { 196 if (targetFamily->fFonts[i].fWeight == weight) { 197 family->fFonts.push_back(targetFamily->fFonts[i]); 198 } 199 } 200 *familyData->families.append() = family; 201 } else { 202 targetFamily->fNames.push_back().set(aliasName); 203 } 204 } 205 206 bool findWeight400(FontFamily* family) { 207 for (int i = 0; i < family->fFonts.count(); i++) { 208 if (family->fFonts[i].fWeight == 400) { 209 return true; 210 } 211 } 212 return false; 213 } 214 215 bool desiredWeight(int weight) { 216 return (weight == 400 || weight == 700); 217 } 218 219 int countDesiredWeight(FontFamily* family) { 220 int count = 0; 221 for (int i = 0; i < family->fFonts.count(); i++) { 222 if (desiredWeight(family->fFonts[i].fWeight)) { 223 count++; 224 } 225 } 226 return count; 227 } 228 229 // To meet Skia's expectations, any family that contains weight=400 230 // fonts should *only* contain {400,700} 231 void purgeUndesiredWeights(FontFamily* family) { 232 int count = countDesiredWeight(family); 233 for (int i = 1, j = 0; i < family->fFonts.count(); i++) { 234 if (desiredWeight(family->fFonts[j].fWeight)) { 235 j++; 236 } 237 if ((i != j) && desiredWeight(family->fFonts[i].fWeight)) { 238 family->fFonts[j] = family->fFonts[i]; 239 } 240 } 241 family->fFonts.resize_back(count); 242 } 243 244 void familysetElementEndHandler(FamilyData* familyData) { 245 for (int i = 0; i < familyData->families.count(); i++) { 246 if (findWeight400(familyData->families[i])) { 247 purgeUndesiredWeights(familyData->families[i]); 248 } 249 } 250 } 251 252 void startElementHandler(void* data, const char* tag, 253 const char** attributes) { 254 FamilyData* familyData = (FamilyData*) data; 255 size_t len = strlen(tag); 256 if (len == 6 && !strncmp(tag, "family", len)) { 257 familyData->currentFamily = new FontFamily(); 258 familyElementHandler(familyData->currentFamily, attributes); 259 } else if (len == 4 && !strncmp(tag, "font", len)) { 260 FontFileInfo* file = &familyData->currentFamily->fFonts.push_back(); 261 familyData->currentFontInfo = file; 262 fontElementHandler(familyData->parser, file, attributes); 263 } else if (len == 5 && !strncmp(tag, "alias", len)) { 264 aliasElementHandler(familyData, attributes); 265 } 266 } 267 268 void endElementHandler(void* data, const char* tag) { 269 FamilyData* familyData = (FamilyData*) data; 270 size_t len = strlen(tag); 271 if (len == 9 && strncmp(tag, "familyset", len) == 0) { 272 familysetElementEndHandler(familyData); 273 } else if (len == 6 && strncmp(tag, "family", len) == 0) { 274 *familyData->families.append() = familyData->currentFamily; 275 familyData->currentFamily = NULL; 276 } else if (len == 4 && !strncmp(tag, "font", len)) { 277 XML_SetCharacterDataHandler(*familyData->parser, NULL); 278 } 279 } 280 281 } // lmpParser 282 283 namespace jbParser { 284 285 /** 286 * Handler for arbitrary text. This is used to parse the text inside each name 287 * or file tag. The resulting strings are put into the fNames or FontFileInfo arrays. 288 */ 289 static void textHandler(void* data, const char* s, int len) { 290 FamilyData* familyData = (FamilyData*) data; 291 // Make sure we're in the right state to store this name information 292 if (familyData->currentFamily && 293 (familyData->currentTag == NAMESET_TAG || familyData->currentTag == FILESET_TAG)) { 294 switch (familyData->currentTag) { 295 case NAMESET_TAG: { 296 SkAutoAsciiToLC tolc(s, len); 297 familyData->currentFamily->fNames.push_back().set(tolc.lc(), len); 298 break; 299 } 300 case FILESET_TAG: 301 if (familyData->currentFontInfo) { 302 familyData->currentFontInfo->fFileName.set(s, len); 303 } 304 break; 305 default: 306 // Noop - don't care about any text that's not in the Fonts or Names list 307 break; 308 } 309 } 310 } 311 312 /** 313 * Handler for font files. This processes the attributes for language and 314 * variants then lets textHandler handle the actual file name 315 */ 316 static void fontFileElementHandler(FamilyData* familyData, const char** attributes) { 317 FontFileInfo& newFileInfo = familyData->currentFamily->fFonts.push_back(); 318 if (attributes) { 319 size_t currentAttributeIndex = 0; 320 while (attributes[currentAttributeIndex] && 321 attributes[currentAttributeIndex + 1]) { 322 const char* attributeName = attributes[currentAttributeIndex]; 323 const char* attributeValue = attributes[currentAttributeIndex+1]; 324 size_t nameLength = strlen(attributeName); 325 size_t valueLength = strlen(attributeValue); 326 if (nameLength == 7 && strncmp(attributeName, "variant", nameLength) == 0) { 327 const FontVariant prevVariant = familyData->currentFamily->fVariant; 328 if (valueLength == 7 && strncmp(attributeValue, "elegant", valueLength) == 0) { 329 familyData->currentFamily->fVariant = kElegant_FontVariant; 330 } else if (valueLength == 7 && 331 strncmp(attributeValue, "compact", valueLength) == 0) { 332 familyData->currentFamily->fVariant = kCompact_FontVariant; 333 } 334 if (familyData->currentFamily->fFonts.count() > 1 && 335 familyData->currentFamily->fVariant != prevVariant) { 336 SkDebugf("Every font file within a family must have identical variants"); 337 sk_throw(); 338 } 339 340 } else if (nameLength == 4 && strncmp(attributeName, "lang", nameLength) == 0) { 341 SkLanguage prevLang = familyData->currentFamily->fLanguage; 342 familyData->currentFamily->fLanguage = SkLanguage(attributeValue); 343 if (familyData->currentFamily->fFonts.count() > 1 && 344 familyData->currentFamily->fLanguage != prevLang) { 345 SkDebugf("Every font file within a family must have identical languages"); 346 sk_throw(); 347 } 348 } else if (nameLength == 5 && strncmp(attributeName, "index", nameLength) == 0) { 349 int value; 350 if (parseNonNegativeInteger(attributeValue, &value)) { 351 newFileInfo.fIndex = value; 352 } else { 353 SkDebugf("---- SystemFonts index=%s (INVALID)", attributeValue); 354 } 355 } 356 //each element is a pair of attributeName/attributeValue string pairs 357 currentAttributeIndex += 2; 358 } 359 } 360 familyData->currentFontInfo = &newFileInfo; 361 XML_SetCharacterDataHandler(*familyData->parser, textHandler); 362 } 363 364 /** 365 * Handler for the start of a tag. The only tags we expect are familyset, family, 366 * nameset, fileset, name, and file. 367 */ 368 static void startElementHandler(void* data, const char* tag, const char** atts) { 369 FamilyData* familyData = (FamilyData*) data; 370 size_t len = strlen(tag); 371 if (len == 9 && strncmp(tag, "familyset", len) == 0) { 372 // The familyset tag has an optional "version" attribute with an integer value >= 0 373 for (size_t i = 0; atts[i] != NULL && 374 atts[i+1] != NULL; i += 2) { 375 size_t nameLen = strlen(atts[i]); 376 if (nameLen == 7 && strncmp(atts[i], "version", nameLen)) continue; 377 const char* valueString = atts[i+1]; 378 int version; 379 if (parseNonNegativeInteger(valueString, &version) && (version >= 21)) { 380 XML_SetElementHandler(*familyData->parser, 381 lmpParser::startElementHandler, 382 lmpParser::endElementHandler); 383 } 384 } 385 } else if (len == 6 && strncmp(tag, "family", len) == 0) { 386 familyData->currentFamily = new FontFamily(); 387 // The Family tag has an optional "order" attribute with an integer value >= 0 388 // If this attribute does not exist, the default value is -1 389 for (size_t i = 0; atts[i] != NULL && 390 atts[i+1] != NULL; i += 2) { 391 const char* valueString = atts[i+1]; 392 int value; 393 if (parseNonNegativeInteger(valueString, &value)) { 394 familyData->currentFamily->fOrder = value; 395 } 396 } 397 } else if (len == 7 && strncmp(tag, "nameset", len) == 0) { 398 familyData->currentTag = NAMESET_TAG; 399 } else if (len == 7 && strncmp(tag, "fileset", len) == 0) { 400 familyData->currentTag = FILESET_TAG; 401 } else if (len == 4 && strncmp(tag, "name", len) == 0 && familyData->currentTag == NAMESET_TAG) { 402 // If it's a Name, parse the text inside 403 XML_SetCharacterDataHandler(*familyData->parser, textHandler); 404 } else if (len == 4 && strncmp(tag, "file", len) == 0 && familyData->currentTag == FILESET_TAG) { 405 // If it's a file, parse the attributes, then parse the text inside 406 fontFileElementHandler(familyData, atts); 407 } 408 } 409 410 /** 411 * Handler for the end of tags. We only care about family, nameset, fileset, 412 * name, and file. 413 */ 414 static void endElementHandler(void* data, const char* tag) { 415 FamilyData* familyData = (FamilyData*) data; 416 size_t len = strlen(tag); 417 if (len == 6 && strncmp(tag, "family", len)== 0) { 418 // Done parsing a Family - store the created currentFamily in the families array 419 *familyData->families.append() = familyData->currentFamily; 420 familyData->currentFamily = NULL; 421 } else if (len == 7 && strncmp(tag, "nameset", len) == 0) { 422 familyData->currentTag = NO_TAG; 423 } else if (len == 7 && strncmp(tag, "fileset", len) == 0) { 424 familyData->currentTag = NO_TAG; 425 } else if ((len == 4 && 426 strncmp(tag, "name", len) == 0 && 427 familyData->currentTag == NAMESET_TAG) || 428 (len == 4 && 429 strncmp(tag, "file", len) == 0 && 430 familyData->currentTag == FILESET_TAG)) { 431 // Disable the arbitrary text handler installed to load Name data 432 XML_SetCharacterDataHandler(*familyData->parser, NULL); 433 } 434 } 435 436 } // namespace jbParser 437 438 /** 439 * This function parses the given filename and stores the results in the given 440 * families array. 441 */ 442 static void parseConfigFile(const char* filename, SkTDArray<FontFamily*> &families) { 443 444 FILE* file = fopen(filename, "r"); 445 446 // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml) 447 // are optional - failure here is okay because one of these optional files may not exist. 448 if (NULL == file) { 449 return; 450 } 451 452 XML_Parser parser = XML_ParserCreate(NULL); 453 FamilyData* familyData = new FamilyData(&parser, families); 454 XML_SetUserData(parser, familyData); 455 // Start parsing oldschool; switch these in flight if we detect a newer version of the file. 456 XML_SetElementHandler(parser, jbParser::startElementHandler, jbParser::endElementHandler); 457 458 char buffer[512]; 459 bool done = false; 460 while (!done) { 461 fgets(buffer, sizeof(buffer), file); 462 size_t len = strlen(buffer); 463 if (feof(file) != 0) { 464 done = true; 465 } 466 XML_Parse(parser, buffer, len, done); 467 } 468 XML_ParserFree(parser); 469 fclose(file); 470 } 471 472 static void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies) { 473 int initialCount = fontFamilies.count(); 474 parseConfigFile(LMP_SYSTEM_FONTS_FILE, fontFamilies); 475 476 if (initialCount == fontFamilies.count()) { 477 parseConfigFile(OLD_SYSTEM_FONTS_FILE, fontFamilies); 478 } 479 } 480 481 /** 482 * In some versions of Android prior to Android 4.2 (JellyBean MR1 at API 483 * Level 17) the fallback fonts for certain locales were encoded in their own 484 * XML files with a suffix that identified the locale. We search the provided 485 * directory for those files,add all of their entries to the fallback chain, and 486 * include the locale as part of each entry. 487 */ 488 static void getFallbackFontFamiliesForLocale(SkTDArray<FontFamily*> &fallbackFonts, const char* dir) { 489 #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) 490 // The framework is beyond Android 4.2 and can therefore skip this function 491 return; 492 #endif 493 494 DIR* fontDirectory = opendir(dir); 495 if (fontDirectory != NULL){ 496 struct dirent* dirEntry = readdir(fontDirectory); 497 while (dirEntry) { 498 499 // The size of both the prefix, suffix, and a minimum valid language code 500 static const size_t minSize = strlen(LOCALE_FALLBACK_FONTS_PREFIX) + 501 strlen(LOCALE_FALLBACK_FONTS_SUFFIX) + 2; 502 503 SkString fileName(dirEntry->d_name); 504 if (fileName.size() >= minSize && 505 fileName.startsWith(LOCALE_FALLBACK_FONTS_PREFIX) && 506 fileName.endsWith(LOCALE_FALLBACK_FONTS_SUFFIX)) { 507 508 static const size_t fixedLen = strlen(LOCALE_FALLBACK_FONTS_PREFIX) - 509 strlen(LOCALE_FALLBACK_FONTS_SUFFIX); 510 511 SkString locale(fileName.c_str() - strlen(LOCALE_FALLBACK_FONTS_PREFIX), 512 fileName.size() - fixedLen); 513 514 SkString absoluteFilename; 515 absoluteFilename.printf("%s/%s", dir, fileName.c_str()); 516 517 SkTDArray<FontFamily*> langSpecificFonts; 518 parseConfigFile(absoluteFilename.c_str(), langSpecificFonts); 519 520 for (int i = 0; i < langSpecificFonts.count(); ++i) { 521 FontFamily* family = langSpecificFonts[i]; 522 family->fLanguage = SkLanguage(locale); 523 *fallbackFonts.append() = family; 524 } 525 } 526 527 // proceed to the next entry in the directory 528 dirEntry = readdir(fontDirectory); 529 } 530 // cleanup the directory reference 531 closedir(fontDirectory); 532 } 533 } 534 535 static void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts) { 536 SkTDArray<FontFamily*> vendorFonts; 537 parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts); 538 parseConfigFile(VENDOR_FONTS_FILE, vendorFonts); 539 540 getFallbackFontFamiliesForLocale(fallbackFonts, LOCALE_FALLBACK_FONTS_SYSTEM_DIR); 541 getFallbackFontFamiliesForLocale(vendorFonts, LOCALE_FALLBACK_FONTS_VENDOR_DIR); 542 543 // This loop inserts the vendor fallback fonts in the correct order in the 544 // overall fallbacks list. 545 int currentOrder = -1; 546 for (int i = 0; i < vendorFonts.count(); ++i) { 547 FontFamily* family = vendorFonts[i]; 548 int order = family->fOrder; 549 if (order < 0) { 550 if (currentOrder < 0) { 551 // Default case - just add it to the end of the fallback list 552 *fallbackFonts.append() = family; 553 } else { 554 // no order specified on this font, but we're incrementing the order 555 // based on an earlier order insertion request 556 *fallbackFonts.insert(currentOrder++) = family; 557 } 558 } else { 559 // Add the font into the fallback list in the specified order. Set 560 // currentOrder for correct placement of other fonts in the vendor list. 561 *fallbackFonts.insert(order) = family; 562 currentOrder = order + 1; 563 } 564 } 565 } 566 567 /** 568 * Loads data on font families from various expected configuration files. The 569 * resulting data is returned in the given fontFamilies array. 570 */ 571 void SkFontConfigParser::GetFontFamilies(SkTDArray<FontFamily*> &fontFamilies) { 572 573 getSystemFontFamilies(fontFamilies); 574 575 // Append all the fallback fonts to system fonts 576 SkTDArray<FontFamily*> fallbackFonts; 577 getFallbackFontFamilies(fallbackFonts); 578 for (int i = 0; i < fallbackFonts.count(); ++i) { 579 fallbackFonts[i]->fIsFallbackFont = true; 580 *fontFamilies.append() = fallbackFonts[i]; 581 } 582 } 583 584 void SkFontConfigParser::GetTestFontFamilies(SkTDArray<FontFamily*> &fontFamilies, 585 const char* testMainConfigFile, 586 const char* testFallbackConfigFile) { 587 parseConfigFile(testMainConfigFile, fontFamilies); 588 589 SkTDArray<FontFamily*> fallbackFonts; 590 if (testFallbackConfigFile) { 591 parseConfigFile(testFallbackConfigFile, fallbackFonts); 592 } 593 594 // Append all fallback fonts to system fonts 595 for (int i = 0; i < fallbackFonts.count(); ++i) { 596 fallbackFonts[i]->fIsFallbackFont = true; 597 *fontFamilies.append() = fallbackFonts[i]; 598 } 599 } 600 601 SkLanguage SkLanguage::getParent() const { 602 SkASSERT(!fTag.isEmpty()); 603 const char* tag = fTag.c_str(); 604 605 // strip off the rightmost "-.*" 606 const char* parentTagEnd = strrchr(tag, '-'); 607 if (parentTagEnd == NULL) { 608 return SkLanguage(); 609 } 610 size_t parentTagLen = parentTagEnd - tag; 611 return SkLanguage(tag, parentTagLen); 612 } 613