595 lines
14 KiB
C++
595 lines
14 KiB
C++
/**
|
|
* @file llfontfreetype.cpp
|
|
* @brief Freetype font library wrapper
|
|
*
|
|
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "llfontfreetype.h"
|
|
#include "llfontgl.h"
|
|
|
|
// Freetype stuff
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
|
|
|
|
#include "llerror.h"
|
|
#include "llimage.h"
|
|
//#include "llimagej2c.h"
|
|
#include "llmath.h" // Linden math
|
|
#include "llstring.h"
|
|
//#include "imdebug.h"
|
|
#include "llfontbitmapcache.h"
|
|
#include "llgl.h"
|
|
|
|
FT_Render_Mode gFontRenderMode = FT_RENDER_MODE_NORMAL;
|
|
|
|
LLFontManager *gFontManagerp = NULL;
|
|
|
|
FT_Library gFTLibrary = NULL;
|
|
|
|
bool LLFontFreetype::sOpenGLcrashOnRestart = false;
|
|
|
|
//static
|
|
void LLFontManager::initClass()
|
|
{
|
|
if (!gFontManagerp)
|
|
{
|
|
gFontManagerp = new LLFontManager;
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLFontManager::cleanupClass()
|
|
{
|
|
delete gFontManagerp;
|
|
gFontManagerp = NULL;
|
|
}
|
|
|
|
LLFontManager::LLFontManager()
|
|
{
|
|
int error;
|
|
error = FT_Init_FreeType(&gFTLibrary);
|
|
if (error)
|
|
{
|
|
// Clean up freetype libs.
|
|
LL_ERRS() << "Freetype initialization failure!" << LL_ENDL;
|
|
FT_Done_FreeType(gFTLibrary);
|
|
}
|
|
}
|
|
|
|
LLFontManager::~LLFontManager()
|
|
{
|
|
FT_Done_FreeType(gFTLibrary);
|
|
}
|
|
|
|
|
|
LLFontGlyphInfo::LLFontGlyphInfo(U32 index)
|
|
: mGlyphIndex(index),
|
|
mWidth(0), // In pixels
|
|
mHeight(0), // In pixels
|
|
mXAdvance(0.f), // In pixels
|
|
mYAdvance(0.f), // In pixels
|
|
mXBitmapOffset(0), // Offset to the origin in the bitmap
|
|
mYBitmapOffset(0), // Offset to the origin in the bitmap
|
|
mXBearing(0), // Distance from baseline to left in pixels
|
|
mYBearing(0), // Distance from baseline to top in pixels
|
|
mBitmapNum(0) // Which bitmap in the bitmap cache contains this glyph
|
|
{
|
|
}
|
|
|
|
LLFontFreetype::LLFontFreetype()
|
|
: mFontBitmapCachep(new LLFontBitmapCache),
|
|
mAscender(0.f),
|
|
mDescender(0.f),
|
|
mLineHeight(0.f),
|
|
mIsFallback(FALSE),
|
|
mFTFace(NULL),
|
|
mRenderGlyphCount(0),
|
|
mAddGlyphCount(0),
|
|
mStyle(0),
|
|
mPointSize(0)
|
|
{
|
|
}
|
|
|
|
|
|
LLFontFreetype::~LLFontFreetype()
|
|
{
|
|
// Clean up freetype libs.
|
|
if (mFTFace)
|
|
FT_Done_Face(mFTFace);
|
|
mFTFace = NULL;
|
|
|
|
// Delete glyph info
|
|
std::for_each(mCharGlyphInfoMap.begin(), mCharGlyphInfoMap.end(), DeletePairedPointer());
|
|
|
|
// mFontBitmapCachep will be cleaned up by LLPointer destructor.
|
|
}
|
|
|
|
BOOL LLFontFreetype::loadFace(const std::string& filename, const F32 point_size, const F32 vert_dpi, const F32 horz_dpi, const S32 components, BOOL is_fallback)
|
|
{
|
|
// Don't leak face objects. This is also needed to deal with
|
|
// changed font file names.
|
|
if (mFTFace)
|
|
{
|
|
FT_Done_Face(mFTFace);
|
|
mFTFace = NULL;
|
|
}
|
|
|
|
LL_INFOS() << "Loading font file: " << filename << LL_ENDL;
|
|
|
|
int error;
|
|
|
|
error = FT_New_Face( gFTLibrary,
|
|
filename.c_str(),
|
|
0,
|
|
&mFTFace );
|
|
|
|
if (error)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
mIsFallback = is_fallback;
|
|
F32 pixels_per_em = (point_size / 72.f)*vert_dpi; // Size in inches * dpi
|
|
|
|
error = FT_Set_Char_Size(mFTFace, /* handle to face object */
|
|
0, /* char_width in 1/64th of points */
|
|
(S32)(point_size*64), /* char_height in 1/64th of points */
|
|
(U32)horz_dpi, /* horizontal device resolution */
|
|
(U32)vert_dpi); /* vertical device resolution */
|
|
|
|
if (error)
|
|
{
|
|
// Clean up freetype libs.
|
|
FT_Done_Face(mFTFace);
|
|
mFTFace = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
F32 y_max, y_min, x_max, x_min;
|
|
F32 ems_per_unit = 1.f/ mFTFace->units_per_EM;
|
|
F32 pixels_per_unit = pixels_per_em * ems_per_unit;
|
|
|
|
// Get size of bbox in pixels
|
|
y_max = mFTFace->bbox.yMax * pixels_per_unit;
|
|
y_min = mFTFace->bbox.yMin * pixels_per_unit;
|
|
x_max = mFTFace->bbox.xMax * pixels_per_unit;
|
|
x_min = mFTFace->bbox.xMin * pixels_per_unit;
|
|
mAscender = mFTFace->ascender * pixels_per_unit;
|
|
mDescender = -mFTFace->descender * pixels_per_unit;
|
|
mLineHeight = mFTFace->height * pixels_per_unit;
|
|
|
|
S32 max_char_width = ll_pos_round(0.5f + (x_max - x_min));
|
|
S32 max_char_height = ll_pos_round(0.5f + (y_max - y_min));
|
|
|
|
mFontBitmapCachep->init(components, max_char_width, max_char_height);
|
|
|
|
if (!mFTFace->charmap)
|
|
{
|
|
//LL_INFOS() << " no unicode encoding, set whatever encoding there is..." << LL_ENDL;
|
|
FT_Set_Charmap(mFTFace, mFTFace->charmaps[0]);
|
|
}
|
|
|
|
if (!mIsFallback || !sOpenGLcrashOnRestart) // because this often crashes under Linux...
|
|
{
|
|
// Add the default glyph
|
|
addGlyphFromFont(this, 0, 0);
|
|
}
|
|
|
|
mName = filename;
|
|
mPointSize = point_size;
|
|
|
|
mStyle = LLFontGL::NORMAL;
|
|
if(mFTFace->style_flags & FT_STYLE_FLAG_BOLD)
|
|
{
|
|
mStyle |= LLFontGL::BOLD;
|
|
mStyle &= ~LLFontGL::NORMAL;
|
|
}
|
|
|
|
if(mFTFace->style_flags & FT_STYLE_FLAG_ITALIC)
|
|
{
|
|
mStyle |= LLFontGL::ITALIC;
|
|
mStyle &= ~LLFontGL::NORMAL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void LLFontFreetype::setFallbackFonts(const font_vector_t &font)
|
|
{
|
|
mFallbackFonts = font;
|
|
}
|
|
|
|
const LLFontFreetype::font_vector_t &LLFontFreetype::getFallbackFonts() const
|
|
{
|
|
return mFallbackFonts;
|
|
}
|
|
|
|
F32 LLFontFreetype::getLineHeight() const
|
|
{
|
|
return mLineHeight;
|
|
}
|
|
|
|
F32 LLFontFreetype::getAscenderHeight() const
|
|
{
|
|
return mAscender;
|
|
}
|
|
|
|
F32 LLFontFreetype::getDescenderHeight() const
|
|
{
|
|
return mDescender;
|
|
}
|
|
|
|
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(const LLFontGlyphInfo* glyph) const
|
|
{
|
|
if (mFTFace == NULL)
|
|
return 0.0;
|
|
|
|
return glyph->mXAdvance;
|
|
}
|
|
|
|
F32 LLFontFreetype::getXKerning(llwchar char_left, llwchar char_right) const
|
|
{
|
|
if (mFTFace == nullptr)
|
|
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);
|
|
}
|
|
|
|
F32 LLFontFreetype::getXKerning(const LLFontGlyphInfo* left_glyph_info, const LLFontGlyphInfo* right_glyph_info) const
|
|
{
|
|
if (mFTFace == nullptr)
|
|
return 0.0;
|
|
|
|
U32 left_glyph = left_glyph_info ? left_glyph_info->mGlyphIndex : 0;
|
|
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 LLFontFreetype::hasGlyph(llwchar wch) const
|
|
{
|
|
llassert(!mIsFallback);
|
|
return(mCharGlyphInfoMap.find(wch) != mCharGlyphInfoMap.end());
|
|
}
|
|
|
|
LLFontGlyphInfo* LLFontFreetype::addGlyph(llwchar wch) const
|
|
{
|
|
if (mFTFace == NULL)
|
|
return FALSE;
|
|
|
|
llassert(!mIsFallback);
|
|
//LL_DEBUGS() << "Adding new glyph for " << wch << " to font" << LL_ENDL;
|
|
|
|
FT_UInt glyph_index;
|
|
|
|
// Initialize char to glyph map
|
|
glyph_index = FT_Get_Char_Index(mFTFace, wch);
|
|
if (glyph_index == 0)
|
|
{
|
|
//LL_INFOS() << "Trying to add glyph from fallback font!" << LL_ENDL;
|
|
font_vector_t::const_iterator iter;
|
|
for(iter = mFallbackFonts.begin(); iter != mFallbackFonts.end(); iter++)
|
|
{
|
|
glyph_index = FT_Get_Char_Index((*iter)->mFTFace, wch);
|
|
if (glyph_index)
|
|
{
|
|
return addGlyphFromFont(*iter, wch, glyph_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
char_glyph_info_map_t::iterator iter = mCharGlyphInfoMap.find(wch);
|
|
if (iter == mCharGlyphInfoMap.end())
|
|
{
|
|
return addGlyphFromFont(this, wch, glyph_index);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, llwchar wch, U32 glyph_index) const
|
|
{
|
|
if (mFTFace == NULL)
|
|
return NULL;
|
|
|
|
//llassert(!mIsFallback);
|
|
fontp->renderGlyph(glyph_index);
|
|
S32 width = fontp->mFTFace->glyph->bitmap.width;
|
|
S32 height = fontp->mFTFace->glyph->bitmap.rows;
|
|
|
|
S32 pos_x, pos_y;
|
|
S32 bitmap_num;
|
|
mFontBitmapCachep->nextOpenPos(width, pos_x, pos_y, bitmap_num);
|
|
mAddGlyphCount++;
|
|
|
|
LLFontGlyphInfo* gi = new LLFontGlyphInfo(glyph_index);
|
|
gi->mXBitmapOffset = pos_x;
|
|
gi->mYBitmapOffset = pos_y;
|
|
gi->mBitmapNum = bitmap_num;
|
|
gi->mWidth = width;
|
|
gi->mHeight = height;
|
|
gi->mXBearing = fontp->mFTFace->glyph->bitmap_left;
|
|
gi->mYBearing = fontp->mFTFace->glyph->bitmap_top;
|
|
// 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;
|
|
|
|
insertGlyphInfo(wch, gi);
|
|
|
|
llassert(fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO
|
|
|| fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY);
|
|
|
|
if (fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO
|
|
|| fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY)
|
|
{
|
|
U8 *buffer_data = fontp->mFTFace->glyph->bitmap.buffer;
|
|
S32 buffer_row_stride = fontp->mFTFace->glyph->bitmap.pitch;
|
|
U8 *tmp_graydata = NULL;
|
|
|
|
if (fontp->mFTFace->glyph->bitmap.pixel_mode
|
|
== FT_PIXEL_MODE_MONO)
|
|
{
|
|
// need to expand 1-bit bitmap to 8-bit graymap.
|
|
tmp_graydata = new U8[width * height];
|
|
S32 xpos, ypos;
|
|
for (ypos = 0; ypos < height; ++ypos)
|
|
{
|
|
S32 bm_row_offset = buffer_row_stride * ypos;
|
|
for (xpos = 0; xpos < width; ++xpos)
|
|
{
|
|
U32 bm_col_offsetbyte = xpos / 8;
|
|
U32 bm_col_offsetbit = 7 - (xpos % 8);
|
|
U32 bit =
|
|
!!(buffer_data[bm_row_offset
|
|
+ bm_col_offsetbyte
|
|
] & (1 << bm_col_offsetbit) );
|
|
tmp_graydata[width*ypos + xpos] =
|
|
255 * bit;
|
|
}
|
|
}
|
|
// use newly-built graymap.
|
|
buffer_data = tmp_graydata;
|
|
buffer_row_stride = width;
|
|
}
|
|
|
|
switch (mFontBitmapCachep->getNumComponents())
|
|
{
|
|
case 1:
|
|
mFontBitmapCachep->getImageRaw(bitmap_num)->setSubImage(pos_x,
|
|
pos_y,
|
|
width,
|
|
height,
|
|
buffer_data,
|
|
buffer_row_stride,
|
|
TRUE);
|
|
break;
|
|
case 2:
|
|
setSubImageLuminanceAlpha(pos_x,
|
|
pos_y,
|
|
bitmap_num,
|
|
width,
|
|
height,
|
|
buffer_data,
|
|
buffer_row_stride);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (tmp_graydata)
|
|
delete[] tmp_graydata;
|
|
} else {
|
|
// we don't know how to handle this pixel format from FreeType;
|
|
// omit it from the font-image.
|
|
}
|
|
|
|
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 gi;
|
|
}
|
|
|
|
LLFontGlyphInfo* LLFontFreetype::getGlyphInfo(llwchar wch) const
|
|
{
|
|
char_glyph_info_map_t::iterator iter = mCharGlyphInfoMap.find(wch);
|
|
if (iter != mCharGlyphInfoMap.end())
|
|
{
|
|
return iter->second;
|
|
}
|
|
else
|
|
{
|
|
// this glyph doesn't yet exist, so render it and return the result
|
|
return addGlyph(wch);
|
|
}
|
|
}
|
|
|
|
void LLFontFreetype::insertGlyphInfo(llwchar wch, LLFontGlyphInfo* gi) const
|
|
{
|
|
char_glyph_info_map_t::iterator iter = mCharGlyphInfoMap.find(wch);
|
|
if (iter != mCharGlyphInfoMap.end())
|
|
{
|
|
delete iter->second;
|
|
iter->second = gi;
|
|
}
|
|
else
|
|
{
|
|
mCharGlyphInfoMap[wch] = gi;
|
|
}
|
|
}
|
|
|
|
void LLFontFreetype::renderGlyph(const U32 glyph_index) const
|
|
{
|
|
if (mFTFace == NULL)
|
|
return;
|
|
|
|
if (FT_Load_Glyph(mFTFace, glyph_index, FT_LOAD_RENDER | FT_LOAD_TARGET_LIGHT) != 0)
|
|
{
|
|
// If glyph fails to load and/or render, render a fallback character
|
|
llassert_always(!FT_Load_Char(mFTFace, L'?', FT_LOAD_RENDER | FT_LOAD_TARGET_LIGHT));
|
|
}
|
|
|
|
mRenderGlyphCount++;
|
|
}
|
|
|
|
void LLFontFreetype::reset(F32 vert_dpi, F32 horz_dpi)
|
|
{
|
|
resetBitmapCache();
|
|
loadFace(mName,mPointSize,vert_dpi,horz_dpi,mFontBitmapCachep->getNumComponents(),mIsFallback);
|
|
if (!mIsFallback)
|
|
{
|
|
// This is the head of the list - need to rebuild ourself and all fallbacks.
|
|
//loadFace(mName,mPointSize,vert_dpi,horz_dpi,mFontBitmapCachep->getNumComponents(),mIsFallback);
|
|
if (mFallbackFonts.empty())
|
|
{
|
|
LL_WARNS() << "LLFontGL::reset(), no fallback fonts present" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
for(font_vector_t::iterator it = mFallbackFonts.begin();
|
|
it != mFallbackFonts.end();
|
|
++it)
|
|
{
|
|
(*it)->reset(vert_dpi, horz_dpi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLFontFreetype::resetBitmapCache()
|
|
{
|
|
for_each(mCharGlyphInfoMap.begin(), mCharGlyphInfoMap.end(), DeletePairedPointer());
|
|
mCharGlyphInfoMap.clear();
|
|
|
|
mFontBitmapCachep->reset();
|
|
|
|
// Adding default glyph is skipped for fallback fonts here as well as in loadFace().
|
|
// This if was added as fix for EXT-4971.
|
|
if (!mIsFallback || !sOpenGLcrashOnRestart) // because this often crashes under Linux...
|
|
{
|
|
// Add the empty glyph
|
|
addGlyphFromFont(this, 0, 0);
|
|
}
|
|
}
|
|
|
|
void LLFontFreetype::destroyGL()
|
|
{
|
|
mFontBitmapCachep->destroyGL();
|
|
}
|
|
|
|
const std::string &LLFontFreetype::getName() const
|
|
{
|
|
return mName;
|
|
}
|
|
|
|
const LLPointer<LLFontBitmapCache> LLFontFreetype::getFontBitmapCache() const
|
|
{
|
|
return mFontBitmapCachep;
|
|
}
|
|
|
|
void LLFontFreetype::setStyle(U8 style)
|
|
{
|
|
mStyle = style;
|
|
}
|
|
|
|
U8 LLFontFreetype::getStyle() const
|
|
{
|
|
return mStyle;
|
|
}
|
|
|
|
void LLFontFreetype::setSubImageLuminanceAlpha(const U32 x, const U32 y, const U32 bitmap_num, const U32 width, const U32 height, const U8 *data, S32 stride) const
|
|
{
|
|
LLImageRaw *image_raw = mFontBitmapCachep->getImageRaw(bitmap_num);
|
|
|
|
//llassert(!mIsFallback);
|
|
llassert(image_raw && (image_raw->getComponents() == 2));
|
|
|
|
|
|
U8 *target = image_raw->getData();
|
|
|
|
if (!data)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (0 == stride)
|
|
stride = width;
|
|
|
|
U32 i, j;
|
|
U32 to_offset;
|
|
U32 from_offset;
|
|
U32 target_width = image_raw->getWidth();
|
|
for (i = 0; i < height; i++)
|
|
{
|
|
to_offset = (y + i)*target_width + x;
|
|
from_offset = (height - 1 - i)*stride;
|
|
for (j = 0; j < width; j++)
|
|
{
|
|
*(target + to_offset*2 + 1) = *(data + from_offset);
|
|
to_offset++;
|
|
from_offset++;
|
|
}
|
|
}
|
|
}
|