F32 LLFontFreetype::getXKerning(llwchar char_left, llwchar char_right) const { if (mFTFace == NULL) return 0.0; //llassert(!mIsFallback); LLFontGlyphInfo* left_glyph_info = getGlyphInfo(char_left);; U32 left_glyph = left_glyph_info ? left_glyph_info->mGlyphIndex : 0; // Kern this puppy. LLFontGlyphInfo* right_glyph_info = getGlyphInfo(char_right); U32 right_glyph = right_glyph_info ? right_glyph_info->mGlyphIndex : 0; FT_Vector delta; llverify(!FT_Get_Kerning(mFTFace, left_glyph, right_glyph, ft_kerning_unfitted, &delta)); return delta.x*(1.f/64.f); }
BOOL LLFont::hasGlyph(const llwchar wch) const { llassert(!mIsFallback); const LLFontGlyphInfo* gi = getGlyphInfo(wch); if (gi && gi->mIsRendered) { return TRUE; } else { return FALSE; } }
/** Update the supported characters for this font if required. */ void FontWithFace::updateCharactersList() { if (m_fallback_font != NULL) m_fallback_font->updateCharactersList(); if (m_new_char_holder.empty()) return; for (const wchar_t& c : m_new_char_holder) { const GlyphInfo& gi = getGlyphInfo(c); insertGlyph(c, gi); } m_new_char_holder.clear(); } // updateCharactersList
BOOL LLFontGL::addChar(const llwchar wch) const { if (!LLFont::addChar(wch)) { return FALSE; } stop_glerror(); LLFontGlyphInfo *glyph_info = getGlyphInfo(wch); U32 bitmap_num = glyph_info->mBitmapNum; LLImageGL *image_gl = mFontBitmapCachep->getImageGL(bitmap_num); LLImageRaw *image_raw = mFontBitmapCachep->getImageRaw(bitmap_num); image_gl->setSubImage(image_raw, 0, 0, image_gl->getWidth(), image_gl->getHeight()); return TRUE; }
// ---------------------------------------------------------------------------- void FontWithFace::updateCharactersList() { if (m_fallback_font != NULL) m_fallback_font->updateCharactersList(); if (m_new_char_holder.empty()) return; for (const wchar_t& c : m_new_char_holder) { const GlyphInfo& gi = getGlyphInfo(c); insertGlyph(c, gi); } m_new_char_holder.clear(); // Update last glyph page video::ITexture* page_texture = irr_driver->getVideoDriver() ->addTexture("Glyph_page", m_page); m_spritebank->setTexture(m_spritebank->getTextureCount() - 1, page_texture); irr_driver->getVideoDriver()->removeTexture(page_texture); assert(page_texture->getReferenceCount() == 1); } // updateCharactersList
F32 LLFontFreetype::getXAdvance(const llwchar wch) const { if (mFTFace == NULL) return 0.0; // Return existing info only if it is current LLFontGlyphInfo* gi = getGlyphInfo(wch); if (gi) { return gi->mXAdvance; } else { char_glyph_info_map_t::iterator found_it = mCharGlyphInfoMap.find((llwchar)0); if (found_it != mCharGlyphInfoMap.end()) { return found_it->second->mXAdvance; } } // Last ditch fallback - no glyphs defined at all. return (F32)mFontBitmapCachep->getMaxCharWidth(); }
F32 LLFontFreetype::getXAdvance(llwchar wch) const { if (mFTFace == NULL) return 0.0; // Return existing info only if it is current LLFontGlyphInfo* gi = getGlyphInfo(wch); if (gi) { return gi->mXAdvance; } else { gi = get_if_there(mCharGlyphInfoMap, (llwchar)0, (LLFontGlyphInfo*)NULL); if (gi) { return gi->mXAdvance; } } // Last ditch fallback - no glyphs defined at all. return (F32)mFontBitmapCachep->getMaxCharWidth(); }
S32 LLFontGL::render(const LLWString &wstr, const S32 begin_offset, const F32 x, const F32 y, const LLColor4 &color, const HAlign halign, const VAlign valign, U8 style, const S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_embedded, BOOL use_ellipses) const { if(!sDisplayFont) //do not display texts { return wstr.length() ; } if (wstr.empty()) { return 0; } gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX); // Strip off any style bits that are already accounted for by the font. style = style & (~getFontDesc().getStyle()); F32 drop_shadow_strength = 0.f; if (style & (DROP_SHADOW | DROP_SHADOW_SOFT)) { F32 luminance; color.calcHSL(NULL, NULL, &luminance); drop_shadow_strength = clamp_rescale(luminance, 0.35f, 0.6f, 0.f, 1.f); if (luminance < 0.35f) { style = style & ~(DROP_SHADOW | DROP_SHADOW_SOFT); } } gGL.pushMatrix(); glLoadIdentity(); gGL.translatef(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ); // this code snaps the text origin to a pixel grid to start with F32 pixel_offset_x = llround((F32)sCurOrigin.mX) - (sCurOrigin.mX); F32 pixel_offset_y = llround((F32)sCurOrigin.mY) - (sCurOrigin.mY); gGL.translatef(-pixel_offset_x, -pixel_offset_y, 0.f); LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS); gGL.color4fv( color.mV ); S32 chars_drawn = 0; S32 i; S32 length; if (-1 == max_chars) { length = (S32)wstr.length() - begin_offset; } else { length = llmin((S32)wstr.length() - begin_offset, max_chars ); } F32 cur_x, cur_y, cur_render_x, cur_render_y; // Not guaranteed to be set correctly gGL.setSceneBlendType(LLRender::BT_ALPHA); cur_x = ((F32)x * sScaleX); cur_y = ((F32)y * sScaleY); // Offset y by vertical alignment. switch (valign) { case TOP: cur_y -= mAscender; break; case BOTTOM: cur_y += mDescender; break; case VCENTER: cur_y -= ((mAscender - mDescender)/2.f); break; case BASELINE: // Baseline, do nothing. break; default: break; } switch (halign) { case LEFT: break; case RIGHT: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)); break; case HCENTER: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2; break; default: break; } cur_render_y = cur_y; cur_render_x = cur_x; F32 start_x = cur_x; F32 inv_width = 1.f / mFontBitmapCachep->getBitmapWidth(); F32 inv_height = 1.f / mFontBitmapCachep->getBitmapHeight(); const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; BOOL draw_ellipses = FALSE; if (use_ellipses && halign == LEFT) { // check for too long of a string if (getWidthF32(wstr.c_str(), 0, max_chars) * sScaleX > scaled_max_pixels) { // use four dots for ellipsis width to generate padding const LLWString dots(utf8str_to_wstring(std::string("...."))); scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str()))); draw_ellipses = TRUE; } } // Remember last-used texture to avoid unnecesssary bind calls. LLImageGL *last_bound_texture = NULL; for (i = begin_offset; i < begin_offset + length; i++) { llwchar wch = wstr[i]; // Handle embedded characters first, if they're enabled. // Embedded characters are a hack for notecards const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; if (ext_data) { LLImageGL* ext_image = ext_data->mImage; const LLWString& label = ext_data->mLabel; F32 ext_height = (F32)ext_image->getHeight() * sScaleY; F32 ext_width = (F32)ext_image->getWidth() * sScaleX; F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width; if (!label.empty()) { ext_advance += (EXT_X_BEARING + getFontExtChar()->getWidthF32( label.c_str() )) * sScaleX; } if (start_x + scaled_max_pixels < cur_x + ext_advance) { // Not enough room for this character. break; } if (last_bound_texture != ext_image) { gGL.getTexUnit(0)->bind(ext_image); last_bound_texture = ext_image; } // snap origin to whole screen pixel const F32 ext_x = (F32)llround(cur_render_x + (EXT_X_BEARING * sScaleX)); const F32 ext_y = (F32)llround(cur_render_y + (EXT_Y_BEARING * sScaleY + mAscender - mLineHeight)); LLRectf uv_rect(0.f, 1.f, 1.f, 0.f); LLRectf screen_rect(ext_x, ext_y + ext_height, ext_x + ext_width, ext_y); drawGlyph(screen_rect, uv_rect, LLColor4::white, style, drop_shadow_strength); if (!label.empty()) { gGL.pushMatrix(); //glLoadIdentity(); //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); //glScalef(sScaleX, sScaleY, 1.f); getFontExtChar()->render(label, 0, /*llfloor*/((ext_x + (F32)ext_image->getWidth() + EXT_X_BEARING) / sScaleX), /*llfloor*/(cur_y / sScaleY), color, halign, BASELINE, NORMAL, S32_MAX, S32_MAX, NULL, TRUE ); gGL.popMatrix(); } gGL.color4fv(color.mV); chars_drawn++; cur_x += ext_advance; if (((i + 1) < length) && wstr[i+1]) { cur_x += EXT_KERNING * sScaleX; } cur_render_x = cur_x; } else { if (!hasGlyph(wch)) { addChar(wch); } const LLFontGlyphInfo* fgi= getGlyphInfo(wch); if (!fgi) { llerrs << "Missing Glyph Info" << llendl; break; } // Per-glyph bitmap texture. LLImageGL *image_gl = mFontBitmapCachep->getImageGL(fgi->mBitmapNum); if (last_bound_texture != image_gl) { gGL.getTexUnit(0)->bind(image_gl); last_bound_texture = image_gl; } if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth)) { // Not enough room for this character. break; } // Draw the text at the appropriate location //Specify vertices and texture coordinates LLRectf uv_rect((fgi->mXBitmapOffset) * inv_width, (fgi->mYBitmapOffset + fgi->mHeight + PAD_UVY) * inv_height, (fgi->mXBitmapOffset + fgi->mWidth) * inv_width, (fgi->mYBitmapOffset - PAD_UVY) * inv_height); // snap glyph origin to whole screen pixel LLRectf screen_rect(llround(cur_render_x + (F32)fgi->mXBearing), llround(cur_render_y + (F32)fgi->mYBearing), llround(cur_render_x + (F32)fgi->mXBearing) + (F32)fgi->mWidth, llround(cur_render_y + (F32)fgi->mYBearing) - (F32)fgi->mHeight); drawGlyph(screen_rect, uv_rect, color, style, drop_shadow_strength); chars_drawn++; cur_x += fgi->mXAdvance; cur_y += fgi->mYAdvance; llwchar next_char = wstr[i+1]; if (next_char && (next_char < LAST_CHARACTER)) { // Kern this puppy. if (!hasGlyph(next_char)) { addChar(next_char); } cur_x += getXKerning(wch, next_char); } // Round after kerning. // Must do this to cur_x, not just to cur_render_x, otherwise you // will squish sub-pixel kerned characters too close together. // For example, "CCCCC" looks bad. cur_x = (F32)llfloor(cur_x + 0.5f); //cur_y = (F32)llfloor(cur_y + 0.5f); cur_render_x = cur_x; cur_render_y = cur_y; } } if (right_x) { *right_x = cur_x / sScaleX; } if (style & UNDERLINE) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.begin(LLRender::LINES); gGL.vertex2f(start_x, cur_y - (mDescender)); gGL.vertex2f(cur_x, cur_y - (mDescender)); gGL.end(); } // *FIX: get this working in all alignment cases, etc. if (draw_ellipses) { // recursively render ellipses at end of string // we've already reserved enough room gGL.pushMatrix(); //glLoadIdentity(); //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); //glScalef(sScaleX, sScaleY, 1.f); renderUTF8(std::string("..."), 0, cur_x / sScaleX, (F32)y, color, LEFT, valign, style, S32_MAX, max_pixels, right_x, FALSE); gGL.popMatrix(); } gGL.popMatrix(); return chars_drawn; }
bool FontManager::preloadGlyph(FontHandle _handle, CodePoint _codePoint) { BX_CHECK(bgfx::isValid(_handle), "Invalid handle used"); CachedFont& font = m_cachedFonts[_handle.idx]; FontInfo& fontInfo = font.fontInfo; GlyphHashMap::iterator iter = font.cachedGlyphs.find(_codePoint); if (iter != font.cachedGlyphs.end() ) { return true; } if (NULL != font.trueTypeFont) { GlyphInfo glyphInfo; switch (font.fontInfo.fontType) { case FONT_TYPE_ALPHA: font.trueTypeFont->bakeGlyphAlpha(_codePoint, glyphInfo, m_buffer); break; case FONT_TYPE_DISTANCE: font.trueTypeFont->bakeGlyphDistance(_codePoint, glyphInfo, m_buffer); break; case FONT_TYPE_DISTANCE_SUBPIXEL: font.trueTypeFont->bakeGlyphDistance(_codePoint, glyphInfo, m_buffer); break; default: BX_CHECK(false, "TextureType not supported yet"); } if (!addBitmap(glyphInfo, m_buffer) ) { return false; } glyphInfo.advance_x = (glyphInfo.advance_x * fontInfo.scale); glyphInfo.advance_y = (glyphInfo.advance_y * fontInfo.scale); glyphInfo.offset_x = (glyphInfo.offset_x * fontInfo.scale); glyphInfo.offset_y = (glyphInfo.offset_y * fontInfo.scale); glyphInfo.height = (glyphInfo.height * fontInfo.scale); glyphInfo.width = (glyphInfo.width * fontInfo.scale); font.cachedGlyphs[_codePoint] = glyphInfo; return true; } if (isValid(font.masterFontHandle) && preloadGlyph(font.masterFontHandle, _codePoint) ) { const GlyphInfo* glyph = getGlyphInfo(font.masterFontHandle, _codePoint); GlyphInfo glyphInfo = *glyph; glyphInfo.advance_x = (glyphInfo.advance_x * fontInfo.scale); glyphInfo.advance_y = (glyphInfo.advance_y * fontInfo.scale); glyphInfo.offset_x = (glyphInfo.offset_x * fontInfo.scale); glyphInfo.offset_y = (glyphInfo.offset_y * fontInfo.scale); glyphInfo.height = (glyphInfo.height * fontInfo.scale); glyphInfo.width = (glyphInfo.width * fontInfo.scale); font.cachedGlyphs[_codePoint] = glyphInfo; return true; } return false; }
F32 LLFont::getXAdvance(const llwchar wch) const { if (mFTFace == NULL) return 0.0; llassert(!mIsFallback); U32 glyph_index; // Return existing info only if it is current LLFontGlyphInfo* gi = getGlyphInfo(wch); if (gi && gi->mMetricsValid) { return gi->mXAdvance; } const LLFont* fontp = this; // Initialize char to glyph map glyph_index = FT_Get_Char_Index(mFTFace, wch); if (glyph_index == 0 && mFallbackFontp) { LLFontList::iterator iter; for(iter = mFallbackFontp->begin(); (iter != mFallbackFontp->end()) && (glyph_index == 0); iter++) { glyph_index = FT_Get_Char_Index((*iter)->mFTFace, wch); if(glyph_index) { fontp = *iter; } } } if (glyph_index) { // This font has this glyph fontp->renderGlyph(glyph_index); // Create the entry if it's not there char_glyph_info_map_t::iterator iter2 = mCharGlyphInfoMap.find(wch); if (iter2 == mCharGlyphInfoMap.end()) { gi = new LLFontGlyphInfo(glyph_index); insertGlyphInfo(wch, gi); } else { gi = iter2->second; } gi->mWidth = fontp->mFTFace->glyph->bitmap.width; gi->mHeight = fontp->mFTFace->glyph->bitmap.rows; // Convert these from 26.6 units to float pixels. gi->mXAdvance = fontp->mFTFace->glyph->advance.x / 64.f; gi->mYAdvance = fontp->mFTFace->glyph->advance.y / 64.f; gi->mMetricsValid = TRUE; return gi->mXAdvance; } else { gi = get_if_there(mCharGlyphInfoMap, (llwchar)0, (LLFontGlyphInfo*)NULL); if (gi) { return gi->mXAdvance; } } // Last ditch fallback - no glyphs defined at all. return (F32)mFontBitmapCachep->getMaxCharWidth(); }
S32 LLFontGL::render(const LLWString &wstr, const S32 begin_offset, const F32 x, const F32 y, const LLColor4 &color, const HAlign halign, const VAlign valign, U8 style, const S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_embedded, BOOL use_ellipses) const { if(!sDisplayFont) //do not display texts { return wstr.length() ; } LLGLEnable tex(GL_TEXTURE_2D); if (wstr.empty()) { return 0; } S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX); // HACK for better bolding if (style & BOLD) { if (this == LLFontGL::sSansSerif) { return LLFontGL::sSansSerifBold->render( wstr, begin_offset, x, y, color, halign, valign, (style & ~BOLD), max_chars, max_pixels, right_x, use_embedded); } } F32 drop_shadow_strength = 0.f; if (style & (DROP_SHADOW | DROP_SHADOW_SOFT)) { F32 luminance; color.calcHSL(NULL, NULL, &luminance); drop_shadow_strength = clamp_rescale(luminance, 0.35f, 0.6f, 0.f, 1.f); if (luminance < 0.35f) { style = style & ~(DROP_SHADOW | DROP_SHADOW_SOFT); } } gGL.pushMatrix(); glLoadIdentity(); gGL.translatef(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ); //glScalef(sScaleX, sScaleY, 1.0f); // avoid half pixels // RN: if we're going to this trouble, might as well snap to nearest pixel all the time // but the plan is to get rid of this so that fonts "just work" //F32 half_pixel_distance = llabs(fmodf(sCurOrigin.mX * sScaleX, 1.f) - 0.5f); //if (half_pixel_distance < PIXEL_BORDER_THRESHOLD) //{ gGL.translatef(PIXEL_CORRECTION_DISTANCE*sScaleX, 0.f, 0.f); //} // this code would just snap to pixel grid, although it seems to introduce more jitter //F32 pixel_offset_x = llround(sCurOrigin.mX * sScaleX) - (sCurOrigin.mX * sScaleX); //F32 pixel_offset_y = llround(sCurOrigin.mY * sScaleY) - (sCurOrigin.mY * sScaleY); //gGL.translatef(-pixel_offset_x, -pixel_offset_y, 0.f); // scale back to native pixel size //glScalef(1.f / sScaleX, 1.f / sScaleY, 1.f); //glScaled(1.0 / (F64) sScaleX, 1.0 / (F64) sScaleY, 1.0f); LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS); gGL.color4fv( color.mV ); S32 chars_drawn = 0; S32 i; S32 length; if (-1 == max_chars) { length = (S32)wstr.length() - begin_offset; } else { length = llmin((S32)wstr.length() - begin_offset, max_chars ); } F32 cur_x, cur_y, cur_render_x, cur_render_y; // Bind the font texture mImageGLp->bind(0); // Not guaranteed to be set correctly gGL.setSceneBlendType(LLRender::BT_ALPHA); cur_x = ((F32)x * sScaleX); cur_y = ((F32)y * sScaleY); // Offset y by vertical alignment. switch (valign) { case TOP: cur_y -= mAscender; break; case BOTTOM: cur_y += mDescender; break; case VCENTER: cur_y -= ((mAscender - mDescender)/2.f); break; case BASELINE: // Baseline, do nothing. break; default: break; } switch (halign) { case LEFT: break; case RIGHT: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)); break; case HCENTER: cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2; break; default: break; } // Round properly. //cur_render_y = (F32)llfloor(cur_y/sScaleY + 0.5f)*sScaleY; //cur_render_x = (F32)llfloor(cur_x/sScaleX + 0.5f)*sScaleX; cur_render_y = cur_y; cur_render_x = cur_x; F32 start_x = cur_x; F32 inv_width = 1.f / mImageGLp->getWidth(); F32 inv_height = 1.f / mImageGLp->getHeight(); const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; BOOL draw_ellipses = FALSE; if (use_ellipses && halign == LEFT) { // check for too long of a string if (getWidthF32(wstr.c_str(), 0, max_chars) * sScaleX > scaled_max_pixels) { // use four dots for ellipsis width to generate padding const LLWString dots(utf8str_to_wstring(std::string("...."))); scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str()))); draw_ellipses = TRUE; } } for (i = begin_offset; i < begin_offset + length; i++) { llwchar wch = wstr[i]; // Handle embedded characters first, if they're enabled. // Embedded characters are a hack for notecards const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; if (ext_data) { LLImageGL* ext_image = ext_data->mImage; const LLWString& label = ext_data->mLabel; F32 ext_height = (F32)ext_image->getHeight() * sScaleY; F32 ext_width = (F32)ext_image->getWidth() * sScaleX; F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width; if (!label.empty()) { ext_advance += (EXT_X_BEARING + gExtCharFont->getWidthF32( label.c_str() )) * sScaleX; } if (start_x + scaled_max_pixels < cur_x + ext_advance) { // Not enough room for this character. break; } ext_image->bind(); const F32 ext_x = cur_render_x + (EXT_X_BEARING * sScaleX); const F32 ext_y = cur_render_y + (EXT_Y_BEARING * sScaleY + mAscender - mLineHeight); LLRectf uv_rect(0.f, 1.f, 1.f, 0.f); LLRectf screen_rect(ext_x, ext_y + ext_height, ext_x + ext_width, ext_y); drawGlyph(screen_rect, uv_rect, LLColor4::white, style, drop_shadow_strength); if (!label.empty()) { gGL.pushMatrix(); //glLoadIdentity(); //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); //glScalef(sScaleX, sScaleY, 1.f); gExtCharFont->render(label, 0, /*llfloor*/((ext_x + (F32)ext_image->getWidth() + EXT_X_BEARING) / sScaleX), /*llfloor*/(cur_y / sScaleY), color, halign, BASELINE, NORMAL, S32_MAX, S32_MAX, NULL, TRUE ); gGL.popMatrix(); } gGL.color4fv(color.mV); chars_drawn++; cur_x += ext_advance; if (((i + 1) < length) && wstr[i+1]) { cur_x += EXT_KERNING * sScaleX; } cur_render_x = cur_x; // Bind the font texture mImageGLp->bind(); } else { if (!hasGlyph(wch)) { (const_cast<LLFontGL*>(this))->addChar(wch); } const LLFontGlyphInfo* fgi= getGlyphInfo(wch); if (!fgi) { llerrs << "Missing Glyph Info" << llendl; break; } if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth)) { // Not enough room for this character. break; } // Draw the text at the appropriate location //Specify vertices and texture coordinates LLRectf uv_rect((fgi->mXBitmapOffset - PAD_AMT) * inv_width, (fgi->mYBitmapOffset + fgi->mHeight + PAD_AMT) * inv_height, (fgi->mXBitmapOffset + fgi->mWidth + PAD_AMT) * inv_width, (fgi->mYBitmapOffset - PAD_AMT) * inv_height); LLRectf screen_rect(cur_render_x + (F32)fgi->mXBearing - PAD_AMT, cur_render_y + (F32)fgi->mYBearing + PAD_AMT, cur_render_x + (F32)fgi->mXBearing + (F32)fgi->mWidth + PAD_AMT, cur_render_y + (F32)fgi->mYBearing - (F32)fgi->mHeight - PAD_AMT); drawGlyph(screen_rect, uv_rect, color, style, drop_shadow_strength); chars_drawn++; cur_x += fgi->mXAdvance; cur_y += fgi->mYAdvance; llwchar next_char = wstr[i+1]; if (next_char && (next_char < LAST_CHARACTER)) { // Kern this puppy. if (!hasGlyph(next_char)) { (const_cast<LLFontGL*>(this))->addChar(next_char); } cur_x += getXKerning(wch, next_char); } // Round after kerning. // Must do this to cur_x, not just to cur_render_x, otherwise you // will squish sub-pixel kerned characters too close together. // For example, "CCCCC" looks bad. cur_x = (F32)llfloor(cur_x + 0.5f); //cur_y = (F32)llfloor(cur_y + 0.5f); cur_render_x = cur_x; cur_render_y = cur_y; } } if (right_x) { *right_x = cur_x / sScaleX; } if (style & UNDERLINE) { LLGLSNoTexture no_texture; gGL.begin(LLVertexBuffer::LINES); gGL.vertex2f(start_x, cur_y - (mDescender)); gGL.vertex2f(cur_x, cur_y - (mDescender)); gGL.end(); } // *FIX: get this working in all alignment cases, etc. if (draw_ellipses) { // recursively render ellipses at end of string // we've already reserved enough room gGL.pushMatrix(); //glLoadIdentity(); //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); //glScalef(sScaleX, sScaleY, 1.f); renderUTF8(std::string("..."), 0, cur_x / sScaleX, (F32)y, color, LEFT, valign, style, S32_MAX, max_pixels, right_x, FALSE); gGL.popMatrix(); } gGL.popMatrix(); return chars_drawn; }
void ResourceTrueTypeFont::renderGlyphs(const GlyphHeightMap& _glyphHeightMap, const FT_Library& _ftLibrary, const FT_Face& _ftFace, FT_Int32 _ftLoadFlags, uint8* _texBuffer, int _texWidth, int _texHeight) { FT_Bitmap ftBitmap; FT_Bitmap_New(&ftBitmap); int texX = mGlyphSpacing, texY = mGlyphSpacing; for (GlyphHeightMap::const_iterator j = _glyphHeightMap.begin(); j != _glyphHeightMap.end(); ++j) { for (GlyphHeightMap::mapped_type::const_iterator i = j->second.begin(); i != j->second.end(); ++i) { GlyphInfo& info = *i->second; switch (info.codePoint) { case FontCodeType::Selected: case FontCodeType::SelectedBack: { renderGlyph<LAMode, false, false>(info, charMaskWhite, charMaskBlack, charMask.find(info.codePoint)->second, j->first, _texBuffer, _texWidth, _texHeight, texX, texY); // Manually adjust the glyph's width to zero. This prevents artifacts from appearing at the seams when // rendering multi-character selections. GlyphInfo* glyphInfo = getGlyphInfo(info.codePoint); glyphInfo->width = 0.0f; glyphInfo->uvRect.right = glyphInfo->uvRect.left; } break; case FontCodeType::Cursor: case FontCodeType::Tab: renderGlyph<LAMode, false, false>(info, charMaskWhite, charMaskBlack, charMask.find(info.codePoint)->second, j->first, _texBuffer, _texWidth, _texHeight, texX, texY); break; default: if (FT_Load_Glyph(_ftFace, i->first, _ftLoadFlags | FT_LOAD_RENDER) == 0) { if (_ftFace->glyph->bitmap.buffer != nullptr) { uint8* glyphBuffer = nullptr; switch (_ftFace->glyph->bitmap.pixel_mode) { case FT_PIXEL_MODE_GRAY: glyphBuffer = _ftFace->glyph->bitmap.buffer; break; case FT_PIXEL_MODE_MONO: // Convert the monochrome bitmap to 8-bit before rendering it. if (FT_Bitmap_Convert(_ftLibrary, &_ftFace->glyph->bitmap, &ftBitmap, 1) == 0) { // Go through the bitmap and convert all of the nonzero values to 0xFF (white). for (uint8* p = ftBitmap.buffer, * endP = p + ftBitmap.width * ftBitmap.rows; p != endP; ++p) *p ^= -*p ^ *p; glyphBuffer = ftBitmap.buffer; } break; } if (glyphBuffer != nullptr) renderGlyph<LAMode, true, Antialias>(info, charMaskWhite, charMaskWhite, charMaskWhite, j->first, _texBuffer, _texWidth, _texHeight, texX, texY, glyphBuffer); } } else { MYGUI_LOG(Warning, "ResourceTrueTypeFont: Cannot render glyph " << i->first << " for character " << info.codePoint << " in font '" << getResourceName() << "'."); } break; } } } FT_Bitmap_Done(_ftLibrary, &ftBitmap); }
void ResourceTrueTypeFont::initialiseFreeType() { //-------------------------------------------------------------------// // Initialise FreeType and load the font. //-------------------------------------------------------------------// FT_Library ftLibrary; if (FT_Init_FreeType(&ftLibrary) != 0) MYGUI_EXCEPT("ResourceTrueTypeFont: Could not init the FreeType library!"); uint8* fontBuffer = nullptr; FT_Face ftFace = loadFace(ftLibrary, fontBuffer); if (ftFace == nullptr) { MYGUI_LOG(Error, "ResourceTrueTypeFont: Could not load the font '" << getResourceName() << "'!"); return; } //-------------------------------------------------------------------// // Calculate the font metrics. //-------------------------------------------------------------------// // The font's overall ascent and descent are defined in three different places in a TrueType font, and with different // values in each place. The most reliable source for these metrics is usually the "usWinAscent" and "usWinDescent" pair of // values in the OS/2 header; however, some fonts contain inaccurate data there. To be safe, we use the highest of the set // of values contained in the face metrics and the two sets of values contained in the OS/2 header. int fontAscent = ftFace->size->metrics.ascender >> 6; int fontDescent = -ftFace->size->metrics.descender >> 6; TT_OS2* os2 = (TT_OS2*)FT_Get_Sfnt_Table(ftFace, ft_sfnt_os2); if (os2 != nullptr) { setMax(fontAscent, os2->usWinAscent * ftFace->size->metrics.y_ppem / ftFace->units_per_EM); setMax(fontDescent, os2->usWinDescent * ftFace->size->metrics.y_ppem / ftFace->units_per_EM); setMax(fontAscent, os2->sTypoAscender * ftFace->size->metrics.y_ppem / ftFace->units_per_EM); setMax(fontDescent, -os2->sTypoDescender * ftFace->size->metrics.y_ppem / ftFace->units_per_EM); } // The nominal font height is calculated as the sum of its ascent and descent as specified by the font designer. Previously // it was defined by MyGUI in terms of the maximum ascent and descent of the glyphs currently in use, but this caused the // font's line spacing to change whenever glyphs were added to or removed from the font definition. Doing it this way // instead prevents a lot of layout problems, and it is also more typographically correct and more aesthetically pleasing. mDefaultHeight = fontAscent + fontDescent; // Set the load flags based on the specified type of hinting. FT_Int32 ftLoadFlags; switch (mHinting) { case HintingForceAuto: ftLoadFlags = FT_LOAD_FORCE_AUTOHINT; break; case HintingDisableAuto: ftLoadFlags = FT_LOAD_NO_AUTOHINT; break; case HintingDisableAll: // When hinting is completely disabled, glyphs must always be rendered -- even during layout calculations -- due to // discrepancies between the glyph metrics and the actual rendered bitmap metrics. ftLoadFlags = FT_LOAD_NO_HINTING | FT_LOAD_RENDER; break; case HintingUseNative: ftLoadFlags = FT_LOAD_DEFAULT; break; } //-------------------------------------------------------------------// // Create the glyphs and calculate their metrics. //-------------------------------------------------------------------// GlyphHeightMap glyphHeightMap; int texWidth = 0; // Before creating the glyphs, add the "Space" code point to force that glyph to be created. For historical reasons, MyGUI // users are accustomed to omitting this code point in their font definitions. addCodePoint(FontCodeType::Space); // Create the standard glyphs. for (CharMap::iterator iter = mCharMap.begin(); iter != mCharMap.end(); ) { const Char& codePoint = iter->first; FT_UInt glyphIndex = FT_Get_Char_Index(ftFace, codePoint); texWidth += createFaceGlyph(glyphIndex, codePoint, fontAscent, ftFace, ftLoadFlags, glyphHeightMap); // If the newly created glyph is the "Not Defined" glyph, it means that the code point is not supported by the font. // Remove it from the character map so that we can provide our own substitute instead of letting FreeType do it. if (iter->second != 0) ++iter; else mCharMap.erase(iter++); } // Do some special handling for the "Space" and "Tab" glyphs. GlyphInfo* spaceGlyphInfo = getGlyphInfo(FontCodeType::Space); if (spaceGlyphInfo != nullptr && spaceGlyphInfo->codePoint == FontCodeType::Space) { // Adjust the width of the "Space" glyph if it has been customized. if (mSpaceWidth != 0.0f) { texWidth += (int)std::ceil(mSpaceWidth) - (int)std::ceil(spaceGlyphInfo->width); spaceGlyphInfo->width = mSpaceWidth; spaceGlyphInfo->advance = mSpaceWidth; } // If the width of the "Tab" glyph hasn't been customized, make it eight spaces wide. if (mTabWidth == 0.0f) mTabWidth = mDefaultTabWidth * spaceGlyphInfo->advance; } // Create the special glyphs. They must be created after the standard glyphs so that they take precedence in case of a // collision. To make sure that the indices of the special glyphs don't collide with any glyph indices in the font, we must // use glyph indices higher than the highest glyph index in the font. FT_UInt nextGlyphIndex = (FT_UInt)ftFace->num_glyphs; float height = (float)mDefaultHeight; texWidth += createGlyph(nextGlyphIndex++, GlyphInfo(static_cast<Char>(FontCodeType::Tab), 0.0f, 0.0f, mTabWidth, 0.0f, 0.0f), glyphHeightMap); texWidth += createGlyph(nextGlyphIndex++, GlyphInfo(static_cast<Char>(FontCodeType::Selected), mSelectedWidth, height, 0.0f, 0.0f, 0.0f), glyphHeightMap); texWidth += createGlyph(nextGlyphIndex++, GlyphInfo(static_cast<Char>(FontCodeType::SelectedBack), mSelectedWidth, height, 0.0f, 0.0f, 0.0f), glyphHeightMap); texWidth += createGlyph(nextGlyphIndex++, GlyphInfo(static_cast<Char>(FontCodeType::Cursor), mCursorWidth, height, 0.0f, 0.0f, 0.0f), glyphHeightMap); // If a substitute code point has been specified, check to make sure that it exists in the character map. If it doesn't, // revert to the default "Not Defined" code point. This is not a real code point but rather an invalid Unicode value that // is guaranteed to cause the "Not Defined" special glyph to be created. if (mSubstituteCodePoint != FontCodeType::NotDefined && mCharMap.find(mSubstituteCodePoint) == mCharMap.end()) mSubstituteCodePoint = static_cast<Char>(FontCodeType::NotDefined); // Create the "Not Defined" code point (and its corresponding glyph) if it's in use as the substitute code point. if (mSubstituteCodePoint == FontCodeType::NotDefined) texWidth += createFaceGlyph(0, static_cast<Char>(FontCodeType::NotDefined), fontAscent, ftFace, ftLoadFlags, glyphHeightMap); // Cache a pointer to the substitute glyph info for fast lookup. mSubstituteGlyphInfo = &mGlyphMap.find(mSubstituteCodePoint)->second; // Calculate the average height of all of the glyphs that are in use. This value will be used for estimating how large the // texture needs to be. double averageGlyphHeight = 0.0; for (GlyphHeightMap::const_iterator j = glyphHeightMap.begin(); j != glyphHeightMap.end(); ++j) averageGlyphHeight += j->first * j->second.size(); averageGlyphHeight /= mGlyphMap.size(); //-------------------------------------------------------------------// // Calculate the final texture size. //-------------------------------------------------------------------// // Round the current texture width and height up to the nearest powers of two. texWidth = Bitwise::firstPO2From(texWidth); int texHeight = Bitwise::firstPO2From((int)ceil(averageGlyphHeight) + mGlyphSpacing); // At this point, the texture is only one glyph high and is probably very wide. For efficiency reasons, we need to make the // texture as square as possible. If the texture cannot be made perfectly square, make it taller than it is wide, because // the height may decrease in the final layout due to height packing. while (texWidth > texHeight) { texWidth /= 2; texHeight *= 2; } // Calculate the final layout of all of the glyphs in the texture, packing them tightly by first arranging them by height. // We assume that the texture width is fixed but that the texture height can be adjusted up or down depending on how much // space is actually needed. // In most cases, the final texture will end up square or almost square. In some rare cases, however, we can end up with a // texture that's more than twice as high as it is wide; when this happens, we double the width and try again. do { if (texHeight > texWidth * 2) texWidth *= 2; int texX = mGlyphSpacing; int texY = mGlyphSpacing; for (GlyphHeightMap::const_iterator j = glyphHeightMap.begin(); j != glyphHeightMap.end(); ++j) { for (GlyphHeightMap::mapped_type::const_iterator i = j->second.begin(); i != j->second.end(); ++i) { GlyphInfo& info = *i->second; int glyphWidth = (int)std::ceil(info.width); int glyphHeight = (int)std::ceil(info.height); autoWrapGlyphPos(glyphWidth, texWidth, glyphHeight, texX, texY); if (glyphWidth > 0) texX += mGlyphSpacing + glyphWidth; } } texHeight = Bitwise::firstPO2From(texY + glyphHeightMap.rbegin()->first); } while (texHeight > texWidth * 2); //-------------------------------------------------------------------// // Create the texture and render the glyphs onto it. //-------------------------------------------------------------------// if (mTexture) { RenderManager::getInstance().destroyTexture( mTexture ); mTexture = nullptr; } mTexture = RenderManager::getInstance().createTexture(MyGUI::utility::toString((size_t)this, "_TrueTypeFont")); mTexture->createManual(texWidth, texHeight, TextureUsage::Static | TextureUsage::Write, Pixel<LAMode>::getFormat()); mTexture->setInvalidateListener(this); uint8* texBuffer = static_cast<uint8*>(mTexture->lock(TextureUsage::Write)); if (texBuffer != nullptr) { // Make the texture background transparent white. for (uint8* dest = texBuffer, * endDest = dest + texWidth * texHeight * Pixel<LAMode>::getNumBytes(); dest != endDest; ) Pixel<LAMode, false, false>::set(dest, charMaskWhite, charMaskBlack); renderGlyphs<LAMode, Antialias>(glyphHeightMap, ftLibrary, ftFace, ftLoadFlags, texBuffer, texWidth, texHeight); mTexture->unlock(); MYGUI_LOG(Info, "ResourceTrueTypeFont: Font '" << getResourceName() << "' using texture size " << texWidth << " x " << texHeight << "."); MYGUI_LOG(Info, "ResourceTrueTypeFont: Font '" << getResourceName() << "' using real height " << mDefaultHeight << " pixels."); } else { MYGUI_LOG(Error, "ResourceTrueTypeFont: Error locking texture; pointer is nullptr."); } FT_Done_Face(ftFace); FT_Done_FreeType(ftLibrary); delete [] fontBuffer; }