Merge "Remove FontFamily.addFont and make FontFamily immutable."

This commit is contained in:
TreeHugger Robot 2017-01-12 07:43:49 +00:00 committed by Android (Google) Code Review
commit bdb50a7cc5
9 changed files with 139 additions and 164 deletions

View File

@ -98,64 +98,61 @@ struct FakedFont {
FontFakery fakery;
};
struct Font {
Font(MinikinFont* typeface, FontStyle style);
Font(Font&& o);
Font(const Font& o);
~Font();
MinikinFont* typeface;
FontStyle style;
};
class FontFamily : public MinikinRefCounted {
public:
FontFamily();
explicit FontFamily(int variant);
FontFamily(uint32_t langId, int variant)
: mLangId(langId),
mVariant(variant),
mHasVSTable(false),
mCoverageValid(false) {
}
explicit FontFamily(std::vector<Font>&& fonts);
FontFamily(int variant, std::vector<Font>&& fonts);
FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts);
~FontFamily();
// Add font to family, extracting style information from the font
bool addFont(MinikinFont* typeface);
// TODO: Good to expose FontUtil.h.
static bool analyzeStyle(MinikinFont* typeface, int* weight, bool* italic);
void addFont(MinikinFont* typeface, FontStyle style);
FakedFont getClosestMatch(FontStyle style) const;
uint32_t langId() const { return mLangId; }
int variant() const { return mVariant; }
// API's for enumerating the fonts in a family. These don't guarantee any particular order
size_t getNumFonts() const;
MinikinFont* getFont(size_t index) const;
FontStyle getStyle(size_t index) const;
size_t getNumFonts() const { return mFonts.size(); }
MinikinFont* getFont(size_t index) const { return mFonts[index].typeface; }
FontStyle getStyle(size_t index) const { return mFonts[index].style; }
bool isColorEmojiFamily() const;
// Get Unicode coverage. Lifetime of returned bitset is same as receiver. May return nullptr on
// error.
const SparseBitSet* getCoverage();
// Get Unicode coverage.
const SparseBitSet& getCoverage() const { return mCoverage; }
// Returns true if the font has a glyph for the code point and variation selector pair.
// Caller should acquire a lock before calling the method.
bool hasGlyph(uint32_t codepoint, uint32_t variationSelector);
bool hasGlyph(uint32_t codepoint, uint32_t variationSelector) const;
// Returns true if this font family has a variaion sequence table (cmap format 14 subtable).
bool hasVSTable() const;
bool hasVSTable() const { return mHasVSTable; }
private:
void addFontLocked(MinikinFont* typeface, FontStyle style);
void computeCoverage();
class Font {
public:
Font(MinikinFont* typeface, FontStyle style) :
typeface(typeface), style(style) { }
MinikinFont* typeface;
FontStyle style;
};
uint32_t mLangId;
int mVariant;
std::vector<Font> mFonts;
SparseBitSet mCoverage;
bool mHasVSTable;
bool mCoverageValid;
// Forbid copying and assignment.
FontFamily(const FontFamily&) = delete;
void operator=(const FontFamily&) = delete;
};
} // namespace minikin

View File

