#include "gx/font/CGxFont.hpp" #include "gx/font/FontFace.hpp" #include "gx/Texture.hpp" #include #include #include #include #include uint16_t TEXTURECACHE::s_textureData[256 * 256]; GLYPHBITMAPDATA::~GLYPHBITMAPDATA() { if (this->m_data) { SMemFree(this->m_data, __FILE__, __LINE__, 0x0); } this->m_data = nullptr; } void GLYPHBITMAPDATA::CopyFrom(GLYPHBITMAPDATA* data) { if (this->m_data) { SMemFree(this->m_data, __FILE__, __LINE__, 0); } this->m_data = nullptr; *this = *data; data->m_data = nullptr; } uint32_t CHARCODEDESC::GapToNextTexture() { CHARCODEDESC* next = this->textureRowLink.Next(); return next ? next->glyphStartPixel - this->glyphEndPixel - 1 : 255 - this->glyphEndPixel; } uint32_t CHARCODEDESC::GapToPreviousTexture() { CHARCODEDESC* previous = this->textureRowLink.Prev(); return previous ? this->glyphStartPixel - previous->glyphEndPixel - 1 : this->glyphStartPixel; } void CHARCODEDESC::GenerateTextureCoords(uint32_t rowNumber, uint32_t glyphSide) { this->bitmapData.m_textureCoords.minY = (glyphSide * rowNumber) / 256.0f; this->bitmapData.m_textureCoords.minX = this->glyphStartPixel / 256.0f; this->bitmapData.m_textureCoords.maxY = ((glyphSide * rowNumber) + glyphSide) / 256.0f; this->bitmapData.m_textureCoords.maxX = (this->glyphStartPixel + this->bitmapData.m_glyphCellWidth) / 256.0f; } KERNINGHASHKEY& KERNINGHASHKEY::operator=(const KERNINGHASHKEY& rhs) { if (this->code != rhs.code) { this->code = rhs.code; } return *this; } bool KERNINGHASHKEY::operator==(const KERNINGHASHKEY& rhs) { return this->code == rhs.code; } CHARCODEDESC* TEXTURECACHEROW::CreateNewDesc(GLYPHBITMAPDATA* data, uint32_t rowNumber, uint32_t glyphCellHeight) { uint32_t glyphWidth = data->m_glyphCellWidth; if (this->widestFreeSlot < glyphWidth) { return nullptr; } if (!this->glyphList.Head()) { CHARCODEDESC* newCode = this->glyphList.NewNode(2, 0, 0); newCode->glyphStartPixel = 0; newCode->glyphEndPixel = glyphWidth - 1; newCode->rowNumber = rowNumber; newCode->bitmapData.CopyFrom(data); newCode->GenerateTextureCoords(rowNumber, glyphCellHeight); this->widestFreeSlot -= glyphWidth; return newCode; } uint32_t gapToPrevious = this->glyphList.Head()->GapToPreviousTexture(); this->widestFreeSlot = gapToPrevious; if (gapToPrevious >= glyphWidth) { auto m = SMemAlloc(sizeof(CHARCODEDESC), __FILE__, __LINE__, 0x0); auto newCode = new (m) CHARCODEDESC(); this->glyphList.LinkNode(newCode, 2, this->glyphList.Head()); newCode->glyphEndPixel = this->glyphList.Head()->glyphStartPixel - 1; newCode->glyphStartPixel = newCode->glyphEndPixel - glyphWidth + 1; newCode->rowNumber = rowNumber; newCode->bitmapData.CopyFrom(data); newCode->GenerateTextureCoords(rowNumber, glyphCellHeight); this->widestFreeSlot = this->glyphList.Head() ? this->glyphList.Head()->GapToPreviousTexture() : 0; for (auto code = this->glyphList.Head(); code; code = this->glyphList.Link(code)->Next()) { uint32_t gapToNext = code->GapToNextTexture(); if (gapToNext > this->widestFreeSlot) { this->widestFreeSlot = gapToNext; } } return newCode; } int32_t inserted = 0; CHARCODEDESC* newCode = nullptr; for (auto code = this->glyphList.Head(); code; code = this->glyphList.Link(code)->Next()) { uint32_t gapToNext = code->GapToNextTexture(); if (inserted) { if (gapToNext > this->widestFreeSlot) { this->widestFreeSlot = gapToNext; } continue; } if (gapToNext >= glyphWidth) { auto m = SMemAlloc(sizeof(CHARCODEDESC), __FILE__, __LINE__, 0x0); newCode = new (m) CHARCODEDESC(); this->glyphList.LinkNode(newCode, 1, code); newCode->glyphStartPixel = code->glyphEndPixel + 1; newCode->glyphEndPixel = code->glyphEndPixel + glyphWidth; newCode->rowNumber = rowNumber; newCode->bitmapData.CopyFrom(data); newCode->GenerateTextureCoords(rowNumber, glyphCellHeight); inserted = 1; } } return newCode; } void TEXTURECACHEROW::EvictGlyph(CHARCODEDESC* desc) { // TODO } void TEXTURECACHE::TextureCallback(EGxTexCommand cmd, uint32_t w, uint32_t h, uint32_t d, uint32_t mipLevel, void* userArg, uint32_t& texelStrideInBytes, const void*& texels) { TEXTURECACHE* cache = static_cast(userArg); switch (cmd) { case GxTex_Latch: { for (int32_t i = cache->m_textureRows.Count() - 1; i >= 0; i--) { auto& cacheRow = cache->m_textureRows[i]; for (auto glyph = cacheRow.glyphList.Head(); glyph; glyph = cacheRow.glyphList.Next(glyph)) { cache->WriteGlyphToTexture(glyph); } } texelStrideInBytes = 512; texels = TEXTURECACHE::s_textureData; break; }; } } CHARCODEDESC* TEXTURECACHE::AllocateNewGlyph(GLYPHBITMAPDATA* data) { for (int32_t i = 0; i < this->m_textureRows.Count(); i++) { auto& cacheRow = this->m_textureRows[i]; CHARCODEDESC* glyph = cacheRow.CreateNewDesc(data, i, this->m_theFace->m_cellHeight); if (glyph) { return glyph; } } return nullptr; } void TEXTURECACHE::CreateTexture(int32_t filter) { CGxTexFlags flags = CGxTexFlags(filter ? GxTex_Linear : GxTex_Nearest, 0, 0, 0, 0, 0, 1); HTEXTURE texture = TextureCreate( 256, 256, GxTex_Argb4444, GxTex_Argb4444, flags, this, TEXTURECACHE::TextureCallback, "GxuFont", 0 ); this->m_texture = texture; } void TEXTURECACHE::Initialize(CGxFont* face, uint32_t pixelSize) { this->m_theFace = face; uint32_t rowCount = 256 / pixelSize; this->m_textureRows.SetCount(rowCount); for (int32_t i = 0; i < rowCount; i++) { this->m_textureRows[i].widestFreeSlot = 256; } } void TEXTURECACHE::PasteGlyph(const GLYPHBITMAPDATA& data, uint16_t* dst) { if (this->m_theFace->m_flags & FONT_OUTLINE) { if (this->m_theFace->m_flags & FONT_MONOCHROME) { this->PasteGlyphOutlinedMonochrome(data, dst); } else { this->PasteGlyphOutlinedAA(data, dst); } } else if (this->m_theFace->m_flags & FONT_MONOCHROME) { this->PasteGlyphNonOutlinedMonochrome(data, dst); } else { this->PasteGlyphNonOutlinedAA(data, dst); } } void TEXTURECACHE::PasteGlyphNonOutlinedAA(const GLYPHBITMAPDATA& glyphData, uint16_t* dst) { auto src = reinterpret_cast(glyphData.m_data); auto pitch = glyphData.m_glyphPitch; auto dstCellStride = glyphData.m_glyphCellWidth * 2; for (int32_t y = 0; y < glyphData.m_yStart; y++) { memset(dst, 0, dstCellStride); dst += 256; } for (int32_t y = 0; y < glyphData.m_glyphHeight; y++) { for (int32_t x = 0; x < glyphData.m_glyphWidth; x++) { dst[x] = ((src[x] & 0xF0) << 8) | 0xFFF; } src += pitch; dst += 256; } auto glyphHeight = glyphData.m_glyphHeight; auto yStart = glyphData.m_yStart; if (this->m_theFace->m_cellHeight - glyphHeight - yStart > 0 && this->m_theFace->m_cellHeight - glyphHeight != yStart) { for (int32_t y = 0; y < this->m_theFace->m_cellHeight - glyphHeight - yStart; y++) { memset(dst, 0, dstCellStride); dst += 256; } } } void TEXTURECACHE::PasteGlyphNonOutlinedMonochrome(const GLYPHBITMAPDATA& data, uint16_t* dst) { // TODO } void TEXTURECACHE::PasteGlyphOutlinedAA(const GLYPHBITMAPDATA& glyphData, uint16_t* dst) { uint32_t v6; uint32_t v7; uint16_t* v8; int32_t v9; int32_t v15; int32_t v16; uint32_t v17; uint32_t v18; uint32_t v19; uint32_t v20; int32_t v21; int32_t v22; int32_t v23; int32_t v24; int32_t v25; uint32_t v26; uint32_t v27; uint32_t v28; bool v29; bool v30; int32_t v31; int32_t v32; int32_t v33; int32_t v34; int32_t v35; int32_t v36; bool v37; int32_t v38; int32_t v39; int32_t v40; uint32_t v41; uint8_t v42; bool v43; uint16_t v44[9216]; uint16_t v45[9216]; uint16_t v46[9216]; uint32_t v49; uint16_t* v52; uint32_t v52_2; uint32_t v52_3; int32_t v53; static uint8_t pixelsLitLevels[] = { 0, 1, 1, 3, 5, 7, 9, 0xB, 0xD, 0xF, 0, 0 }; const char* src = reinterpret_cast(glyphData.m_data); uint32_t thick = this->m_theFace->m_flags & 0x8; memset(v45, 0, sizeof(v45)); memset(v46, 0, sizeof(v46)); uint32_t ofs = thick ? 256 * glyphData.m_yStart + 258 : 256 * glyphData.m_yStart + 257; for (int32_t y = 0; y < glyphData.m_glyphHeight; y++) { for (int32_t x = 0; x < glyphData.m_glyphWidth; x++) { uint8_t v10 = src[(y * glyphData.m_glyphPitch) + x]; if (v10) { v45[ofs + (y * 256) + x] = v10; v46[ofs + (y * 256) + x] = 1; } } } v52_2 = 0; v15 = glyphData.m_yStart - 1; for (int32_t i = 0; i < (thick != 0) + 1; i++) { v49 = 2 * (i != 0) + 2; v16 = 2 * (i != 0) + 1; v17 = v15 < 0 ? 0 : v15; v53 = v15 < 0 ? 0 : v15; if (v17 >= this->m_theFace->m_cellHeight) { continue; } do { v18 = glyphData.m_glyphCellWidth; v19 = v17 << 8; v20 = 0; if (!v18) { goto LABEL_68; } do { v21 = v19 + v20; if (v46[v19 + v20] & v16) { goto LABEL_66; } if (v53) { if (v53 == this->m_theFace->m_cellHeight - 1) { if (v20) { if (v20 == v18 - 1) { if ( v46[v21 - 1] & v16 || v46[v19 + v20 - 257] & v16 ) { goto LABEL_65; } v22 = v46[v19 + v20 - 256]; } else { if ( v46[v19 + v20 - 257] & v16 || v46[v19 + v20 - 256] & v16 || v46[v19 + v20 - 255] & v16 || v46[v21 - 1] & v16 ) { goto LABEL_65; } v22 = v46[v21 + 1]; } } else { if ( v46[v19 - 256] & v16 || v46[v19 - 255] & v16 ) { goto LABEL_65; } v22 = v46[v19 + 1]; } } else if (v20) { v24 = v19 + v20; if (v20 == v18 - 1) { if ( v46[v24 - 256] & v16 || v46[v19 + v20 - 257] & v16 || v46[v21 - 1] & v16 || v46[v21 + 255] & v16 ) { goto LABEL_65; } v22 = v46[v21 + 256]; } else { if ( v46[v24 - 257] & v16 || v46[v19 + v20 - 256] & v16 || v46[v19 + v20 - 255] & v16 || v46[v21 - 1] & v16 || v46[v21 + 1] & v16 || v46[v21 + 255] & v16 || v46[v21 + 256] & v16 ) { goto LABEL_65; } v22 = v46[v21 + 257]; } } else { if ( v46[v19 - 256] & v16 || v46[v19 - 255] & v16 || v46[v19 + 1] & v16 || v46[v19 + 257] & v16 ) { goto LABEL_65; } v22 = v46[v19 + 256]; } LABEL_64: if (!(v22 & v16)) { goto LABEL_66; } goto LABEL_65; } if (v20) { v23 = v46[v20 - 1]; if (v20 == v18 - 1) { if (!(v23 & v16) && !(v46[v20 + 255] & v16)) { v22 = v46[v20 + 256]; goto LABEL_64; } } else if (!(v23 & v16) && !(v46[v20 + 1] & v16) && !(v46[v20 + 255] & v16) && !(v46[v20 + 256] & v16)) { v22 = v46[v20 + 257]; goto LABEL_64; } } else if (!(v46[1] & v16) && !(v46[257] & v16)) { v22 = v46[256]; goto LABEL_64; } LABEL_65: v46[v21] = v49; LABEL_66: ++v20; } while (v20 < v18); v17 = v53; LABEL_68: v53 = ++v17; } while (v17 < this->m_theFace->m_cellHeight); } memset(v44, 0, sizeof(v44)); v26 = 0; v53 = 0; if (!this->m_theFace->m_cellHeight) { goto LABEL_95; } while (2) { v27 = glyphData.m_glyphCellWidth; v28 = v26 << 8; v25 = 0; if (!v27) { goto LABEL_94; } while (2) { v37 = v46[v28 + v25] == 0; v52 = &v46[v28 + v25]; v29 = !v37; v30 = !v37; if (v26) { if (v53 == this->m_theFace->m_cellHeight - 1) { if (!v25) { v31 = v30 + (v46[v28 + 1] != 0) + (v46[v28 - 255] != 0) + (v46[v28 - 256] != 0); goto LABEL_92; } v37 = v25 == v27 - 1; v34 = v28 + v25; if (v37) { v31 = (v46[v28 + v25 - 256] != 0) + (v46[v34 - 257] != 0) + v30 + (*(v52 - 1) != 0); goto LABEL_92; } v35 = (v46[v34 - 256] != 0) + (v46[v28 + v25 - 257] != 0); v36 = 0; v37 = v46[v28 + v25 - 255] == 0; v38 = v28 + v25; } else { if (!v25) { v31 = v30 + (v46[v28 + 256] != 0) + (v46[v28 + 1] != 0) + (v46[v28 + 257] != 0) + (v46[v28 - 255] != 0) + (v46[v28 - 256] != 0); goto LABEL_92; } v37 = v25 == v27 - 1; v39 = v28 + v25; if (v37) { v31 = (v46[v28 + v25 + 256] != 0) + (v46[v28 + v25 + 255] != 0) + (v46[v28 + v25 - 256] != 0) + (v46[v39 - 257] != 0) + v30 + (*(v52 - 1) != 0); goto LABEL_92; } v37 = v46[v39 - 256] == 0; v38 = v28 + v25; v35 = (v46[v28 + v25 + 256] != 0) + (v46[v28 + v25 + 255] != 0) + (v46[v28 + v25 - 255] != 0) + !v37 + (v46[v28 + v25 - 257] != 0); v36 = 0; v37 = v46[v28 + v25 + 257] == 0; } v36 = !v37; v31 = (v46[v38 + 1] != 0) + v36 + v35 + v30 + (*(v52 - 1) != 0); } else if (v25) { if (v25 == v27 - 1) { v32 = (v46[v25 + 255] != 0) + (v46[v25 - 1] != 0); v33 = v29 + (v46[v25 + 256] != 0); } else { v32 = (v46[v25 + 257] != 0) + (v46[v25 + 256] != 0) + (v46[v25 + 255] != 0) + (v46[v25 - 1] != 0); v33 = v29 + (v46[v25 + 1] != 0); } v31 = v33 + v32; } else { v31 = v29 + (v46[1] != 0) + (v46[257] != 0) + (v46[256] != 0); } LABEL_92: v26 = v53; v44[v28 + v25] = pixelsLitLevels[v31]; v27 = glyphData.m_glyphCellWidth; if (++v25 < v27) { continue; } break; } LABEL_94: v53 = ++v26; if (v26 < this->m_theFace->m_cellHeight) { continue; } break; } LABEL_95: v40 = 0; for (int32_t y = 0; y < this->m_theFace->m_cellHeight; y++) { for (int32_t x = 0; x < glyphData.m_glyphCellWidth; x++) { if (v46[v40 + x]) { if (v45[v40 + x]) { v42 = (255 * v45[v40 + x]) >> 12; dst[v40 + x] = v42 | (16 * (v42 | (16 * (v42 | (16 * v44[v40 + x]))))); } else { dst[v40 + x] = v44[v40 + x] << 12; } } else { dst[v40 + x] = 0; } } v40 += 256; } } void TEXTURECACHE::PasteGlyphOutlinedMonochrome(const GLYPHBITMAPDATA& data, uint16_t* dst) { // TODO } void TEXTURECACHE::UpdateDirty() { if (this->m_anyDirtyGlyphs && this->m_texture) { CGxTex* gxTex = TextureGetGxTex(this->m_texture, 1, nullptr); CiRect updateRect = { 0, 0, 256, 256 }; GxTexUpdate(gxTex, updateRect, 1); this->m_anyDirtyGlyphs = 0; } } void TEXTURECACHE::WriteGlyphToTexture(CHARCODEDESC* glyph) { if (!this->m_texture || !this->m_theFace || !this->m_theFace->m_cellHeight) { return; } uint32_t ofs = glyph->glyphStartPixel + (glyph->rowNumber * this->m_theFace->m_cellHeight << 8); uint16_t* ptr = &TEXTURECACHE::s_textureData[ofs]; this->PasteGlyph(glyph->bitmapData, ptr); } CGxFont::~CGxFont() { this->Clear(); } int32_t CGxFont::CheckStringGlyphs(const char* string) { if (!string || !*string) { return 1; } while (*string) { int32_t advance; auto code = SUniSGetUTF8(reinterpret_cast(string), &advance); HASHKEY_NONE key = {}; if (code != '\r' && code != '\n' && !this->m_activeCharacters.Ptr(code, key)) { return 0; } string += advance; } return 1; } void CGxFont::Clear() { if (this->m_faceHandle) { FontFaceCloseHandle(this->m_faceHandle); } this->m_faceHandle = nullptr; this->ClearGlyphs(); } void CGxFont::ClearGlyphs() { for (int32_t i = 0; i < 8; i++) { auto& cache = this->m_textureCache[i]; if (cache.m_texture) { HandleClose(this->m_textureCache[i].m_texture); } cache.m_texture = nullptr; // TODO } this->m_activeCharacters.Clear(); this->m_activeCharacterCache.Clear(); this->m_kernInfo.Clear(); } float CGxFont::ComputeStep(uint32_t currentCode, uint32_t nextCode) { KERNINGHASHKEY kernKey = { nextCode | (currentCode << 16) }; KERNNODE* kern = this->m_kernInfo.Ptr(currentCode, kernKey); if (kern && kern->flags & 0x02) { return kern->proporportionalSpacing; } auto face = FontFaceGetFace(this->m_faceHandle); auto currentIndex = FT_Get_Char_Index(face, currentCode); auto nextIndex = FT_Get_Char_Index(face, nextCode); FT_Vector vector; vector.x = 0; if (face->face_flags & FT_FACE_FLAG_KERNING) { FT_Get_Kerning(face, currentIndex, nextIndex, ft_kerning_unscaled, &vector); vector.x &= (vector.x >= 0) - 1; } HASHKEY_NONE charKey = {}; auto activeChar = this->m_activeCharacters.Ptr(currentCode, charKey); float advance = 0.0f; if (activeChar) { advance = this->m_flags & 0x08 ? activeChar->bitmapData.m_glyphAdvance + 1.0f : activeChar->bitmapData.m_glyphAdvance; } float spacing = (this->m_pixelsPerUnit * vector.x) + advance; if (!kern) { kern = this->m_kernInfo.New(currentCode, kernKey, 0, 0); } kern->flags |= 0x02; kern->proporportionalSpacing = ceil(spacing); return kern->proporportionalSpacing; } float CGxFont::ComputeStepFixedWidth(uint32_t currentCode, uint32_t nextCode) { // TODO return 0.0f; } float CGxFont::GetGlyphBearing(const CHARCODEDESC* glyph, bool billboarded, float height) { if (billboarded) { float v8 = ScreenToPixelHeight(1, height) / this->GetPixelSize(); return glyph->bitmapData.m_glyphBearing * v8; } return ceil(glyph->bitmapData.m_glyphBearing); } int32_t CGxFont::GetGlyphData(GLYPHBITMAPDATA* glyphData, uint32_t code) { FT_Face face = FontFaceGetFace(this->m_faceHandle); FT_Set_Pixel_Sizes(face, this->m_pixelSize, 0); uint32_t v6 = 0; if (this->m_flags & 0x8) { v6 = 4; } else if (this->m_flags & FONT_OUTLINE) { v6 = 2; } return IGxuFontGlyphRenderGlyph( face, this->m_pixelSize, code, this->m_baseline, glyphData, this->m_flags & FONT_MONOCHROME, v6 ); } const char* CGxFont::GetName(void) const { STORM_ASSERT(this->m_faceHandle); return FontFaceGetFontName(this->m_faceHandle); } uint32_t CGxFont::GetPixelSize() { return this->m_pixelSize; } int32_t CGxFont::Initialize(const char* name, uint32_t newFlags, float fontHeight) { SStrPrintf(this->m_fontName, 260, "%s", name); this->m_requestedFontHeight = fontHeight; this->Clear(); this->m_flags = newFlags; this->m_faceHandle = FontFaceGetHandle(name, GetFreeTypeLibrary()); if (this->m_faceHandle) { return this->UpdateDimensions(); } else { return 0; } } const CHARCODEDESC* CGxFont::NewCodeDesc(uint32_t code) { HASHKEY_NONE key = {}; CHARCODEDESC* charDesc = this->m_activeCharacters.Ptr(code, key); if (charDesc) { this->m_activeCharacterCache.LinkToHead(charDesc); return charDesc; } GLYPHBITMAPDATA data; if (!CGxFont::GetGlyphData(&data, code)) { return nullptr; } // Attempt to allocate the character off of texture caches for (uint32_t textureNumber = 0; textureNumber < 8; textureNumber++) { TEXTURECACHE* textureCache = &this->m_textureCache[textureNumber]; if (textureCache->m_texture && TextureGetGxTex(reinterpret_cast(textureCache->m_texture), 1, nullptr)) { charDesc = textureCache->AllocateNewGlyph(&data); if (charDesc) { charDesc->textureNumber = textureNumber; break; } } else { textureCache->CreateTexture(this->m_flags & 0x4); textureCache->Initialize(this, this->m_cellHeight); charDesc = textureCache->AllocateNewGlyph(&data); if (charDesc) { charDesc->textureNumber = textureNumber; } break; } } // No character was allocated from the texture caches, so evict the oldest character and // attempt to allocate from that character's texture cache row if (!charDesc) { CHARCODEDESC* oldestDesc = this->m_activeCharacterCache.Tail(); if (oldestDesc) { uint32_t textureNumber = oldestDesc->textureNumber; uint32_t rowNumber = oldestDesc->rowNumber; TEXTURECACHE* textureCache = &this->m_textureCache[textureNumber]; TEXTURECACHEROW* cacheRow = &textureCache->m_textureRows[rowNumber]; cacheRow->EvictGlyph(oldestDesc); this->RegisterEvictNotice(textureNumber); charDesc = cacheRow->CreateNewDesc(&data, rowNumber, this->m_cellHeight); if (charDesc) { charDesc->rowNumber = rowNumber; charDesc->textureNumber = textureNumber; } } } if (charDesc) { this->m_activeCharacters.Insert(charDesc, code, key); this->m_activeCharacterCache.LinkToHead(charDesc); this->m_textureCache[charDesc->textureNumber].m_anyDirtyGlyphs = 1; } return charDesc; } void CGxFont::RegisterEvictNotice(uint32_t a2) { // TODO } int32_t CGxFont::UpdateDimensions() { FT_Face theFace = FontFaceGetFace(this->m_faceHandle); float v11 = Sub6C2280(theFace, this->m_requestedFontHeight); float v3 = 2.0 / GetScreenPixelHeight(); if (v11 > v3) { v3 = v11; } float height = v3; uint32_t pixelSize = ScreenToPixelHeight(0, height); if (pixelSize <= 32) { if (!pixelSize) { // TODO // nullsub_3(); } } else { pixelSize = 32; } this->m_pixelSize = pixelSize; uint32_t v10 = theFace->ascender + abs(theFace->descender); if (!v10) { return 0; } this->m_cellHeight = pixelSize; float baseline = (double)(pixelSize * theFace->ascender) / (double)v10; this->m_baseline = (int64_t)(baseline + 0.5); uint32_t flags = this->m_flags; if (flags & 0x8) { this->m_cellHeight = pixelSize + 4; } else if (flags & 0x1) { this->m_cellHeight = pixelSize + 2; } int32_t result = FT_Set_Pixel_Sizes(theFace, pixelSize, 0) == FT_Err_Ok; this->m_pixelsPerUnit = (double)theFace->size->metrics.x_ppem / (double)theFace->units_per_EM; return result; }