@ -96,17 +96,13 @@ FontCollection::FontCollection(const vector<FontFamily*>& typefaces) :
continue;
}
family->RefLocked();
const SparseBitSet* coverage = family->getCoverage();
if (coverage == nullptr) {
family->UnrefLocked();
continue;
}
const SparseBitSet& coverage = family->getCoverage();
mFamilies.push_back(family); // emplace_back would be better
if (family->hasVSTable()) {
mVSFamilyVec.push_back(family);
}
mMaxChar = max(mMaxChar, coverage->length());
lastChar.push_back(coverage->nextSetBit(0));
mMaxChar = max(mMaxChar, coverage.length());
lastChar.push_back(coverage.nextSetBit(0));
}
nTypefaces = mFamilies.size();
LOG_ALWAYS_FATAL_IF(nTypefaces == 0,
@ -132,7 +128,7 @@ FontCollection::FontCollection(const vector<FontFamily*>& typefaces) :
FontFamily* family = mFamilies[j];
mFamilyVec.push_back((uint8_t)j);
offset++;
uint32_t nextChar = family->getCoverage()->nextSetBit((i + 1) << kLogCharsPerPage);
uint32_t nextChar = family->getCoverage().nextSetBit((i + 1) << kLogCharsPerPage);
#ifdef VERBOSE_DEBUG
ALOGD("nextChar = %d (j = %zd)\n", nextChar, j);
#endif
@ -199,7 +195,7 @@ uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, int variant,
// variation sequence's base character.
uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, FontFamily* fontFamily) const {
const bool hasVSGlyph = (vs != 0) && fontFamily->hasGlyph(ch, vs);
if (!hasVSGlyph && !fontFamily->getCoverage()->get(ch)) {
if (!hasVSGlyph && !fontFamily->getCoverage().get(ch)) {
// The font doesn't support either variation sequence or even the base character.
return kUnsupportedFontScore;
}
@ -417,7 +413,7 @@ void FontCollection::itemize(const uint16_t *string, size_t string_size, FontSty
if (lastFamily != nullptr) {
if (isStickyWhitelisted(ch)) {
// Continue using existing font as long as it has coverage and is whitelisted
shouldContinueRun = lastFamily->getCoverage()->get(ch);
shouldContinueRun = lastFamily->getCoverage().get(ch);
} else if (isVariationSelector(ch)) {
// Always continue if the character is a variation selector.
shouldContinueRun = true;
@ -437,7 +433,7 @@ void FontCollection::itemize(const uint16_t *string, size_t string_size, FontSty
if (utf16Pos != 0 &&
((U_GET_GC_MASK(ch) & U_GC_M_MASK) != 0 ||
(isEmojiModifier(ch) && isEmojiBase(prevCh))) &&
family && family->getCoverage()->get(prevCh)) {
family && family->getCoverage().get(prevCh)) {
const size_t prevChLength = U16_LENGTH(prevCh);
run->end -= prevChLength;
if (run->start == run->end) {

View File

@ -64,45 +64,51 @@ uint32_t FontStyle::pack(int variant, int weight, bool italic) {
return (weight & kWeightMask) | (italic ? kItalicMask : 0) | (variant << kVariantShift);
}
FontFamily::FontFamily() : FontFamily(0 /* variant */) {
Font::Font(MinikinFont* typeface, FontStyle style)
: typeface(typeface), style(style) {
typeface->Ref();
}
FontFamily::FontFamily(int variant) : FontFamily(FontLanguageListCache::kEmptyListId, variant) {
Font::Font(Font&& o) {
typeface = o.typeface;
style = o.style;
o.typeface = nullptr;
}
Font::Font(const Font& o) {
typeface = o.typeface;
typeface->Ref();
style = o.style;
}
Font::~Font() {
if (typeface == nullptr) {
return;
}
typeface->UnrefLocked();
}
FontFamily::FontFamily(std::vector<Font>&& fonts) : FontFamily(0 /* variant */, std::move(fonts)) {
}
FontFamily::FontFamily(int variant, std::vector<Font>&& fonts)
: FontFamily(FontLanguageListCache::kEmptyListId, variant, std::move(fonts)) {
}
FontFamily::FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts)
: mLangId(langId), mVariant(variant), mFonts(std::move(fonts)), mHasVSTable(false) {
computeCoverage();
}
FontFamily::~FontFamily() {
for (size_t i = 0; i < mFonts.size(); i++) {
mFonts[i].typeface->UnrefLocked();
}
}
bool FontFamily::addFont(MinikinFont* typeface) {
bool FontFamily::analyzeStyle(MinikinFont* typeface, int* weight, bool* italic) {
android::AutoMutex _l(gMinikinLock);
const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');
HbBlob os2Table(getFontTable(typeface, os2Tag));
if (os2Table.get() == nullptr) return false;
int weight;
bool italic;
if (analyzeStyle(os2Table.get(), os2Table.size(), &weight, &italic)) {
//ALOGD("analyzed weight = %d, italic = %s", weight, italic ? "true" : "false");
FontStyle style(weight, italic);
addFontLocked(typeface, style);
return true;
} else {
ALOGD("failed to analyze style");
}
return false;
}
void FontFamily::addFont(MinikinFont* typeface, FontStyle style) {
android::AutoMutex _l(gMinikinLock);
addFontLocked(typeface, style);
}
void FontFamily::addFontLocked(MinikinFont* typeface, FontStyle style) {
typeface->RefLocked();
mFonts.push_back(Font(typeface, style));
mCoverageValid = false;
return ::minikin::analyzeStyle(os2Table.get(), os2Table.size(), weight, italic);
}
// Compute a matching metric between two styles - 0 is an exact match
@ -146,18 +152,6 @@ FakedFont FontFamily::getClosestMatch(FontStyle style) const {
return result;
}
size_t FontFamily::getNumFonts() const {
return mFonts.size();
}
MinikinFont* FontFamily::getFont(size_t index) const {
return mFonts[index].typeface;
}
FontStyle FontFamily::getStyle(size_t index) const {
return mFonts[index].style;
}
bool FontFamily::isColorEmojiFamily() const {
const FontLanguages& languageList = FontLanguageListCache::getById(mLangId);
for (size_t i = 0; i < languageList.size(); ++i) {
@ -168,30 +162,24 @@ bool FontFamily::isColorEmojiFamily() const {
return false;
}
const SparseBitSet* FontFamily::getCoverage() {
if (!mCoverageValid) {
const FontStyle defaultStyle;
MinikinFont* typeface = getClosestMatch(defaultStyle).font;
const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
HbBlob cmapTable(getFontTable(typeface, cmapTag));
if (cmapTable.get() == nullptr) {
ALOGE("Could not get cmap table size!\n");
// Note: This means we will retry on the next call to getCoverage, as we can't store
// the failure. This is fine, as we assume this doesn't really happen in practice.
return nullptr;
}
// TODO: Error check?
CmapCoverage::getCoverage(mCoverage, cmapTable.get(), cmapTable.size(), &mHasVSTable);
#ifdef VERBOSE_DEBUG
ALOGD("font coverage length=%d, first ch=%x\n", mCoverage.length(),
mCoverage.nextSetBit(0));
#endif
mCoverageValid = true;
void FontFamily::computeCoverage() {
android::AutoMutex _l(gMinikinLock);
const FontStyle defaultStyle;
MinikinFont* typeface = getClosestMatch(defaultStyle).font;
const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
HbBlob cmapTable(getFontTable(typeface, cmapTag));
if (cmapTable.get() == nullptr) {
ALOGE("Could not get cmap table size!\n");
return;
}
return &mCoverage;
// TODO: Error check?
CmapCoverage::getCoverage(mCoverage, cmapTable.get(), cmapTable.size(), &mHasVSTable);
#ifdef VERBOSE_DEBUG
ALOGD("font coverage length=%d, first ch=%x\n", mCoverage.length(), mCoverage.nextSetBit(0));
#endif
}
bool FontFamily::hasGlyph(uint32_t codepoint, uint32_t variationSelector) {
bool FontFamily::hasGlyph(uint32_t codepoint, uint32_t variationSelector) const {
assertMinikinLocked();
if (variationSelector != 0 && !mHasVSTable) {
// Early exit if the variation selector is specified but the font doesn't have a cmap format
@ -208,9 +196,4 @@ bool FontFamily::hasGlyph(uint32_t codepoint, uint32_t variationSelector) {
return result;
}
bool FontFamily::hasVSTable() const {
LOG_ALWAYS_FATAL_IF(!mCoverageValid, "Do not call this method before getCoverage() call");
return mHasVSTable;
}
} // namespace minikin

View File

@ -45,9 +45,9 @@ FontCollection *makeFontCollection() {
"/system/fonts/Roboto-LightItalic.ttf"
};
FontFamily *family = new FontFamily();
FT_Face face;
FT_Error error;
std::vector<Font> fonts;
for (size_t i = 0; i < sizeof(fns)/sizeof(fns[0]); i++) {
const char *fn = fns[i];
printf("adding %s\n", fn);
@ -56,16 +56,16 @@ FontCollection *makeFontCollection() {
printf("error loading %s, %d\n", fn, error);
}
MinikinFont *font = new MinikinFontFreeType(face);
family->addFont(font);
fonts.push_back(Font(font, FontStyle()));
}
FontFamily *family = new FontFamily(std::move(fonts));
typefaces.push_back(family);
#if 1
family = new FontFamily();
const char *fn = "/system/fonts/DroidSansDevanagari-Regular.ttf";
error = FT_New_Face(library, fn, 0, &face);
MinikinFont *font = new MinikinFontFreeType(face);
family->addFont(font);
family = new FontFamily(std::vector<Font>({ Font(font, FontStyle()) }));
typefaces.push_back(family);
#endif

View File

@ -54,21 +54,21 @@ FontCollection *makeFontCollection() {
"/system/fonts/Roboto-LightItalic.ttf"
};
FontFamily *family = new FontFamily();
std::vector<Font> fonts;
for (size_t i = 0; i < sizeof(fns)/sizeof(fns[0]); i++) {
const char *fn = fns[i];
sk_sp<SkTypeface> skFace = SkTypeface::MakeFromFile(fn);
MinikinFont *font = new MinikinFontSkia(std::move(skFace));
family->addFont(font);
fonts.push_back(Font(font, FontStyle()));
}
FontFamily *family = new FontFamily(std::move(fonts));
typefaces.push_back(family);
#if 1
family = new FontFamily();
const char *fn = "/system/fonts/DroidSansDevanagari-Regular.ttf";
sk_sp<SkTypeface> skFace = SkTypeface::MakeFromFile(fn);
MinikinFont *font = new MinikinFontSkia(std::move(skFace));
family->addFont(font);
family = new FontFamily(std::vector<Font>({ Font(font, FontStyle()) }));
typefaces.push_back(family);
#endif

View File

@ -668,14 +668,14 @@ TEST_F(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) {
const std::string kVSTestFont = kTestFontDir "VarioationSelectorTest-Regular.ttf";
std::vector<FontFamily*> families;
FontFamily* family1 = new FontFamily(VARIANT_DEFAULT);
MinikinAutoUnref<MinikinFont> font(new MinikinFontForTest(kLatinFont));
family1->addFont(font.get());
FontFamily* family1 = new FontFamily(VARIANT_DEFAULT,
std::vector<Font>{ Font(font.get(), FontStyle()) });
families.push_back(family1);
FontFamily* family2 = new FontFamily(VARIANT_DEFAULT);
MinikinAutoUnref<MinikinFont> font2(new MinikinFontForTest(kVSTestFont));
family2->addFont(font2.get());
FontFamily* family2 = new FontFamily(VARIANT_DEFAULT,
std::vector<Font>{ Font(font2.get(), FontStyle()) });
families.push_back(family2);
FontCollection collection(families);
@ -811,11 +811,11 @@ TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
std::vector<FontFamily*> families;
// Prepare first font which doesn't supports U+9AA8
FontFamily* firstFamily = new FontFamily(
FontStyle::registerLanguageList("und"), 0 /* variant */);
MinikinAutoUnref<MinikinFont> firstFamilyMinikinFont(
new MinikinFontForTest(kNoGlyphFont));
firstFamily->addFont(firstFamilyMinikinFont.get());
FontFamily* firstFamily = new FontFamily(
FontStyle::registerLanguageList("und"), 0 /* variant */,
std::vector<Font>({ Font(firstFamilyMinikinFont.get(), FontStyle()) }));
families.push_back(firstFamily);
// Prepare font families
@ -824,10 +824,10 @@ TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
std::unordered_map<MinikinFont*, int> fontLangIdxMap;
for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
FontFamily* family = new FontFamily(
FontStyle::registerLanguageList(testCase.fontLanguages[i]), 0 /* variant */);
MinikinAutoUnref<MinikinFont> minikin_font(new MinikinFontForTest(kJAFont));
family->addFont(minikin_font.get());
FontFamily* family = new FontFamily(
FontStyle::registerLanguageList(testCase.fontLanguages[i]), 0 /* variant */,
std::vector<Font>({ Font(minikin_font.get(), FontStyle()) }));
families.push_back(family);
fontLangIdxMap.insert(std::make_pair(minikin_font.get(), i));
}
@ -1417,13 +1417,12 @@ TEST_F(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS) {
MinikinAutoUnref<MinikinFont> fontA(new MinikinFontForTest(kZH_HansFont));
MinikinAutoUnref<MinikinFont> fontB(new MinikinFontForTest(kZH_HansFont));
MinikinAutoUnref<FontFamily> dummyFamily(new FontFamily());
MinikinAutoUnref<FontFamily> familyA(new FontFamily());
MinikinAutoUnref<FontFamily> familyB(new FontFamily());
dummyFamily->addFont(dummyFont.get());
familyA->addFont(fontA.get());
familyB->addFont(fontB.get());
MinikinAutoUnref<FontFamily> dummyFamily(new FontFamily(
std::vector<Font>({ Font(dummyFont.get(), FontStyle()) })));
MinikinAutoUnref<FontFamily> familyA(new FontFamily(
std::vector<Font>({ Font(fontA.get(), FontStyle()) })));
MinikinAutoUnref<FontFamily> familyB(new FontFamily(
std::vector<Font>({ Font(fontB.get(), FontStyle()) })));
std::vector<FontFamily*> families =
{ dummyFamily.get(), familyA.get(), familyB.get() };
@ -1453,13 +1452,12 @@ TEST_F(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS2) {
MinikinAutoUnref<MinikinFont> noCmapFormat14Font(
new MinikinFontForTest(kNoCmapFormat14Font));
MinikinAutoUnref<FontFamily> dummyFamily(new FontFamily());
MinikinAutoUnref<FontFamily> hasCmapFormat14Family(new FontFamily());
MinikinAutoUnref<FontFamily> noCmapFormat14Family(new FontFamily());
dummyFamily->addFont(dummyFont.get());
hasCmapFormat14Family->addFont(hasCmapFormat14Font.get());
noCmapFormat14Family->addFont(noCmapFormat14Font.get());
MinikinAutoUnref<FontFamily> dummyFamily(new FontFamily(
std::vector<Font>({ Font(dummyFont.get(), FontStyle()) })));
MinikinAutoUnref<FontFamily> hasCmapFormat14Family(new FontFamily(
std::vector<Font>({ Font(hasCmapFormat14Font.get(), FontStyle()) })));
MinikinAutoUnref<FontFamily> noCmapFormat14Family(new FontFamily(
std::vector<Font>({ Font(noCmapFormat14Font.get(), FontStyle()) })));
std::vector<FontFamily*> families =
{ dummyFamily.get(), hasCmapFormat14Family.get(), noCmapFormat14Family.get() };

View File

@ -57,9 +57,9 @@ void expectVSGlyphs(const FontCollection* fc, uint32_t codepoint, const std::set
}
TEST(FontCollectionTest, hasVariationSelectorTest) {
MinikinAutoUnref<FontFamily> family(new FontFamily());
MinikinAutoUnref<MinikinFont> font(new MinikinFontForTest(kVsTestFont));
family->addFont(font.get());
MinikinAutoUnref<FontFamily> family(new FontFamily(
std::vector<Font>({ Font(font.get(), FontStyle()) })));
std::vector<FontFamily*> families({family.get()});
MinikinAutoUnref<FontCollection> fc(new FontCollection(families));

View File

@ -464,8 +464,10 @@ void expectVSGlyphs(FontFamily* family, uint32_t codepoint, const std::set<uint3
TEST_F(FontFamilyTest, hasVariationSelectorTest) {
MinikinAutoUnref<MinikinFontForTest>
minikinFont(new MinikinFontForTest(kVsTestFont));
MinikinAutoUnref<FontFamily> family(new FontFamily);
family->addFont(minikinFont.get());
MinikinAutoUnref<FontFamily> family(
new FontFamily(std::vector<Font>{
Font(minikinFont.get(), FontStyle())
}));
android::AutoMutex _l(gMinikinLock);
@ -478,23 +480,23 @@ TEST_F(FontFamilyTest, hasVariationSelectorTest) {
const uint32_t kVS20 = 0xE0103;
const uint32_t kSupportedChar1 = 0x82A6;
EXPECT_TRUE(family->getCoverage()->get(kSupportedChar1));
EXPECT_TRUE(family->getCoverage().get(kSupportedChar1));
expectVSGlyphs(family.get(), kSupportedChar1, std::set<uint32_t>({kVS1, kVS17, kVS18, kVS19}));
const uint32_t kSupportedChar2 = 0x845B;
EXPECT_TRUE(family->getCoverage()->get(kSupportedChar2));
EXPECT_TRUE(family->getCoverage().get(kSupportedChar2));
expectVSGlyphs(family.get(), kSupportedChar2, std::set<uint32_t>({kVS2, kVS18, kVS19, kVS20}));
const uint32_t kNoVsSupportedChar = 0x537F;
EXPECT_TRUE(family->getCoverage()->get(kNoVsSupportedChar));
EXPECT_TRUE(family->getCoverage().get(kNoVsSupportedChar));
expectVSGlyphs(family.get(), kNoVsSupportedChar, std::set<uint32_t>());
const uint32_t kVsOnlySupportedChar = 0x717D;
EXPECT_FALSE(family->getCoverage()->get(kVsOnlySupportedChar));
EXPECT_FALSE(family->getCoverage().get(kVsOnlySupportedChar));
expectVSGlyphs(family.get(), kVsOnlySupportedChar, std::set<uint32_t>({kVS3, kVS19, kVS20}));
const uint32_t kNotSupportedChar = 0x845C;
EXPECT_FALSE(family->getCoverage()->get(kNotSupportedChar));
EXPECT_FALSE(family->getCoverage().get(kNotSupportedChar));
expectVSGlyphs(family.get(), kNotSupportedChar, std::set<uint32_t>());
}
@ -518,11 +520,9 @@ TEST_F(FontFamilyTest, hasVSTableTest) {
MinikinAutoUnref<MinikinFontForTest> minikinFont(
new MinikinFontForTest(testCase.fontPath));
MinikinAutoUnref<FontFamily> family(new FontFamily);
family->addFont(minikinFont.get());
MinikinAutoUnref<FontFamily> family(new FontFamily(
std::vector<Font>{ Font(minikinFont.get(), FontStyle()) }));
android::AutoMutex _l(gMinikinLock);
family->getCoverage();
EXPECT_EQ(testCase.hasVSTable, family->hasVSTable());
}
}

View File

@ -48,16 +48,7 @@ FontCollection* getFontCollection(const char* fontDir, const char* fontXml) {
}
}
xmlChar* lang = xmlGetProp(familyNode, (const xmlChar*)"lang");
FontFamily* family;
if (lang == nullptr) {
family = new FontFamily(variant);
} else {
uint32_t langId = FontStyle::registerLanguageList(
std::string((const char*)lang, xmlStrlen(lang)));
family = new FontFamily(langId, variant);
}
std::vector<Font> fonts;
for (xmlNode* fontNode = familyNode->children; fontNode; fontNode = fontNode->next) {
if (xmlStrcmp(fontNode->name, (const xmlChar*)"font") != 0) {
continue;
@ -80,13 +71,23 @@ FontCollection* getFontCollection(const char* fontDir, const char* fontXml) {
if (index == nullptr) {
MinikinAutoUnref<MinikinFontForTest>
minikinFont(new MinikinFontForTest(fontPath));
family->addFont(minikinFont.get(), FontStyle(weight, italic));
fonts.push_back(Font(minikinFont.get(), FontStyle(weight, italic)));
} else {
MinikinAutoUnref<MinikinFontForTest>
minikinFont(new MinikinFontForTest(fontPath, atoi((const char*)index)));
family->addFont(minikinFont.get(), FontStyle(weight, italic));
fonts.push_back(Font(minikinFont.get(), FontStyle(weight, italic)));
}
}
xmlChar* lang = xmlGetProp(familyNode, (const xmlChar*)"lang");
FontFamily* family;
if (lang == nullptr) {
family = new FontFamily(variant, std::move(fonts));
} else {
uint32_t langId = FontStyle::registerLanguageList(
std::string((const char*)lang, xmlStrlen(lang)));
family = new FontFamily(langId, variant, std::move(fonts));
}
families.push_back(family);
}
xmlFreeDoc(doc);