diff --git a/indra/cmake/DirectX.cmake b/indra/cmake/DirectX.cmake index d406f37..347b25f 100644 --- a/indra/cmake/DirectX.cmake +++ b/indra/cmake/DirectX.cmake @@ -8,6 +8,10 @@ if (VIEWER AND WINDOWS) "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2008)/Include" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2007)/Include" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2007)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2007)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2007)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2006)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2006)/Include" "C:/DX90SDK/Include" "$ENV{PROGRAMFILES}/DX90SDK/Include" ) @@ -28,6 +32,10 @@ if (VIEWER AND WINDOWS) "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2008)/Lib/x86" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2007)/Lib/x86" "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2007)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2007)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2007)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2006)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2006)/Lib/x86" "C:/DX90SDK/Lib" "$ENV{PROGRAMFILES}/DX90SDK/Lib" ) diff --git a/indra/cmake/LLRender.cmake b/indra/cmake/LLRender.cmake index bbcf4cd..d6a4585 100644 --- a/indra/cmake/LLRender.cmake +++ b/indra/cmake/LLRender.cmake @@ -1,6 +1,7 @@ # -*- cmake -*- include(FreeType) +include(Pango) set(LLRENDER_INCLUDE_DIRS ${LIBS_OPEN_DIR}/llrender diff --git a/indra/cmake/Pango.cmake b/indra/cmake/Pango.cmake new file mode 100644 index 0000000..b00ccdf --- /dev/null +++ b/indra/cmake/Pango.cmake @@ -0,0 +1,31 @@ +# -*- cmake -*- +include(Prebuilt) + +if (STANDALONE) + include(FindPkgConfig) + + pkg_check_modules(PANGO REQUIRED pango) + pkg_check_modules(PANGOCAIRO REQUIRED pangocairo) + pkg_check_modules(CAIRO REQUIRED cairo) +else (STANDALONE) + if (LINUX) + use_prebuilt_binary(gtk-atk-pango-glib) # we need pango and glib but gtk nor atk. + set(PANGO_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/include/pango-1.0 + ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/include/cairo + ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/include/glib-2.0) + else (LINUX) + set(PANGO_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/include/pango-1.0 + ${LIBS_PREBUILT_DIR}/include/cairo + ${LIBS_PREBUILT_DIR}/include/glib-2.0) + endif (LINUX) + + set(PANGO_LIBRARIES pangoft2-1.0 pangocairo-1.0 pango-1.0 cairo fontconfig gobject-2.0 gmodule-2.0 glib-2.0) +endif (STANDALONE) + +link_directories( + ${PANGO_LIBRARY_DIRS} + ${PANGOCAIRO_LIBRARY_DIRS} + ${CAIRO_LIBRARY_DIRS} + ) diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt index 381d3cf..6eacfec 100644 --- a/indra/llrender/CMakeLists.txt +++ b/indra/llrender/CMakeLists.txt @@ -4,6 +4,7 @@ project(llrender) include(00-Common) include(FreeType) +include(Pango) include(LLCommon) include(LLImage) include(LLMath) @@ -12,6 +13,9 @@ include(LLWindow) include_directories( ${FREETYPE_INCLUDE_DIRS} + ${PANGO_INCLUDE_DIRS} + ${PANGOCAIRO_INCLUDE_DIRS} + ${CAIRO_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} @@ -22,7 +26,12 @@ include_directories( set(llrender_SOURCE_FILES llcubemap.cpp llfont.cpp + llfontmanager.cpp llfontgl.cpp + llfontglwrapper.cpp + llfontfreetypegl.cpp + llfontpangogl.cpp + llfontpangocairogl.cpp llgldbg.cpp llglslshader.cpp llimagegl.cpp @@ -38,8 +47,13 @@ set(llrender_HEADER_FILES CMakeLists.txt llcubemap.h + llfontmanager.h llfontgl.h llfont.h + llfontglwrapper.h + llfontfreetypegl.h + llfontpangogl.h + llfontpangocairogl.h llgl.h llgldbg.h llglheaders.h diff --git a/indra/llrender/llfont.cpp b/indra/llrender/llfont.cpp index d7310bd..e1e8591 100644 --- a/indra/llrender/llfont.cpp +++ b/indra/llrender/llfont.cpp @@ -1,6 +1,6 @@ /** * @file llfont.cpp - * @brief Font library wrapper + * @brief Wrapper around FreeType library * * $LicenseInfo:firstyear=2002&license=viewergpl$ * @@ -55,24 +55,9 @@ FT_Render_Mode gFontRenderMode = FT_RENDER_MODE_NORMAL; -LLFontManager *gFontManagerp = NULL; - FT_Library gFTLibrary = NULL; -//static -void LLFontManager::initClass() -{ - gFontManagerp = new LLFontManager; -} - -//static -void LLFontManager::cleanupClass() -{ - delete gFontManagerp; - gFontManagerp = NULL; -} - -LLFontManager::LLFontManager() +LLFontManagerFreeTypeBase::LLFontManagerFreeTypeBase() { int error; error = FT_Init_FreeType(&gFTLibrary); @@ -85,7 +70,7 @@ LLFontManager::LLFontManager() } -LLFontManager::~LLFontManager() +LLFontManagerFreeTypeBase::~LLFontManagerFreeTypeBase() { FT_Done_FreeType(gFTLibrary); } @@ -146,6 +131,9 @@ LLFont::LLFont(LLImageRaw *imagep) LLFont::~LLFont() { + delete mFallbackFontp; + mFallbackFontp = NULL; + mRawImagep = NULL; // dereferences or deletes image // Clean up freetype libs. @@ -162,24 +150,6 @@ void LLFont::setRawImage(LLImageRaw *imagep) mRawImagep = imagep; // will delete old raw image if we have one and created it } -// virtual -F32 LLFont::getLineHeight() const -{ - return mLineHeight; -} - -// virtual -F32 LLFont::getAscenderHeight() const -{ - return mAscender; -} - -// virtual -F32 LLFont::getDescenderHeight() const -{ - return mDescender; -} - BOOL LLFont::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 @@ -264,7 +234,7 @@ BOOL LLFont::loadFace(const std::string& filename, const F32 point_size, const F image_width = llmin(512, image_width); // Don't make bigger than 512x512, ever. S32 image_height = image_width; - //llinfos << "Guessing texture size of " << image_width << " pixels square" << llendl; + llinfos << "Guessing texture size of " << image_width << " pixels square" << llendl; mRawImagep->resize(image_width, image_height, components); @@ -662,3 +632,9 @@ void LLFont::setSubImageLuminanceAlpha(const U32 x, } } } + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfont.h b/indra/llrender/llfont.h index bce8d76..02ee7ad 100644 --- a/indra/llrender/llfont.h +++ b/indra/llrender/llfont.h @@ -1,6 +1,6 @@ /** * @file llfont.h - * @brief Font library wrapper + * @brief Wrapper around FreeType library * * $LicenseInfo:firstyear=2002&license=viewergpl$ * @@ -32,13 +32,14 @@ #ifndef LL_LLFONT_H #define LL_LLFONT_H +#include "llfontmanager.h" + #include //#include "lllocalidhashmap.h" #include "llmemory.h" #include "llstl.h" class LLImageRaw; -class LLFontManager; class LLFont; // Hack. FT_Face is just a typedef for a pointer to a struct, @@ -48,17 +49,17 @@ class LLFont; struct FT_FaceRec_; typedef struct FT_FaceRec_* LLFT_Face; -extern LLFontManager *gFontManagerp; +// LLFontManager class, that was here in older versions has moved to +// llfontmanager.{h,cpp}. It has more functions than before to +// facilitate various font related house keeping. The original +// function, initialization and cleanup of FreeType library, is +// retained in the LLFontManagerFreeTypeBase class. -class LLFontManager +class LLFontManagerFreeTypeBase : public LLFontManager { -public: - static void initClass(); - static void cleanupClass(); - -public: - LLFontManager(); - virtual ~LLFontManager(); +protected: + LLFontManagerFreeTypeBase(); + virtual ~LLFontManagerFreeTypeBase(); }; class LLFontGlyphInfo @@ -92,6 +93,11 @@ public: }; +// In this version LLFont class acts simply as a wrapper for FreeType +// library. It _doesn't_ act as a base class for all text related +// facilities; LLFontGL acts so, and LLFont is not a base class of +// LLFontGL. + class LLFont { public: @@ -110,38 +116,6 @@ public: void setCharToGlyphMap(llwchar wch, U32 glyph_index) const; void setRawImage( LLImageRaw *imagep ); - // Global font metrics - in units of pixels - virtual F32 getLineHeight() const; - virtual F32 getAscenderHeight() const; - virtual F32 getDescenderHeight() const; - - -// For a lowercase "g": -// -// ------------------------------ -// ^ ^ -// | | -// xxx x |Ascender -// x x v | -// --------- xxxx-------------- Baseline -// ^ x | -// | Descender x | -// v xxxx |LineHeight -// ----------------------- | -// v -// ------------------------------ - - enum - { - FIRST_CHAR = 32, - NUM_CHARS = 127 - 32, - LAST_CHAR_BASIC = 127, - - // Need full 8-bit ascii range for spanish - NUM_CHARS_FULL = 255 - 32, - LAST_CHAR_FULL = 255 - }; - const LLFontGlyphInfo &getMetrics(const llwchar wc) const; F32 getXAdvance(const llwchar wc) const; F32 getXKerning(const llwchar char_left, const llwchar char_right) const; // Get the kerning between the two characters @@ -192,3 +166,9 @@ private: }; #endif // LL_FONT_ + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontfreetypegl.cpp b/indra/llrender/llfontfreetypegl.cpp new file mode 100644 index 0000000..0491710 --- /dev/null +++ b/indra/llrender/llfontfreetypegl.cpp @@ -0,0 +1,909 @@ +/** + * @file llfontfreetypegl.cpp + * @author Doug Soo + * @brief Wrapper around FreeType + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include + +#include "llfontmanager.h" +#include "llfontgl.h" +#include "llfontfreetypegl.h" +#include "llgl.h" +#include "llrender.h" +#include "v4color.h" +#include "llstl.h" + +static const S32 BOLD_OFFSET = 1; +static const S32 LAST_CHARACTER = 255; +static const F32 PIXEL_BORDER_THRESHOLD = 0.0001f; +static const F32 PIXEL_CORRECTION_DISTANCE = 0.01f; +static const F32 PAD_AMT = 0.5f; + +F32 LLFontFreeTypeGL::sVertDPI = 96.f; +F32 LLFontFreeTypeGL::sHorizDPI = 96.f; + +LLFontManagerFreeTypeGL::LLFontManagerFreeTypeGL() +{ +} + +void LLFontManagerFreeTypeGL::setDPIs(F32 horiz_dpi, F32 vert_dpi) +{ + // Why are we truncating (floor) the dpis? I don't know. I'm + // just following the LL's original LLFontGL implementation. + LLFontFreeTypeGL::sHorizDPI = (F32)llfloor(horiz_dpi); + LLFontFreeTypeGL::sVertDPI = (F32)llfloor(vert_dpi); +} + +void LLFontManagerFreeTypeGL::setFallback(const std::string &face, F32 scale) +{ + mFallbackFace = face; + mFallbackScale = scale; +} + +LLFontGL *LLFontManagerFreeTypeGL::createFont(const std::string &face, F32 size, bool console) +{ + return LLFontFreeTypeGL::createFont(face, size, mFallbackFace, mFallbackScale); +} + + +LLFontFreeTypeGL::LLFontFreeTypeGL() + : LLFont() +{ + init(); +} + +LLFontFreeTypeGL::~LLFontFreeTypeGL() +{ + mImageGLp = NULL; + mRawImageGLp = NULL; +} + +LLFontFreeTypeGL *LLFontFreeTypeGL::createFont(const std::string &face, F32 size, const std::string &fallback_face, F32 fallback_scale) +{ + LLFontList *fallback_fontp = new LLFontList(); + if (!LLFontFreeTypeGL::loadFaceFallback( + fallback_fontp, + fallback_face, + size * fallback_scale)) + { + delete fallback_fontp; + fallback_fontp = NULL; + } + + LLFontFreeTypeGL *new_fontp = new LLFontFreeTypeGL(); + if (!LLFontFreeTypeGL::loadFace(new_fontp, face, size, fallback_fontp)) + { + delete fallback_fontp; + delete new_fontp; + new_fontp = NULL; + } + + return new_fontp; +} + +void LLFontFreeTypeGL::init() +{ + if (mImageGLp.isNull()) + { + mImageGLp = new LLImageGL(FALSE); + //RN: use nearest mipmap filtering to obviate the need to do pixel-accurate positioning + gGL.getTexUnit(0)->bind(mImageGLp); + // we allow bilinear filtering to get sub-pixel positioning for drop shadows + //mImageGLp->setMipFilterNearest(TRUE, TRUE); + } + if (mRawImageGLp.isNull()) + { + mRawImageGLp = new LLImageRaw; // Note LLFontGL owns the image, not LLFont. + } + setRawImage( mRawImageGLp ); +} + +void LLFontFreeTypeGL::reset() +{ + init(); + resetBitmap(); +} + +//static +bool LLFontFreeTypeGL::loadFaceFallback(LLFontList *fontlistp, const std::string& fontname, const F32 point_size) +{ + std::string local_path = getFontPathLocal(); + std::string sys_path = getFontPathSystem(); + + // The fontname string may contain multiple font file names separated by semicolons. + // Break it apart and try loading each one, in order. + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(";"); + tokenizer tokens(fontname, sep); + tokenizer::iterator token_iter; + + for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + LLFont *fontp = new LLFont(); + std::string font_path = local_path + *token_iter; + if (!fontp->loadFace(font_path, point_size, sVertDPI, sHorizDPI, 2, TRUE)) + { + font_path = sys_path + *token_iter; + if (!fontp->loadFace(font_path, point_size, sVertDPI, sHorizDPI, 2, TRUE)) + { + LL_INFOS_ONCE("ViewerImages") << "Couldn't load font " << *token_iter << LL_ENDL; + delete fontp; + fontp = NULL; + } + } + + if(fontp) + { + fontlistp->addAtEnd(fontp); + } + } + + // We want to return true if at least one fallback font loaded correctly. + return (fontlistp->size() > 0); +} + +//static +bool LLFontFreeTypeGL::loadFace(LLFontFreeTypeGL *fontp, const std::string& fontname, const F32 point_size, LLFontList *fallback_fontp) +{ + std::string local_path = getFontPathLocal(); + std::string font_path = local_path + fontname; + if (!fontp->loadFace(font_path, point_size, sVertDPI, sHorizDPI, 2, FALSE)) + { + std::string sys_path = getFontPathSystem(); + font_path = sys_path + fontname; + if (!fontp->loadFace(font_path, point_size, sVertDPI, sHorizDPI, 2, FALSE)) + { + LL_WARNS("ViewerImages") << "Couldn't load font " << fontname << LL_ENDL; + return false; + } + } + + fontp->setFallbackFont(fallback_fontp); + return true; +} + + +BOOL LLFontFreeTypeGL::loadFace(const std::string& filename, + const F32 point_size, const F32 vert_dpi, const F32 horz_dpi, + const S32 components, BOOL is_fallback) +{ + if (!LLFont::loadFace(filename, point_size, vert_dpi, horz_dpi, components, is_fallback)) + { + return FALSE; + } + mImageGLp->createGLTexture(0, mRawImageGLp); + gGL.getTexUnit(0)->bind(mImageGLp); + mImageGLp->setMipFilterNearest(TRUE, TRUE); + return TRUE; +} + +BOOL LLFontFreeTypeGL::addChar(const llwchar wch) +{ + if (!LLFont::addChar(wch)) + { + return FALSE; + } + + stop_glerror(); + mImageGLp->setSubImage(mRawImageGLp, 0, 0, mImageGLp->getWidth(), mImageGLp->getHeight()); + gGL.getTexUnit(0)->bind(mImageGLp); + mImageGLp->setMipFilterNearest(TRUE, TRUE); + stop_glerror(); + return TRUE; +} + + +S32 LLFontFreeTypeGL::renderUTF8(const std::string &text, const S32 offset, + const F32 x, const F32 y, + const LLColor4 &color, + const HAlign halign, const VAlign valign, + U8 style, + const S32 max_chars, const S32 max_pixels, + F32* right_x, + BOOL use_embedded, + BOOL use_ellipses) const +{ + LLWString wstr = utf8str_to_wstring(text); + return render(wstr, offset, x, y, color, halign, valign, style, max_chars, max_pixels, right_x, use_embedded, use_ellipses); +} + +S32 LLFontFreeTypeGL::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() ; + } + + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + + 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 + + gGL.getTexUnit(0)->bind(mImageGLp); + + // 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(); + + 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; + + F32 ext_height = (F32)ext_image->getHeight() * sScaleY; + F32 ext_width = (F32)ext_image->getWidth() * sScaleX; + F32 ext_advance = getEmbeddedCharAdvance(ext_data); + + if (start_x + scaled_max_pixels < cur_x + ext_advance) + { + // Not enough room for this character. + break; + } + + gGL.getTexUnit(0)->bind(ext_image); + const F32 ext_x = cur_render_x + (ext_data->mXBearing * sScaleX); + const F32 ext_y = cur_render_y + (ext_data->mYBearing * 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); + + const LLWString& label = ext_data->mLabel; + if (!label.empty()) + { + gGL.pushMatrix(); + //glLoadIdentity(); + //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); + //glScalef(sScaleX, sScaleY, 1.f); + ext_data->mFont->render(label, 0, + ((ext_x + (F32)ext_image->getWidth() + ext_data->mXBearing) / sScaleX), + (cur_y / sScaleY), + color, halign); + gGL.popMatrix(); + } + + gGL.color4fv(color.mV); + + chars_drawn++; + cur_x += ext_advance; + if (((i + 1) < length) && wstr[i+1]) + { + cur_x += ext_data->mKerning * sScaleX; + } + cur_render_x = cur_x; + + // Bind the font texture + gGL.getTexUnit(0)->bind(mImageGLp); + } + else + { + if (!hasGlyph(wch)) + { + (const_cast(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(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) + { + 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, + FALSE); + gGL.popMatrix(); + } + + gGL.popMatrix(); + + return chars_drawn; +} + + +//LLImageGL *LLFontFreeTypeGL::getImageGL() const +//{ +// return mImageGLp; +//} + +F32 LLFontFreeTypeGL::getWidthF32(const std::string& utf8text, const S32 begin_offset, const S32 max_chars, BOOL use_embeded ) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidthF32(wtext.c_str(), begin_offset, max_chars); +} + +F32 LLFontFreeTypeGL::getWidthF32(const llwchar* wchars, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const +{ + F32 cur_x = 0; + const S32 max_index = begin_offset + max_chars; + for (S32 i = begin_offset; i < max_index; i++) + { + const llwchar wch = wchars[i]; + if (wch == 0) + { + break; // done + } + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + // Handle crappy embedded hack + cur_x += getEmbeddedCharAdvance(ext_data); + + if( ((i+1) < max_chars) && (i+1 < max_index)) + { + cur_x += ext_data->mKerning * sScaleX; + } + } + else + { + cur_x += getXAdvance(wch); + llwchar next_char = wchars[i+1]; + + if (((i + 1) < max_chars) + && next_char + && (next_char < LAST_CHARACTER)) + { + // Kern this puppy. + cur_x += getXKerning(wch, next_char); + } + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + + return cur_x / sScaleX; +} + + + +// Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels +S32 LLFontFreeTypeGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, + BOOL end_on_word_boundary, const BOOL use_embedded, + F32* drawn_pixels) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + llassert(max_pixels >= 0.f); + llassert(max_chars >= 0); + + BOOL clip = FALSE; + F32 cur_x = 0; + F32 drawn_x = 0; + + S32 start_of_last_word = 0; + BOOL in_word = FALSE; + + F32 scaled_max_pixels = (F32)llceil(max_pixels * sScaleX); + + S32 i; + for (i=0; (i < max_chars); i++) + { + llwchar wch = wchars[i]; + + if(wch == 0) + { + // Null terminator. We're done. + break; + } + + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + if (in_word) + { + in_word = FALSE; + } + else + { + start_of_last_word = i; + } + cur_x += getEmbeddedCharAdvance(ext_data); + + if (scaled_max_pixels < cur_x) + { + clip = TRUE; + break; + } + + if (((i+1) < max_chars) && wchars[i+1]) + { + cur_x += ext_data->mKerning * sScaleX; + } + + if( scaled_max_pixels < cur_x ) + { + clip = TRUE; + break; + } + } + else + { + if (in_word) + { + if (iswspace(wch)) + { + in_word = FALSE; + } + } + else + { + start_of_last_word = i; + if (!iswspace(wch)) + { + in_word = TRUE; + } + } + + cur_x += getXAdvance(wch); + + if (scaled_max_pixels < cur_x) + { + clip = TRUE; + break; + } + + if (((i+1) < max_chars) && wchars[i+1]) + { + // Kern this puppy. + cur_x += getXKerning(wch, wchars[i+1]); + } + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + drawn_x = cur_x; + } + + if( clip && end_on_word_boundary && (start_of_last_word != 0) ) + { + i = start_of_last_word; + } + if (drawn_pixels) + { + *drawn_pixels = drawn_x; + } + return i; +} + + +S32 LLFontFreeTypeGL::firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + F32 total_width = 0.0; + S32 drawable_chars = 0; + + F32 scaled_max_pixels = max_pixels * sScaleX; + + S32 start = llmin(start_pos, text_len - 1); + for (S32 i = start; i >= 0; i--) + { + llwchar wch = wchars[i]; + + const embedded_data_t* ext_data = getEmbeddedCharData(wch); + if (ext_data) + { + F32 char_width = getEmbeddedCharAdvance(ext_data); + + if( scaled_max_pixels < (total_width + char_width) ) + { + break; + } + + total_width += char_width; + + drawable_chars++; + if( max_chars >= 0 && drawable_chars >= max_chars ) + { + break; + } + + if ( i > 0 ) + { + total_width += ext_data->mKerning * sScaleX; + } + + // Round after kerning. + total_width = (F32)llfloor(total_width + 0.5f); + } + else + { + F32 char_width = getXAdvance(wch); + if( scaled_max_pixels < (total_width + char_width) ) + { + break; + } + + total_width += char_width; + + drawable_chars++; + if( max_chars >= 0 && drawable_chars >= max_chars ) + { + break; + } + + if ( i > 0 ) + { + // Kerning + total_width += getXKerning(wchars[i-1], wch); + } + + // Round after kerning. + total_width = (F32)llfloor(total_width + 0.5f); + } + } + + return text_len - drawable_chars; +} + + +S32 LLFontFreeTypeGL::charFromPixelOffset(const llwchar* wchars, const S32 begin_offset, F32 target_x, F32 max_pixels, S32 max_chars, BOOL round, BOOL use_embedded) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + F32 cur_x = 0; + S32 pos = 0; + + target_x *= sScaleX; + + // max_chars is S32_MAX by default, so make sure we don't get overflow + const S32 max_index = begin_offset + llmin(S32_MAX - begin_offset, max_chars); + + F32 scaled_max_pixels = max_pixels * sScaleX; + + for (S32 i = begin_offset; (i < max_index); i++) + { + llwchar wch = wchars[i]; + if (!wch) + { + break; // done + } + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + F32 ext_advance = getEmbeddedCharAdvance(ext_data); + + if (round) + { + // Note: if the mouse is on the left half of the character, the pick is to the character's left + // If it's on the right half, the pick is to the right. + if (target_x < cur_x + ext_advance/2) + { + break; + } + } + else + { + if (target_x < cur_x + ext_advance) + { + break; + } + } + + if (scaled_max_pixels < cur_x + ext_advance) + { + break; + } + + pos++; + cur_x += ext_advance; + + if (((i + 1) < max_index) + && (wchars[(i + 1)])) + { + cur_x += ext_data->mKerning * sScaleX; + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + else + { + F32 char_width = getXAdvance(wch); + + if (round) + { + // Note: if the mouse is on the left half of the character, the pick is to the character's left + // If it's on the right half, the pick is to the right. + if (target_x < cur_x + char_width*0.5f) + { + break; + } + } + else if (target_x < cur_x + char_width) + { + break; + } + + if (scaled_max_pixels < cur_x + char_width) + { + break; + } + + pos++; + cur_x += char_width; + + if (((i + 1) < max_index) + && (wchars[(i + 1)])) + { + llwchar next_char = wchars[i + 1]; + // Kern this puppy. + cur_x += getXKerning(wch, next_char); + } + + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + } + + return pos; +} + + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontfreetypegl.h b/indra/llrender/llfontfreetypegl.h new file mode 100644 index 0000000..5dcc30d --- /dev/null +++ b/indra/llrender/llfontfreetypegl.h @@ -0,0 +1,136 @@ +/** + * @file llfontfreetypegl.h + * @author Doug Soo + * @brief Wrapper around FreeType + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFONTFREETYPEGL_H +#define LL_LLFONTFREETYPRGL_H + +#include "llfontmanager.h" +#include "llfontgl.h" +#include "llfont.h" +#include "llimagegl.h" +#include "llmath.h" + +class LLFontManagerFreeTypeGL : public LLFontManagerFreeTypeBase +{ +public: + + LLFontManagerFreeTypeGL(); + + /*virtual*/ void setDPIs(F32 horiz_dpi, F32 vert_dpi); + /*virtual*/ void setFallback(const std::string &face, F32 scale); + /*virtual*/ LLFontGL *createFont(const std::string &face, F32 size, bool console); + +protected: + + std::string mFallbackFace; + F32 mFallbackScale; + +}; + +class LLFontFreeTypeGL : public LLFontGL, public LLFont +{ +public: + + static LLFontFreeTypeGL *createFont(const std::string &face, F32 size, const std::string &fallback, F32 fallback_scale); + + // renderUTF8 does a conversion, so is slower! + /*virtual*/ S32 renderUTF8(const std::string &utf8text, S32 begin_offset, + F32 x, F32 y, const LLColor4 &color, + HAlign halign, VAlign valign, U8 style, + S32 max_chars, S32 max_pixels, F32 *right_x, + BOOL use_embedded, BOOL use_ellipses) const; + + /*virtual*/ S32 render(const LLWString &text, S32 begin_offset, + F32 x, F32 y, const LLColor4 &color, + HAlign halign, VAlign valign, U8 style, + S32 max_chars, S32 max_pixels, F32 *right_x, + BOOL use_embedded, BOOL use_ellipses) const; + + /*virtual*/ F32 getLineHeight() const { return (F32)llround(mLineHeight / sScaleY); } + /*virtual*/ F32 getAscenderHeight() const { return (F32)llround(mAscender / sScaleY); } + /*virtual*/ F32 getDescenderHeight() const { return (F32)llround(mDescender / sScaleY); } + + /*virtual*/ F32 getWidthF32(const std::string& text, S32 offset = 0, S32 max_chars = S32_MAX, BOOL use_embedded = FALSE) const; + /*virtual*/ F32 getWidthF32(const llwchar* wchars, S32 offset = 0, S32 max_chars = S32_MAX, BOOL use_embedded = FALSE) const; + + // The following are called often, frequently with large buffers, so do not use a string interface + + // Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels + /*virtual*/ S32 maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, + BOOL end_on_word_boundary, const BOOL use_embedded, + F32* drawn_pixels) const; + + // Returns the index of the first complete characters from text that can be drawn in max_pixels + // starting on the right side (at character start_pos). + /*virtual*/ S32 firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const; + + // Returns the index of the character closest to pixel position x (ignoring text to the right of max_pixels and max_chars) + /*virtual*/ S32 charFromPixelOffset(const llwchar* wchars, const S32 char_offset, + F32 x, F32 max_pixels, S32 max_chars, + BOOL round, BOOL use_embedded) const; + +// LLImageGL *getImageGL() const; + +protected: + /*virtual*/ void destroyGLTexture() { mImageGLp->destroyGLTexture(); } + /*virtual*/ BOOL addChar(const llwchar wch); + /*virtual*/ BOOL loadFace(const std::string& filename, + const F32 point_size, const F32 vert_dpi, const F32 horz_dpi, + const S32 components, BOOL is_fallback); + +private: + LLFontFreeTypeGL(); + virtual ~LLFontFreeTypeGL(); + + void init(); // Internal init, or reinitialization + void reset(); // Reset a font after GL cleanup. ONLY works on an already loaded font. + + static bool loadFaceFallback(LLFontList *fontlistp, const std::string& fontname, const F32 point_size); + static bool loadFace(LLFontFreeTypeGL *fontp, const std::string& fontname, const F32 point_size, LLFontList *fallback_fontp); + +public: + static F32 sVertDPI; + static F32 sHorizDPI; + static LLColor4 sShadowColor; + +protected: + LLPointer mRawImageGLp; + LLPointer mImageGLp; +}; + +#endif + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index 526f1a9..8e74428 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -1,6 +1,6 @@ /** * @file llfontgl.cpp - * @brief Wrapper around FreeType + * @brief Base class for text renderers * * $LicenseInfo:firstyear=2001&license=viewergpl$ * @@ -33,8 +33,9 @@ #include -#include "llfont.h" +#include "llfontmanager.h" #include "llfontgl.h" +#include "llfontglwrapper.h" #include "llgl.h" #include "llrender.h" #include "v4color.h" @@ -43,8 +44,6 @@ const S32 BOLD_OFFSET = 1; // static class members -F32 LLFontGL::sVertDPI = 96.f; -F32 LLFontGL::sHorizDPI = 96.f; F32 LLFontGL::sScaleX = 1.f; F32 LLFontGL::sScaleY = 1.f; BOOL LLFontGL::sDisplayFont = TRUE ; @@ -56,12 +55,6 @@ LLFontGL* LLFontGL::sSansSerif = NULL; LLFontGL* LLFontGL::sSansSerifBig = NULL; LLFontGL* LLFontGL::sSansSerifHuge = NULL; LLFontGL* LLFontGL::sSansSerifBold = NULL; -LLFontList* LLFontGL::sMonospaceFallback = NULL; -LLFontList* LLFontGL::sSSFallback = NULL; -LLFontList* LLFontGL::sSSSmallFallback = NULL; -LLFontList* LLFontGL::sSSBigFallback = NULL; -LLFontList* LLFontGL::sSSHugeFallback = NULL; -LLFontList* LLFontGL::sSSBoldFallback = NULL; LLColor4 LLFontGL::sShadowColor(0.f, 0.f, 0.f, 1.f); LLCoordFont LLFontGL::sCurOrigin; @@ -72,10 +65,7 @@ LLFontGL*& gExtCharFont = LLFontGL::sSansSerif; const F32 EXT_X_BEARING = 1.f; const F32 EXT_Y_BEARING = 0.f; const F32 EXT_KERNING = 1.f; -const F32 PIXEL_BORDER_THRESHOLD = 0.0001f; -const F32 PIXEL_CORRECTION_DISTANCE = 0.01f; -const F32 PAD_AMT = 0.5f; const F32 DROP_SHADOW_SOFT_STRENGTH = 0.3f; F32 llfont_round_x(F32 x) @@ -124,10 +114,9 @@ U8 LLFontGL::getStyleFromString(const std::string &style) } LLFontGL::LLFontGL() - : LLFont() { - init(); clearEmbeddedChars(); + mShowGlyphRect = LLFontManager::debugGetBoolOption(LLFontManager::OPTION_GLYPH_RECT); } LLFontGL::LLFontGL(const LLFontGL &source) @@ -137,32 +126,11 @@ LLFontGL::LLFontGL(const LLFontGL &source) LLFontGL::~LLFontGL() { - mImageGLp = NULL; - mRawImageGLp = NULL; clearEmbeddedChars(); } -void LLFontGL::init() -{ - if (mImageGLp.isNull()) - { - mImageGLp = new LLImageGL(FALSE); - //RN: use nearest mipmap filtering to obviate the need to do pixel-accurate positioning - gGL.getTexUnit(0)->bind(mImageGLp); - // we allow bilinear filtering to get sub-pixel positioning for drop shadows - //mImageGLp->setMipFilterNearest(TRUE, TRUE); - } - if (mRawImageGLp.isNull()) - { - mRawImageGLp = new LLImageRaw; // Note LLFontGL owns the image, not LLFont. - } - setRawImage( mRawImageGLp ); -} - void LLFontGL::reset() { - init(); - resetBitmap(); } // static @@ -219,242 +187,59 @@ std::string LLFontGL::getFontPathLocal() return local_path; } -//static -bool LLFontGL::loadFaceFallback(LLFontList *fontlistp, const std::string& fontname, const F32 point_size) +static void init_default_font(LLFontGL *&font, const std::string &file, F32 size, bool console = false) { - std::string local_path = getFontPathLocal(); - std::string sys_path = getFontPathSystem(); + // When updating a font, we need to be very careful so that we + // create a new font before deleting old font. There are a lot of + // logging statements, i.e., llinfos << ..., or llwarns << ... all + // over the SL source codes. SL viewer has an option to redirect + // those logging messages to a console winodw, that is on the SL + // window. As a result, several invocations to methods of + // LLFontGL may occur in a middle of a creation of a new LLFontGL + // object. Those methods must behave well. If we delete an old + // LLFontGL object before creating a new one, such invocations + // will fail. - // The fontname string may contain multiple font file names separated by semicolons. - // Break it apart and try loading each one, in order. - typedef boost::tokenizer > tokenizer; - boost::char_separator sep(";"); - tokenizer tokens(fontname, sep); - tokenizer::iterator token_iter; + LLFontGL *const new_real_font = gFontManagerp->createFont(file, size, console); - for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + if (!font) { - LLFont *fontp = new LLFont(); - std::string font_path = local_path + *token_iter; - if (!fontp->loadFace(font_path, point_size, sVertDPI, sHorizDPI, 2, TRUE)) - { - font_path = sys_path + *token_iter; - if (!fontp->loadFace(font_path, point_size, sVertDPI, sHorizDPI, 2, TRUE)) - { - LL_INFOS_ONCE("ViewerImages") << "Couldn't load font " << *token_iter << LL_ENDL; - delete fontp; - fontp = NULL; - } - } - - if(fontp) - { - fontlistp->addAtEnd(fontp); - } + font = new LLFontGLWrapper(); } + LLFontGLWrapper *const wrapper = static_cast(font); - // We want to return true if at least one fallback font loaded correctly. - return (fontlistp->size() > 0); + delete wrapper->mRealFont; + wrapper->mRealFont = new_real_font; } //static -bool LLFontGL::loadFace(LLFontGL *fontp, const std::string& fontname, const F32 point_size, LLFontList *fallback_fontp) -{ - std::string local_path = getFontPathLocal(); - std::string font_path = local_path + fontname; - if (!fontp->loadFace(font_path, point_size, sVertDPI, sHorizDPI, 2, FALSE)) - { - std::string sys_path = getFontPathSystem(); - font_path = sys_path + fontname; - if (!fontp->loadFace(font_path, point_size, sVertDPI, sHorizDPI, 2, FALSE)) - { - LL_WARNS("ViewerImages") << "Couldn't load font " << fontname << LL_ENDL; - return false; - } - } - - fontp->setFallbackFont(fallback_fontp); - return true; -} - - -// static BOOL LLFontGL::initDefaultFonts(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& monospace_file, F32 monospace_size, const std::string& sansserif_file, const std::string& sanserif_fallback_file, F32 ss_fallback_scale, F32 small_size, F32 medium_size, F32 big_size, F32 huge_size, const std::string& sansserif_bold_file, F32 bold_size, - const std::string& app_dir) + const std::string& app_dir, + const std::string& language) { BOOL failed = FALSE; - sVertDPI = (F32)llfloor(screen_dpi * y_scale); - sHorizDPI = (F32)llfloor(screen_dpi * x_scale); sScaleX = x_scale; sScaleY = y_scale; sAppDir = app_dir; - // - // Monospace font - // - - if (!sMonospace) - { - sMonospace = new LLFontGL(); - } - else - { - sMonospace->reset(); - } - - if (sMonospaceFallback) - { - delete sMonospaceFallback; - } - sMonospaceFallback = new LLFontList(); - if (!loadFaceFallback( - sMonospaceFallback, - sanserif_fallback_file, - monospace_size * ss_fallback_scale)) - { - delete sMonospaceFallback; - sMonospaceFallback = NULL; - } - - failed |= !loadFace(sMonospace, monospace_file, monospace_size, sMonospaceFallback); - - // - // Sans-serif fonts - // - if(!sSansSerifHuge) - { - sSansSerifHuge = new LLFontGL(); - } - else - { - sSansSerifHuge->reset(); - } - - if (sSSHugeFallback) - { - delete sSSHugeFallback; - } - sSSHugeFallback = new LLFontList(); - if (!loadFaceFallback( - sSSHugeFallback, - sanserif_fallback_file, - huge_size*ss_fallback_scale)) - { - delete sSSHugeFallback; - sSSHugeFallback = NULL; - } - - failed |= !loadFace(sSansSerifHuge, sansserif_file, huge_size, sSSHugeFallback); + gFontManagerp->reset(); + gFontManagerp->setDPIs(screen_dpi * y_scale, screen_dpi * x_scale); + gFontManagerp->setLanguage(language); + gFontManagerp->setFallback(sanserif_fallback_file, ss_fallback_scale); + init_default_font(sMonospace, monospace_file, monospace_size, true); + init_default_font(sSansSerifSmall, sansserif_file, small_size); + init_default_font(sSansSerif, sansserif_file, medium_size); + init_default_font(sSansSerifBig, sansserif_file, big_size); + init_default_font(sSansSerifHuge, sansserif_file, huge_size); + init_default_font(sSansSerifBold, sansserif_bold_file, bold_size); - if(!sSansSerifBig) - { - sSansSerifBig = new LLFontGL(); - } - else - { - sSansSerifBig->reset(); - } - - if (sSSBigFallback) - { - delete sSSBigFallback; - } - sSSBigFallback = new LLFontList(); - if (!loadFaceFallback( - sSSBigFallback, - sanserif_fallback_file, - big_size*ss_fallback_scale)) - { - delete sSSBigFallback; - sSSBigFallback = NULL; - } - - failed |= !loadFace(sSansSerifBig, sansserif_file, big_size, sSSBigFallback); - - - if(!sSansSerif) - { - sSansSerif = new LLFontGL(); - } - else - { - sSansSerif->reset(); - } - - if (sSSFallback) - { - delete sSSFallback; - } - sSSFallback = new LLFontList(); - if (!loadFaceFallback( - sSSFallback, - sanserif_fallback_file, - medium_size*ss_fallback_scale)) - { - delete sSSFallback; - sSSFallback = NULL; - } - failed |= !loadFace(sSansSerif, sansserif_file, medium_size, sSSFallback); - - - if(!sSansSerifSmall) - { - sSansSerifSmall = new LLFontGL(); - } - else - { - sSansSerifSmall->reset(); - } - - if(sSSSmallFallback) - { - delete sSSSmallFallback; - } - sSSSmallFallback = new LLFontList(); - if (!loadFaceFallback( - sSSSmallFallback, - sanserif_fallback_file, - small_size*ss_fallback_scale)) - { - delete sSSSmallFallback; - sSSSmallFallback = NULL; - } - failed |= !loadFace(sSansSerifSmall, sansserif_file, small_size, sSSSmallFallback); - - - // - // Sans-serif bold - // - if(!sSansSerifBold) - { - sSansSerifBold = new LLFontGL(); - } - else - { - sSansSerifBold->reset(); - } - - if (sSSBoldFallback) - { - delete sSSBoldFallback; - } - sSSBoldFallback = new LLFontList(); - if (!loadFaceFallback( - sSSBoldFallback, - sanserif_fallback_file, - medium_size*ss_fallback_scale)) - { - delete sSSBoldFallback; - sSSBoldFallback = NULL; - } - failed |= !loadFace(sSansSerifBold, sansserif_bold_file, medium_size, sSSBoldFallback); - + // We are not error checking for the moment. return !failed; } @@ -480,24 +265,6 @@ void LLFontGL::destroyDefaultFonts() delete sSansSerifBold; sSansSerifBold = NULL; - - delete sMonospaceFallback; - sMonospaceFallback = NULL; - - delete sSSHugeFallback; - sSSHugeFallback = NULL; - - delete sSSBigFallback; - sSSBigFallback = NULL; - - delete sSSFallback; - sSSFallback = NULL; - - delete sSSSmallFallback; - sSSSmallFallback = NULL; - - delete sSSBoldFallback; - sSSBoldFallback = NULL; } //static @@ -508,12 +275,12 @@ void LLFontGL::destroyGL() // Already all destroyed. return; } - sMonospace->mImageGLp->destroyGLTexture(); - sSansSerifHuge->mImageGLp->destroyGLTexture(); - sSansSerifSmall->mImageGLp->destroyGLTexture(); - sSansSerif->mImageGLp->destroyGLTexture(); - sSansSerifBig->mImageGLp->destroyGLTexture(); - sSansSerifBold->mImageGLp->destroyGLTexture(); + sMonospace->destroyGLTexture(); + sSansSerifHuge->destroyGLTexture(); + sSansSerifSmall->destroyGLTexture(); + sSansSerif->destroyGLTexture(); + sSansSerifBig->destroyGLTexture(); + sSansSerifBold->destroyGLTexture(); } @@ -524,760 +291,6 @@ LLFontGL &LLFontGL::operator=(const LLFontGL &source) return *this; } -BOOL LLFontGL::loadFace(const std::string& filename, - const F32 point_size, const F32 vert_dpi, const F32 horz_dpi, - const S32 components, BOOL is_fallback) -{ - if (!LLFont::loadFace(filename, point_size, vert_dpi, horz_dpi, components, is_fallback)) - { - return FALSE; - } - mImageGLp->createGLTexture(0, mRawImageGLp); - gGL.getTexUnit(0)->bind(mImageGLp); - mImageGLp->setMipFilterNearest(TRUE, TRUE); - return TRUE; -} - -BOOL LLFontGL::addChar(const llwchar wch) -{ - if (!LLFont::addChar(wch)) - { - return FALSE; - } - - stop_glerror(); - mImageGLp->setSubImage(mRawImageGLp, 0, 0, mImageGLp->getWidth(), mImageGLp->getHeight()); - gGL.getTexUnit(0)->bind(mImageGLp); - mImageGLp->setMipFilterNearest(TRUE, TRUE); - stop_glerror(); - return TRUE; -} - - -S32 LLFontGL::renderUTF8(const std::string &text, const S32 offset, - const F32 x, const F32 y, - const LLColor4 &color, - const HAlign halign, const VAlign valign, - U8 style, - const S32 max_chars, const S32 max_pixels, - F32* right_x, - BOOL use_ellipses) const -{ - LLWString wstr = utf8str_to_wstring(text); - return render(wstr, offset, x, y, color, halign, valign, style, max_chars, max_pixels, right_x, use_ellipses); -} - -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() ; - } - - gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); - - 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 - - gGL.getTexUnit(0)->bind(mImageGLp); - - // 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; - } - - gGL.getTexUnit(0)->bind(ext_image); - 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 - gGL.getTexUnit(0)->bind(mImageGLp); - } - else - { - if (!hasGlyph(wch)) - { - (const_cast(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(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) - { - 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; -} - - -LLImageGL *LLFontGL::getImageGL() const -{ - return mImageGLp; -} - -S32 LLFontGL::getWidth(const std::string& utf8text) const -{ - LLWString wtext = utf8str_to_wstring(utf8text); - return getWidth(wtext.c_str(), 0, S32_MAX); -} - -S32 LLFontGL::getWidth(const llwchar* wchars) const -{ - return getWidth(wchars, 0, S32_MAX); -} - -S32 LLFontGL::getWidth(const std::string& utf8text, const S32 begin_offset, const S32 max_chars) const -{ - LLWString wtext = utf8str_to_wstring(utf8text); - return getWidth(wtext.c_str(), begin_offset, max_chars); -} - -S32 LLFontGL::getWidth(const llwchar* wchars, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const -{ - F32 width = getWidthF32(wchars, begin_offset, max_chars, use_embedded); - return llround(width); -} - -F32 LLFontGL::getWidthF32(const std::string& utf8text) const -{ - LLWString wtext = utf8str_to_wstring(utf8text); - return getWidthF32(wtext.c_str(), 0, S32_MAX); -} - -F32 LLFontGL::getWidthF32(const llwchar* wchars) const -{ - return getWidthF32(wchars, 0, S32_MAX); -} - -F32 LLFontGL::getWidthF32(const std::string& utf8text, const S32 begin_offset, const S32 max_chars ) const -{ - LLWString wtext = utf8str_to_wstring(utf8text); - return getWidthF32(wtext.c_str(), begin_offset, max_chars); -} - -F32 LLFontGL::getWidthF32(const llwchar* wchars, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const -{ - const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; - - F32 cur_x = 0; - const S32 max_index = begin_offset + max_chars; - for (S32 i = begin_offset; i < max_index; i++) - { - const llwchar wch = wchars[i]; - if (wch == 0) - { - break; // done - } - const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; - if (ext_data) - { - // Handle crappy embedded hack - cur_x += getEmbeddedCharAdvance(ext_data); - - if( ((i+1) < max_chars) && (i+1 < max_index)) - { - cur_x += EXT_KERNING * sScaleX; - } - } - else - { - cur_x += getXAdvance(wch); - llwchar next_char = wchars[i+1]; - - if (((i + 1) < max_chars) - && next_char - && (next_char < LAST_CHARACTER)) - { - // Kern this puppy. - cur_x += getXKerning(wch, next_char); - } - } - // Round after kerning. - cur_x = (F32)llfloor(cur_x + 0.5f); - } - - return cur_x / sScaleX; -} - - - -// Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels -S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, - BOOL end_on_word_boundary, const BOOL use_embedded, - F32* drawn_pixels) const -{ - if (!wchars || !wchars[0] || max_chars == 0) - { - return 0; - } - - llassert(max_pixels >= 0.f); - llassert(max_chars >= 0); - - BOOL clip = FALSE; - F32 cur_x = 0; - F32 drawn_x = 0; - - S32 start_of_last_word = 0; - BOOL in_word = FALSE; - - F32 scaled_max_pixels = (F32)llceil(max_pixels * sScaleX); - - S32 i; - for (i=0; (i < max_chars); i++) - { - llwchar wch = wchars[i]; - - if(wch == 0) - { - // Null terminator. We're done. - break; - } - - const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; - if (ext_data) - { - if (in_word) - { - in_word = FALSE; - } - else - { - start_of_last_word = i; - } - cur_x += getEmbeddedCharAdvance(ext_data); - - if (scaled_max_pixels < cur_x) - { - clip = TRUE; - break; - } - - if (((i+1) < max_chars) && wchars[i+1]) - { - cur_x += EXT_KERNING * sScaleX; - } - - if( scaled_max_pixels < cur_x ) - { - clip = TRUE; - break; - } - } - else - { - if (in_word) - { - if (iswspace(wch)) - { - in_word = FALSE; - } - } - else - { - start_of_last_word = i; - if (!iswspace(wch)) - { - in_word = TRUE; - } - } - - cur_x += getXAdvance(wch); - - if (scaled_max_pixels < cur_x) - { - clip = TRUE; - break; - } - - if (((i+1) < max_chars) && wchars[i+1]) - { - // Kern this puppy. - cur_x += getXKerning(wch, wchars[i+1]); - } - } - // Round after kerning. - cur_x = (F32)llfloor(cur_x + 0.5f); - drawn_x = cur_x; - } - - if( clip && end_on_word_boundary && (start_of_last_word != 0) ) - { - i = start_of_last_word; - } - if (drawn_pixels) - { - *drawn_pixels = drawn_x; - } - return i; -} - - -S32 LLFontGL::firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const -{ - if (!wchars || !wchars[0] || max_chars == 0) - { - return 0; - } - - F32 total_width = 0.0; - S32 drawable_chars = 0; - - F32 scaled_max_pixels = max_pixels * sScaleX; - - S32 start = llmin(start_pos, text_len - 1); - for (S32 i = start; i >= 0; i--) - { - llwchar wch = wchars[i]; - - const embedded_data_t* ext_data = getEmbeddedCharData(wch); - if (ext_data) - { - F32 char_width = getEmbeddedCharAdvance(ext_data); - - if( scaled_max_pixels < (total_width + char_width) ) - { - break; - } - - total_width += char_width; - - drawable_chars++; - if( max_chars >= 0 && drawable_chars >= max_chars ) - { - break; - } - - if ( i > 0 ) - { - total_width += EXT_KERNING * sScaleX; - } - - // Round after kerning. - total_width = (F32)llfloor(total_width + 0.5f); - } - else - { - F32 char_width = getXAdvance(wch); - if( scaled_max_pixels < (total_width + char_width) ) - { - break; - } - - total_width += char_width; - - drawable_chars++; - if( max_chars >= 0 && drawable_chars >= max_chars ) - { - break; - } - - if ( i > 0 ) - { - // Kerning - total_width += getXKerning(wchars[i-1], wch); - } - - // Round after kerning. - total_width = (F32)llfloor(total_width + 0.5f); - } - } - - return text_len - drawable_chars; -} - - -S32 LLFontGL::charFromPixelOffset(const llwchar* wchars, const S32 begin_offset, F32 target_x, F32 max_pixels, S32 max_chars, BOOL round, BOOL use_embedded) const -{ - if (!wchars || !wchars[0] || max_chars == 0) - { - return 0; - } - - F32 cur_x = 0; - S32 pos = 0; - - target_x *= sScaleX; - - // max_chars is S32_MAX by default, so make sure we don't get overflow - const S32 max_index = begin_offset + llmin(S32_MAX - begin_offset, max_chars); - - F32 scaled_max_pixels = max_pixels * sScaleX; - - for (S32 i = begin_offset; (i < max_index); i++) - { - llwchar wch = wchars[i]; - if (!wch) - { - break; // done - } - const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; - if (ext_data) - { - F32 ext_advance = getEmbeddedCharAdvance(ext_data); - - if (round) - { - // Note: if the mouse is on the left half of the character, the pick is to the character's left - // If it's on the right half, the pick is to the right. - if (target_x < cur_x + ext_advance/2) - { - break; - } - } - else - { - if (target_x < cur_x + ext_advance) - { - break; - } - } - - if (scaled_max_pixels < cur_x + ext_advance) - { - break; - } - - pos++; - cur_x += ext_advance; - - if (((i + 1) < max_index) - && (wchars[(i + 1)])) - { - cur_x += EXT_KERNING * sScaleX; - } - // Round after kerning. - cur_x = (F32)llfloor(cur_x + 0.5f); - } - else - { - F32 char_width = getXAdvance(wch); - - if (round) - { - // Note: if the mouse is on the left half of the character, the pick is to the character's left - // If it's on the right half, the pick is to the right. - if (target_x < cur_x + char_width*0.5f) - { - break; - } - } - else if (target_x < cur_x + char_width) - { - break; - } - - if (scaled_max_pixels < cur_x + char_width) - { - break; - } - - pos++; - cur_x += char_width; - - if (((i + 1) < max_index) - && (wchars[(i + 1)])) - { - llwchar next_char = wchars[i + 1]; - // Kern this puppy. - cur_x += getXKerning(wch, next_char); - } - - // Round after kerning. - cur_x = (F32)llfloor(cur_x + 0.5f); - } - } - - return pos; -} - const LLFontGL::embedded_data_t* LLFontGL::getEmbeddedCharData(const llwchar wch) const { @@ -1320,7 +333,13 @@ void LLFontGL::addEmbeddedChar( llwchar wc, LLImageGL* image, const std::string& void LLFontGL::addEmbeddedChar( llwchar wc, LLImageGL* image, const LLWString& wlabel ) { - embedded_data_t* ext_data = new embedded_data_t(image, wlabel); + embedded_data_t* ext_data = new embedded_data_t(); + ext_data->mImage = image; + ext_data->mLabel = wlabel; + ext_data->mFont = gExtCharFont; + ext_data->mXBearing = EXT_X_BEARING; + ext_data->mYBearing = EXT_Y_BEARING; + ext_data->mKerning = EXT_KERNING; mEmbeddedChars[wc] = ext_data; } @@ -1356,8 +375,38 @@ void LLFontGL::renderQuad(const LLRectf& screen_rect, const LLRectf& uv_rect, F3 void LLFontGL::drawGlyph(const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4& color, U8 style, F32 drop_shadow_strength) const { - F32 slant_offset; - slant_offset = ((style & ITALIC) ? ( -mAscender * 0.2f) : 0.f); + // We need to draw glyph rectangle before the glyph image so that + // the the rectangle is shown below the glyph image. + if (mShowGlyphRect) + { + // The following inset value assumes PAD_AMT is 0.5, i.e., + // INSET = PAD_AMT + 0.5f + const F32 INSET = 1.f; + + // A texture for the glyph image has been bound by the caller. + // We need to preserve it. So we can't use LLTexUnit::unbind. + // What we are doing here is just _stop_ the llrender's GL + // command buffering, issue ourown commands directly, and + // restore the llrender states. I hope LLRender and/or + // LLTexUnit provided a mothod that returns the current + // eTextureType of a given texture unit. With it, we can + // write a clean push/pop style code here. + + gGL.flush(); + + glDisable(GL_TEXTURE_2D); + glBegin(GL_LINE_LOOP); + glColor4fv(LLColor4::red.mV); + glVertex2f(screen_rect.mLeft + INSET, screen_rect.mTop - INSET); + glVertex2f(screen_rect.mRight - INSET, screen_rect.mTop - INSET); + glVertex2f(screen_rect.mRight - INSET, screen_rect.mBottom + INSET); + glVertex2f(screen_rect.mLeft + INSET, screen_rect.mBottom + INSET); + glEnd(); + + gGL.refreshState(); + } + + const F32 slant_offset = ((style & ITALIC) ? ( -getAscenderHeight() * 0.2f) : 0.f); gGL.begin(LLRender::QUADS); { @@ -1379,29 +428,13 @@ void LLFontGL::drawGlyph(const LLRectf& screen_rect, const LLRectf& uv_rect, con LLColor4 shadow_color = LLFontGL::sShadowColor; shadow_color.mV[VALPHA] = color.mV[VALPHA] * drop_shadow_strength * DROP_SHADOW_SOFT_STRENGTH; gGL.color4fv(shadow_color.mV); + static const F32 coordinates[][2] + = { {-1.f, -1.f}, {1.f, -1.f}, {1.f, 1.f}, {-1.f, 1.f}, {0.f, -2.f} }; for (S32 pass = 0; pass < 5; pass++) { LLRectf screen_rect_offset = screen_rect; - switch(pass) - { - case 0: - screen_rect_offset.translate(-1.f, -1.f); - break; - case 1: - screen_rect_offset.translate(1.f, -1.f); - break; - case 2: - screen_rect_offset.translate(1.f, 1.f); - break; - case 3: - screen_rect_offset.translate(-1.f, 1.f); - break; - case 4: - screen_rect_offset.translate(0, -2.f); - break; - } - + screen_rect_offset.translate(coordinates[pass][0], coordinates[pass][1]); renderQuad(screen_rect_offset, uv_rect, slant_offset); } gGL.color4fv(color.mV); @@ -1554,3 +587,9 @@ LLFontGL::VAlign LLFontGL::vAlignFromName(const std::string& name) //else leave baseline return gl_vfont_align; } + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h index 97bdd1a..697b8c7 100644 --- a/indra/llrender/llfontgl.h +++ b/indra/llrender/llfontgl.h @@ -1,7 +1,7 @@ /** * @file llfontgl.h * @author Doug Soo - * @brief Wrapper around FreeType + * @brief Base class for text renderers * * $LicenseInfo:firstyear=2001&license=viewergpl$ * @@ -33,7 +33,6 @@ #ifndef LL_LLFONTGL_H #define LL_LLFONTGL_H -#include "llfont.h" #include "llimagegl.h" #include "v2math.h" #include "llcoord.h" @@ -41,7 +40,7 @@ class LLColor4; -class LLFontGL : public LLFont +class LLFontGL { public: enum HAlign @@ -75,14 +74,9 @@ public: // Takes a string with potentially several flags, i.e. "NORMAL|BOLD|ITALIC" static U8 getStyleFromString(const std::string &style); - LLFontGL(); - LLFontGL(const LLFontGL &source); - ~LLFontGL(); - - void init(); // Internal init, or reinitialization - void reset(); // Reset a font after GL cleanup. ONLY works on an already loaded font. - - LLFontGL &operator=(const LLFontGL &source); + virtual ~LLFontGL(); + virtual void reset(); // Reset a font after GL cleanup. ONLY works on an already loaded font. + virtual void destroyGLTexture() = 0; static BOOL initDefaultFonts(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& monospace_file, F32 monospace_size, @@ -90,18 +84,12 @@ public: const std::string& sansserif_fallback_file, F32 ss_fallback_scale, F32 small_size, F32 medium_size, F32 large_size, F32 huge_size, const std::string& sansserif_bold_file, F32 bold_size, - const std::string& app_dir = LLStringUtil::null); + const std::string& app_dir = LLStringUtil::null, + const std::string& language = "C"); static void destroyDefaultFonts(); static void destroyGL(); - static bool loadFaceFallback(LLFontList *fontp, const std::string& fontname, const F32 point_size); - static bool loadFace(LLFontGL *fontp, const std::string& fontname, const F32 point_size, LLFontList *fallback_fontp); - /* virtual*/ BOOL loadFace(const std::string& filename, - const F32 point_size, const F32 vert_dpi, const F32 horz_dpi, - const S32 components, BOOL is_fallback); - - S32 renderUTF8(const std::string &text, const S32 begin_offset, S32 x, S32 y, const LLColor4 &color) const @@ -121,8 +109,23 @@ public: S32_MAX, S32_MAX, NULL, FALSE); } - // renderUTF8 does a conversion, so is slower! S32 renderUTF8(const std::string &text, + const S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign = LEFT, VAlign valign = BASELINE, U8 style = NORMAL, + S32 max_chars = S32_MAX, S32 max_pixels = S32_MAX, F32 *right_x = NULL, + BOOL use_ellipses = FALSE) const + { + return renderUTF8(text, begin_offset, x, y, color, + halign, valign, style, + max_chars, max_pixels, right_x, FALSE, use_ellipses); + } + + // LLFontFreeTypeGL::renderUTF8 does a conversion, so it is + // slower. However, LLFontPangoGL::renderUTF8 doesn't and is + // faster than LLFontPangoGL::render that *does* conversion... + virtual S32 renderUTF8(const std::string &utf8text, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, @@ -132,19 +135,10 @@ public: S32 max_chars, S32 max_pixels, F32* right_x, - BOOL use_ellipses) const; + BOOL use_embedded, + BOOL use_ellipses) const = 0; - S32 render(const LLWString &text, const S32 begin_offset, - F32 x, F32 y, - const LLColor4 &color) const - { - return render(text, begin_offset, x, y, color, - LEFT, BASELINE, NORMAL, - S32_MAX, S32_MAX, NULL, FALSE, FALSE); - } - - - S32 render(const LLWString &text, + virtual S32 render(const LLWString &text, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, @@ -155,45 +149,38 @@ public: S32 max_pixels = S32_MAX, F32* right_x=NULL, BOOL use_embedded = FALSE, - BOOL use_ellipses = FALSE) const; + BOOL use_ellipses = FALSE) const = 0; // font metrics - override for LLFont that returns units of virtual pixels - /*virtual*/ F32 getLineHeight() const { return (F32)llround(mLineHeight / sScaleY); } - /*virtual*/ F32 getAscenderHeight() const { return (F32)llround(mAscender / sScaleY); } - /*virtual*/ F32 getDescenderHeight() const { return (F32)llround(mDescender / sScaleY); } + virtual F32 getLineHeight() const = 0; + virtual F32 getAscenderHeight() const = 0; + virtual F32 getDescenderHeight() const = 0; - virtual S32 getWidth(const std::string& utf8text) const; - virtual S32 getWidth(const llwchar* wchars) const; - virtual S32 getWidth(const std::string& utf8text, const S32 offset, const S32 max_chars ) const; - virtual S32 getWidth(const llwchar* wchars, const S32 offset, const S32 max_chars, BOOL use_embedded = FALSE) const; + /* inline */ S32 getWidth(const std::string& utf8text, const S32 offset = 0, const S32 max_chars = S32_MAX, BOOL use_embedded = FALSE) const; + /* inline */ S32 getWidth(const llwchar* wchars, const S32 offset = 0, const S32 max_chars = S32_MAX, BOOL use_embedded = FALSE) const; - virtual F32 getWidthF32(const std::string& utf8text) const; - virtual F32 getWidthF32(const llwchar* wchars) const; - virtual F32 getWidthF32(const std::string& text, const S32 offset, const S32 max_chars ) const; - virtual F32 getWidthF32(const llwchar* wchars, const S32 offset, const S32 max_chars, BOOL use_embedded = FALSE ) const; + virtual F32 getWidthF32(const std::string& utf8text, S32 offset = 0, S32 max_chars = S32_MAX, BOOL use_embedded = FALSE) const = 0; + virtual F32 getWidthF32(const llwchar* wchars, S32 offset = 0, S32 max_chars = S32_MAX, BOOL use_embedded = FALSE) const = 0; // The following are called often, frequently with large buffers, so do not use a string interface // Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels virtual S32 maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars = S32_MAX, BOOL end_on_word_boundary = FALSE, const BOOL use_embedded = FALSE, - F32* drawn_pixels = NULL) const; + F32* drawn_pixels = NULL) const = 0; // Returns the index of the first complete characters from text that can be drawn in max_pixels // starting on the right side (at character start_pos). - virtual S32 firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos=S32_MAX, S32 max_chars = S32_MAX) const; + virtual S32 firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos=S32_MAX, S32 max_chars = S32_MAX) const = 0; // Returns the index of the character closest to pixel position x (ignoring text to the right of max_pixels and max_chars) virtual S32 charFromPixelOffset(const llwchar* wchars, const S32 char_offset, F32 x, F32 max_pixels=F32_MAX, S32 max_chars = S32_MAX, - BOOL round = TRUE, BOOL use_embedded = FALSE) const; - - - LLImageGL *getImageGL() const; + BOOL round = TRUE, BOOL use_embedded = FALSE) const = 0; - void addEmbeddedChar( llwchar wc, LLImageGL* image, const std::string& label); - void addEmbeddedChar( llwchar wc, LLImageGL* image, const LLWString& label); - void removeEmbeddedChar( llwchar wc ); + virtual void addEmbeddedChar( llwchar wc, LLImageGL* image, const std::string& label); + virtual void addEmbeddedChar( llwchar wc, LLImageGL* image, const LLWString& label); + virtual void removeEmbeddedChar( llwchar wc ); static std::string nameFromFont(const LLFontGL* fontp); static LLFontGL* fontFromName(const std::string& name); @@ -209,58 +196,79 @@ public: protected: struct embedded_data_t { - embedded_data_t(LLImageGL* image, const LLWString& label) : mImage(image), mLabel(label) {} LLPointer mImage; LLWString mLabel; + LLFontGL *mFont; + F32 mXBearing; + F32 mYBearing; + F32 mKerning; }; const embedded_data_t* getEmbeddedCharData(const llwchar wch) const; F32 getEmbeddedCharAdvance(const embedded_data_t* ext_data) const; void clearEmbeddedChars(); + bool hasEmbeddedChars() const { return !mEmbeddedChars.empty(); } void renderQuad(const LLRectf& screen_rect, const LLRectf& uv_rect, F32 slant_amt) const; void drawGlyph(const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4& color, U8 style, F32 drop_shadow_fade) const; +protected: + LLFontGL(); + +private: + LLFontGL(const LLFontGL &source); + LLFontGL &operator=(const LLFontGL &source); + public: - static F32 sVertDPI; - static F32 sHorizDPI; static F32 sScaleX; static F32 sScaleY; - static BOOL sDisplayFont ; - static std::string sAppDir; // For loading fonts static LLFontGL* sMonospace; // medium - static LLFontList* sMonospaceFallback; static LLFontGL* sSansSerifSmall; // small - static LLFontList* sSSSmallFallback; static LLFontGL* sSansSerif; // medium - static LLFontList* sSSFallback; static LLFontGL* sSansSerifBig; // large - static LLFontList* sSSBigFallback; static LLFontGL* sSansSerifHuge; // very large - static LLFontList* sSSHugeFallback; static LLFontGL* sSansSerifBold; // medium, bolded - static LLFontList* sSSBoldFallback; static LLColor4 sShadowColor; - friend class LLTextBillboard; - friend class LLHUDText; - protected: - /*virtual*/ BOOL addChar(const llwchar wch); static std::string getFontPathLocal(); static std::string getFontPathSystem(); protected: - LLPointer mRawImageGLp; - LLPointer mImageGLp; + static BOOL sDisplayFont ; + static std::string sAppDir; // For loading fonts + +protected: typedef std::map embedded_map_t; embedded_map_t mEmbeddedChars; + BOOL mShowGlyphRect; public: static LLCoordFont sCurOrigin; static std::vector sOriginStack; }; + +inline S32 LLFontGL::getWidth(const std::string& utf8text, const S32 offset, S32 max_chars, BOOL use_embedded) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + F32 width = getWidthF32(wtext.c_str(), offset, max_chars, use_embedded); + return llceil(width); +} + +inline S32 LLFontGL::getWidth(const llwchar* wchars, S32 offset, S32 max_chars, BOOL use_embedded) const +{ + F32 width = getWidthF32(wchars, offset, max_chars, use_embedded); + return llceil(width); +} + + #endif + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontglwrapper.cpp b/indra/llrender/llfontglwrapper.cpp new file mode 100644 index 0000000..bd9873a --- /dev/null +++ b/indra/llrender/llfontglwrapper.cpp @@ -0,0 +1,152 @@ +/** + * @file llfontglwrapper.cpp + * @brief Wraps an LLFontGL instance that will be deleted and recreated, so that LLUI components can point to a persistent instance. + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llfontglwrapper.h" + +LLFontGLWrapper::LLFontGLWrapper() +{ + mRealFont = NULL; +} + +LLFontGLWrapper::~LLFontGLWrapper() +{ + delete mRealFont; +} + +void LLFontGLWrapper::reset() +{ + mRealFont->reset(); +} + +S32 LLFontGLWrapper::renderUTF8(const std::string &utf8text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign, VAlign valign, U8 style, + S32 max_chars, S32 max_pixels, F32 *right_x, + BOOL use_embedded, BOOL use_ellipses) const +{ + return mRealFont->renderUTF8(utf8text, begin_offset, x, y, color, + halign, valign, style, max_chars, max_pixels, right_x, + use_embedded, use_ellipses); +} + +S32 LLFontGLWrapper::render(const LLWString &text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign, VAlign valign, U8 style, + S32 max_chars, S32 max_pixels, F32 *right_x, + BOOL use_embedded, BOOL use_ellipses) const +{ + return mRealFont->render(text, begin_offset, x, y, color, + halign, valign, style, max_chars, max_pixels, right_x, + use_embedded, use_ellipses); +} + +F32 LLFontGLWrapper::getLineHeight() const +{ + return mRealFont->getLineHeight(); +} + +F32 LLFontGLWrapper::getAscenderHeight() const +{ + return mRealFont->getAscenderHeight(); +} + +F32 LLFontGLWrapper::getDescenderHeight() const +{ + return mRealFont->getDescenderHeight(); +} + +F32 LLFontGLWrapper::getWidthF32(const std::string &utf8text, + S32 offset, S32 max_chars, BOOL use_embedded) const +{ + return mRealFont->getWidthF32(utf8text, offset, max_chars, use_embedded); +} + +F32 LLFontGLWrapper::getWidthF32(const llwchar *wchars, + S32 offset, S32 max_chars, BOOL use_embedded) const +{ + return mRealFont->getWidthF32(wchars, offset, max_chars, use_embedded); +} + +S32 LLFontGLWrapper::maxDrawableChars(const llwchar *wchars, + F32 max_pixels, S32 max_chars, + BOOL end_on_word_boundary, const BOOL use_embedded, + F32 *drawn_pixels) const +{ + return mRealFont->maxDrawableChars(wchars, max_pixels, max_chars, + end_on_word_boundary, use_embedded, drawn_pixels); +} + +S32 LLFontGLWrapper::firstDrawableChar(const llwchar *wchars, + F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const +{ + return mRealFont->firstDrawableChar(wchars, + max_pixels, text_len, start_pos, max_chars); +} + +S32 LLFontGLWrapper::charFromPixelOffset(const llwchar *wchars, + const S32 char_offset, + F32 x, F32 max_pixels, S32 max_chars, + BOOL round, BOOL use_embedded) const +{ + return mRealFont->charFromPixelOffset(wchars, char_offset, + x, max_pixels, max_chars, round, use_embedded); +} + +void LLFontGLWrapper::addEmbeddedChar(llwchar wc, LLImageGL *image, const std::string &label) +{ + mRealFont->addEmbeddedChar(wc, image, label); +} + +void LLFontGLWrapper::addEmbeddedChar(llwchar wc, LLImageGL *image, const LLWString &label) +{ + mRealFont->addEmbeddedChar(wc, image, label); +} + +void LLFontGLWrapper::removeEmbeddedChar(llwchar wc) +{ + mRealFont->removeEmbeddedChar(wc); +} + +void LLFontGLWrapper::destroyGLTexture() +{ + mRealFont->destroyGLTexture(); +} + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontglwrapper.h b/indra/llrender/llfontglwrapper.h new file mode 100644 index 0000000..515c260 --- /dev/null +++ b/indra/llrender/llfontglwrapper.h @@ -0,0 +1,99 @@ +/** + * @file llfontglwrapper.h + * @brief Wraps an LLFontGL instance that will be deleted and recreated, so that LLUI components can point to a persistent instance. + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFONTGLWRAPPER_H +#define LL_LLFONTGLWRAPPER_H + +#include "llfontgl.h" + +class LLFontGLWrapper : public LLFontGL +{ +public: + LLFontGLWrapper(); + /*virutal*/ ~LLFontGLWrapper(); + /*virutal*/ void reset(); + + /*virutal*/ S32 renderUTF8(const std::string &utf8text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign, VAlign valign, U8 style, + S32 max_chars, S32 max_pixels, F32* right_x, + BOOL use_embedded, BOOL use_ellipses) const; + + /*virutal*/ S32 render(const LLWString &text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign, VAlign valign, U8 style, + S32 max_chars, S32 max_pixels, F32* right_x, + BOOL use_embedded, BOOL use_ellipses) const; + + /*virutal*/ F32 getLineHeight() const; + /*virutal*/ F32 getAscenderHeight() const; + /*virutal*/ F32 getDescenderHeight() const; + + /*virutal*/ F32 getWidthF32(const std::string &utf8text, + S32 offset, S32 max_chars, BOOL use_embedded) const; + /*virutal*/ F32 getWidthF32(const llwchar *wchars, + S32 offset, S32 max_chars, BOOL use_embedded) const; + + /*virutal*/ S32 maxDrawableChars(const llwchar *wchars, + F32 max_pixels, S32 max_chars, + BOOL end_on_word_boundary, const BOOL use_embedded, + F32 *drawn_pixels) const; + + /*virutal*/ S32 firstDrawableChar(const llwchar *wchars, + F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const; + + /*virutal*/ S32 charFromPixelOffset(const llwchar *wchars, + const S32 char_offset, + F32 x, F32 max_pixels, S32 max_chars, + BOOL round, BOOL use_embedded) const; + + /*virutal*/ void addEmbeddedChar(llwchar wc, LLImageGL *image, const std::string &label); + /*virutal*/ void addEmbeddedChar(llwchar wc, LLImageGL *image, const LLWString &label); + /*virutal*/ void removeEmbeddedChar(llwchar wc); + +protected: + /*virutal*/ void destroyGLTexture(); + +public: + LLFontGL *mRealFont; +}; + +#endif + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontmanager.cpp b/indra/llrender/llfontmanager.cpp new file mode 100644 index 0000000..2d5e891 --- /dev/null +++ b/indra/llrender/llfontmanager.cpp @@ -0,0 +1,196 @@ +/** + * @file llfontmanager.cpp + * @brief Takes care of LLFontGL object creation and debugging + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llfontgl.h" +#include "llfontfreetypegl.h" +#include "llfontpangogl.h" +#include "llfontpangocairogl.h" +#include "llfontmanager.h" + +#include "llerror.h" + + +LLFontManager *gFontManagerp = NULL; +LLFontManager::options_map_t LLFontManager::sOptionsMap; + +const char LLFontManager::OPTION_TRUE[] = "true"; +const char LLFontManager::OPTION_FALSE[] = "false"; + +const char LLFontManager::OPTION_GLYPH_RECT[] = "GLYPH_RECT"; + +//static +void LLFontManager::initClass() +{ + // Pango will be our default text renderer. + debugSelectTextRenderer(PANGO, NULL, NULL); + + // I believe Auto Hint default adjustment does good on all platforms. + debugSetOption(OPTION_AUTOHINT_DEFAULT, OPTION_TRUE); + + // Console hack is enabled as default. + debugSetOption(OPTION_TWEAK_MONOSPACE, OPTION_TRUE); + + // Substitute SL font names with GTK/FontConfig standard aliases + debugSetOption(OPTION_SUBSTITUTE_FONTNAMES, OPTION_TRUE); +} + +//static +void LLFontManager::cleanupClass() +{ + delete gFontManagerp; + gFontManagerp = NULL; +} + +LLFontManager::LLFontManager() +{ +} + +LLFontManager::~LLFontManager() +{ +} + +// Debug features + +void *const LLFontManager::FREETYPE = (void *)1; +void *const LLFontManager::PANGO = (void *)2; +void *const LLFontManager::PANGOCAIRO = (void *)3; + +void *LLFontManager::sActiveRenderer; + +//static +void LLFontManager::debugSelectTextRenderer(void *renderer, void (*func)(void *data), void *data) +{ + LLFontManager *old_manager = gFontManagerp; + + if (old_manager && renderer == sActiveRenderer) + { + // Already active. + return; + } + sActiveRenderer = renderer; + + if (sActiveRenderer == LLFontManager::PANGO) + { + llinfos << "Using pango text renderer." << llendl; + gFontManagerp = new LLFontManagerPangoGL(); + } + else if (sActiveRenderer == LLFontManager::PANGOCAIRO) + { + llinfos << "Using pango-cairo text renderer." << llendl; + gFontManagerp = new LLFontManagerPangoCairoGL(); + } + else if (sActiveRenderer == LLFontManager::FREETYPE) + { + llinfos << "Using FreeType text renderer." << llendl; + gFontManagerp = new LLFontManagerFreeTypeGL(); + } + else + { + llerrs << "unknown text renderer: " << renderer << llendl; + } + + if (old_manager && func) + { + func(data); + } + + delete old_manager; +} + +//static +BOOL LLFontManager::debugTextRendererInUseCallback(void *renderer) +{ + return renderer == sActiveRenderer; +} + +// Notes on text debug options API. +// +// Debug option APIs identify a debug option with _option_ parameter. +// Although it is of type const char * and appears as a character +// string, the contents of the string is never examined in these APIs. +// In practice, they are handled as raw addresses. So, two _option_s +// with same contents are handled as two different debug option if +// they are stored in different locations. + +//static +void LLFontManager::debugSetOption(const char *option, const char *value) +{ + if (value) + { + sOptionsMap[option] = value; + } + else + { + sOptionsMap.erase(option); + } +} + +//static +BOOL LLFontManager::debugOptionValueCallback(const char *option, const char *value) +{ + options_map_t::const_iterator p = sOptionsMap.find(option); + if (value == NULL) + { + return p == sOptionsMap.end(); + } + if (p != sOptionsMap.end()) + { + return p->second == value; + } + return FALSE; +} + +//static +void LLFontManager::debugFindOption(const char *option, const char **value_location) +{ + options_map_t::const_iterator p = sOptionsMap.find(option); + if (p != sOptionsMap.end()) + { + *value_location = p->second; + } +} + +//static +bool LLFontManager::debugGetBoolOption(const char *option) +{ + options_map_t::const_iterator p = sOptionsMap.find(option); + return p != sOptionsMap.end() + && p->second == LLFontManager::OPTION_TRUE; +} + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: + diff --git a/indra/llrender/llfontmanager.h b/indra/llrender/llfontmanager.h new file mode 100644 index 0000000..a839c50 --- /dev/null +++ b/indra/llrender/llfontmanager.h @@ -0,0 +1,116 @@ +/** + * @file llfontmanager.h + * @brief Takes care of LLFontGL object creation and debugging + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFONTMANAGER_H +#define LL_LLFONTMANAGER_H + +#include + +class LLFontGL; + +class LLFontManager +{ +public: + static void initClass(); + static void cleanupClass(); + +public: + virtual void reset() {} + virtual void setDPIs(F32 horiz_dpi, F32 vert_dpi) {} + virtual void setLanguage(const std::string &language) {} + virtual void setFallback(const std::string &face, F32 scale) {} + virtual LLFontGL *createFont(const std::string &face, F32 size, bool console = false) = 0; + +protected: + LLFontManager(); + virtual ~LLFontManager(); + +// Members below are debug (Advanced) menu interface + +public: + + // text rendering engines. + static void debugSelectTextRenderer(void *renderer, void (*func)(void *data), void *data); + static BOOL debugTextRendererInUseCallback(void *renderer); + + // debug option handling. The option parameter of each API must + // be one of the OPTION_* member defined below, as opposed to + // arbitrary string (array of char.) See notes in + // llfontmanager.cpp for details. + static void debugSetOption(const char *option, const char *value); + static BOOL debugOptionValueCallback(const char *option, const char *value); + static void debugFindOption(const char *option, const char **value_location); + static bool debugGetBoolOption(const char *option); + +public: + + // Selectors for text rendering engines. + static void *const FREETYPE; + static void *const PANGO; + static void *const PANGOCAIRO; + + // Boolean values for debug options. + static const char OPTION_TRUE[]; + static const char OPTION_FALSE[]; + + // Debug options for all engines. + static const char OPTION_GLYPH_RECT[]; + + // Debug options for pango-freetype; they are defined in llfontpangogl.cpp + static const char OPTION_ANTIALIAS[]; + static const char OPTION_HINTING[]; + static const char OPTION_AUTOHINT[]; + static const char OPTION_AUTOHINT_DEFAULT[]; + + // Debug options for all pango based engines, defined in llfontpangogl.cpp + static const char OPTION_SUBSTITUTE_FONTNAMES[]; + static const char OPTION_TWEAK_MONOSPACE[]; + static const char OPTION_TWEAK_ALL[]; + static const char OPTION_NO_GLYPH_CACHE[]; + static const char OPTION_RTL[]; + +private: + static void *sActiveRenderer; + typedef std::map options_map_t; + static options_map_t sOptionsMap; + // It's not an error that options_map_t::key_type is const + // char * but std::string. See llfontmanager.cpp to know why. +}; + +extern LLFontManager *gFontManagerp; + +#endif // LL_FONTMANAGER_H + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontpangocairogl.cpp b/indra/llrender/llfontpangocairogl.cpp new file mode 100644 index 0000000..28326c5 --- /dev/null +++ b/indra/llrender/llfontpangocairogl.cpp @@ -0,0 +1,120 @@ +/** + * @file llfontpangocaiogl.cpp + * @brief pango-over-cairo based text renderer + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llfontpangocairogl.h" + +#include + +LLFontManagerPangoCairoGL::LLFontManagerPangoCairoGL() + : LLFontManagerPangoGL() +{ + // Discard the PangoFT2FontMap object pointed to by mFontMap and + // replace it with a PangoCairoFontMap object. Yes, this is + // hacky. We use the default fontmap, so we should never free it. + // (See pango documentation.) However, + // LLFontManagerPangoGL::~LLFontManagerPangoGL() always unref it. + // We ref it here for compensation. This is hacky, too! + g_object_unref(G_OBJECT(mFontMap)); + mFontMap = pango_cairo_font_map_get_default(); + g_object_ref(G_OBJECT(mFontMap)); +} + +LLFontManagerPangoCairoGL::~LLFontManagerPangoCairoGL() +{ +} + +void LLFontManagerPangoCairoGL::reset() +{ + // We have nothing to do on reset. We, however, need to suppress + // execution of LLFontManagerPangoGL::reset(), because it is + // incompatible with our PangoCairoFontMap. +} + +void LLFontManagerPangoCairoGL::setDPIs(F32 horiz_dpi, F32 vert_dpi) +{ + // PangoCairoFontMap support isotropic DPI only. For the moment, + // we use an average DPI for both horizontal and vertical axes. + // We could simulate anisotropic DPI handling using a pango's (or + // cairo's) transformation matrix, if it is absolutely needed. + // FIXME. + PangoCairoFontMap *fontmap = PANGO_CAIRO_FONT_MAP(mFontMap); + pango_cairo_font_map_set_resolution(fontmap, (horiz_dpi + vert_dpi) / 2); +} + +LLFontGL *LLFontManagerPangoCairoGL::createFont(const std::string &face, F32 size, bool console) +{ + PangoCairoFontMap *fontmap = PANGO_CAIRO_FONT_MAP(mFontMap); + PangoContext *context = pango_cairo_font_map_create_context(fontmap); + LLFontPangoCairoGL *fontp = new LLFontPangoCairoGL(this, context); + fontp->loadFace(face, size, console); + return fontp; +} + +LLFontPangoCairoGL::LLFontPangoCairoGL(const LLFontManagerPangoCairoGL *const manager, PangoContext *context) + : LLFontPangoGL(manager, context) +{ +} + +LLFontPangoCairoGL::~LLFontPangoCairoGL() +{ + // We have nothing to do other than those done i + // LLFontPangoGL::~LLfontPangoGL(). gobject based cleanup process + // works equally both for PangoFT2 based objects and for + // PangoCairo based objects. +} + +unsigned char *LLFontPangoCairoGL::renderGlyphImage(PangoFont *font, PangoGlyphString *glyphs, const PangoRectangle &ink, int *image_stride) const +{ +#if 0 + const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_A8, ink.width); +#else + // cairo_format_stride_for_width() is new to cairo 1.6 and is + // not available on cairo 1.4. We should avoid using it, because + // we currently target at GTK+ 2.10 compatible library versions, + // and cairo 1.4 come with GTK+ 2.10. As an alternative, We use + // implicit knowledge on cairo internals and _guess_ a good + // stride manually. + const int stride = (ink.width + 3) & ~3; +#endif + unsigned char *image_data = new unsigned char[stride * ink.height]; + memset(image_data, 0, stride * ink.height); + cairo_surface_t *surface = cairo_image_surface_create_for_data(image_data, CAIRO_FORMAT_A8, ink.width, ink.height, stride); + + cairo_t *cairo_context = cairo_create(surface); + cairo_translate(cairo_context, -ink.x, -ink.y); + pango_cairo_show_glyph_string(cairo_context, font, glyphs); + cairo_destroy(cairo_context); + + cairo_surface_destroy(surface); + *image_stride = stride; + return image_data; +} diff --git a/indra/llrender/llfontpangocairogl.h b/indra/llrender/llfontpangocairogl.h new file mode 100644 index 0000000..63bd644 --- /dev/null +++ b/indra/llrender/llfontpangocairogl.h @@ -0,0 +1,65 @@ +/** + * @file llfontpangocairogl.h + * @brief pango-over-cairo based text renderer + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFONTPANGOCAIROGL_H +#define LL_LLFONTPANGOCAIROGL_H + +#include "llfontmanager.h" +#include "llfontgl.h" +#include "llfontpangogl.h" + +class LLFontManagerPangoCairoGL : public LLFontManagerPangoGL +{ +public: + LLFontManagerPangoCairoGL(); + ~LLFontManagerPangoCairoGL(); + /*virtual*/ void reset(); + /*virtual*/ void setDPIs(F32 horiz_dpi, F32 vert_dpi); + /*virtual*/ LLFontGL *createFont(const std::string &face, F32 size, bool console); +}; + +class LLFontPangoCairoGL : public LLFontPangoGL +{ +public: + LLFontPangoCairoGL(const LLFontManagerPangoCairoGL *manager, PangoContext *context); + ~LLFontPangoCairoGL(); +protected: + /*virtual*/ unsigned char *renderGlyphImage(PangoFont *font, PangoGlyphString *glyphs, const PangoRectangle &ink, int *image_stride) const; +}; + + +#endif + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontpangogl.cpp b/indra/llrender/llfontpangogl.cpp new file mode 100644 index 0000000..abbef53 --- /dev/null +++ b/indra/llrender/llfontpangogl.cpp @@ -0,0 +1,2298 @@ +/** + * @file llfontpangogl.cpp + * @brief pango-over-freetype based text renderer + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llfontgl.h" +#include "llfontpangogl.h" +#include "llgl.h" +#include "llrender.h" +#include "v4color.h" +#include "llstl.h" + +#include + +#include + +// File local constants + +// I'm not sure under what circumstances the following three values +// are chosen. They are simply copied from LL's original +// implementation along with related GL commands... + +static const F32 PIXEL_BORDER_THRESHOLD = 0.0001f; +static const F32 PIXEL_CORRECTION_DISTANCE = 0.01f; +static const F32 PAD_AMT = 0.5f; + +// For some reason, PangoFT2 renders each glyph slightly larger than +// LL's direct use of FreeType2 for the same DPIs and point sizes. I +// have no idea why. As a workaround, I considered adjusting the size. + +static const F32 PANGO_FONT_SIZE_ADJUSTER = 0.94f; + +// Pango doesn't provide _standard_ line height or line gap, so we +// just estimate with a constant factor. + +static const F32 PANGO_LINE_HEIGHT_ADJUSTER = 1.1f; + +// Embedded objects are indicated by some special llwchar values that +// are registered through addEmbeddedChar methods. Although Linden's +// original implementation of LLFontGL class is so flexible that it +// allows arbitrary llwchar value as the embedded character, in Second +// Life, only Unicode private use code points in range U+100000 to +// U+10FFFF (i.e., the plane #16) are used to indicate the embedding. +// The values 0x100000 and 0x10FFFF are defined in +// llui/lltexteditor.h. Note that, in UTF-8, U+100000 and U+10FFFF +// are F4 80 80 80 and F4 8F BF BF, respectively, and the byte value +// 0xF4 never appears in valid UTF-8 byte stream other than the code +// points in the range U+100000 to U+10FFFF. In this implementation, +// we presume the range U+100000 to U+10FFFF for the embedding +// indicator, and use the magic byte value 0xF4 to detect the presense +// of embedded objects. + +static const gchar EMBEDDED_CHAR_PREFIX = (gchar)0xF4; + +// The embedded character is always four bytes in UTF-8. + +static const int EMBEDDED_CHAR_BYTES = 4; + +// The size of the GL texture that works as a cache for glyph images. +// We absolutely need wider texture than that of LL's original +// FreeType based implementation, because we cache items rather than +// characters (glyphs.) LL's FreeType texture is dynamically sized, +// but usually is 512x512. We surely allocate more. The graphics +// memory pressure, however, is relatively smaller even if we use +// larger texture; (1) Our texture has only one componenet, alpha, +// while LL's original had two, luminance and alpha. (2) We allocate +// single texture for all six LLFontGL instances, while LL's original +// implementation did one texture for an LLFontGL instance. YES, you +// can apply these techniques to FreeType based implementation. They +// are not advantages of pango. They are advantages of _my_design_. :-) + +static const S32 TEXTURE_WIDTH = 512; +static const S32 TEXTURE_HEIGHT = 1024; + +// The following struct/classes are only used in llfontpangogl.cpp. + +struct LLPangoGlyphImageInfo +{ + S32 mXBitmapOffset, mYBitmapOffset; + S32 mWidth, mHeight; + S32 mXBearing, mYBearing; + F32 mAdvance; + BOOL mIsLast; +}; + +struct LLPangoGlyphImageCacheEntry +{ + PangoFont *mFont; + S32 mNumberOfGlyphs; + const PangoGlyphInfo *mGlyphInfo; + S32 mGlyphImageInfoId; + + LLPangoGlyphImageCacheEntry(PangoFont *font, const PangoGlyphString *glyphs); + void retain(); + static void freeInfo(const LLPangoGlyphImageCacheEntry &a); +}; + +bool operator<(const LLPangoGlyphImageCacheEntry &a, const LLPangoGlyphImageCacheEntry &b); + +class LLPangoGlyphImageManager +{ +public: + LLPangoGlyphImageManager(); + ~LLPangoGlyphImageManager(); + void initGLTexture(); + void destroyGLTexture(); + void reset(); + void cacheGlyphImage(const PangoGlyphItem &glyph_item, const LLPangoGlyphImageInfo *info); + const LLPangoGlyphImageInfo *lookupGlyphImage(const PangoGlyphItem &glyph_item) const; + const LLPangoGlyphImageInfo *addGlyphImage(const unsigned char *image_data, S32 image_stride, PangoRectangle &ink, F32 advance); + const LLPangoGlyphImageInfo *getNextGlyphImage(const LLPangoGlyphImageInfo *) const; + void bindGL(const LLPangoGlyphImageInfo *info); + void unbindGL(); + F32 getInvertedTextureWidth() const { return mInvertedTextureWidth; } + F32 getInvertedTextureHeight() const { return mInvertedTextureHeight; } + +private: + S32 allocateRects(S32 width, S32 height); + LLPangoGlyphImageInfo *getInfoFromPool(); + +private: + std::vector mGlyphImageInfoPool; + typedef std::set ll_glyph_image_cache_t; + ll_glyph_image_cache_t mGlyphImageCache; + LLPointer mImageRawp; + LLPointer mImageGLp; + F32 mInvertedTextureWidth, mInvertedTextureHeight; + S32 mAllocateX, mAllocateY, mMaxAllocatedHeight; + S32 mWasted; + const LLImageGL *mActiveImageGL; + +private: + // for debugging and logging + bool mNoCaching; + bool mInitialized; +}; + +const char LLFontPangoGL::sEllipses[] = "...."; + +const char LLFontManager::OPTION_ANTIALIAS[] = FC_ANTIALIAS; +const char LLFontManager::OPTION_HINTING[] = FC_HINTING; +const char LLFontManager::OPTION_AUTOHINT[] = FC_AUTOHINT; +const char LLFontManager::OPTION_AUTOHINT_DEFAULT[] = "AUTOHINT-DEFAULT"; + +static void handle_bool_fc_option(FcPattern *pattern, const char *option) +{ + const char *value = NULL; + LLFontManager::debugFindOption(option, &value); + if (value) + { + FcPatternDel(pattern, option); + FcPatternAddBool(pattern, option, (value == LLFontManager::OPTION_TRUE)); + } +} + +static void substitute_func(FcPattern *pattern, gpointer data) +{ + // Handle bool option adjustment. + handle_bool_fc_option(pattern, LLFontManager::OPTION_ANTIALIAS); + handle_bool_fc_option(pattern, LLFontManager::OPTION_HINTING); + handle_bool_fc_option(pattern, LLFontManager::OPTION_AUTOHINT); + + // Handle the auto hint default adjustment. + FcBool b; + if (LLFontManager::debugGetBoolOption(LLFontManager::OPTION_AUTOHINT_DEFAULT) + && FcPatternGetBool(pattern, FC_AUTOHINT, 0, &b) == FcResultNoMatch) + { + FcBool antialias = TRUE; + FcPatternGetBool(pattern, FC_ANTIALIAS, 0, &antialias); + FcPatternAddBool(pattern, FC_AUTOHINT, antialias); + } +} + +LLFontManagerPangoGL::LLFontManagerPangoGL() + : mEmptyPangoAttrList(pango_attr_list_new()), + mGlyphImageManager(new LLPangoGlyphImageManager()) +{ + llinfos << "Using pango text renderer" + << ", compiled against pango " << PANGO_VERSION_STRING + << ", running on " << pango_version_string () << llendl; + + // Prepare for an FT2 font map here so that all PangoContext's for + // LLFontPangoGL share it. This may be overwritten by a subclass. + mFontMap = pango_ft2_font_map_new(); + pango_ft2_font_map_set_default_substitute(PANGO_FT2_FONT_MAP(mFontMap), substitute_func, NULL, NULL); + + // Prepare for a default PangoLanguage. We use a hard-coded + // default "en". If we call pango_language_get_default(), unknown + // system locale settings can cause unpredictable font matching + // behaviour. We don't want it. Anyway our real locale will be + // that set by the user through viewer's preferences panel. + mPangoLanguage = pango_language_from_string("en"); +} + +LLFontManagerPangoGL::~LLFontManagerPangoGL() +{ + // We must not free/unref PangoLanguage object, so let + // mPangoLanguage as is. (See pango document.) + g_object_unref(G_OBJECT(mFontMap)); + delete mGlyphImageManager; + pango_attr_list_unref(mEmptyPangoAttrList); +} + +void LLFontManagerPangoGL::reset() +{ + PangoFT2FontMap *fontmap = PANGO_FT2_FONT_MAP(mFontMap); + pango_ft2_font_map_substitute_changed(fontmap); +} + +void LLFontManagerPangoGL::setDPIs(F32 horiz_dpi, F32 vert_dpi) +{ + // Update the fontmap to reflect the current settings so that + // following calls to createFont uses it. + PangoFT2FontMap *fontmap = PANGO_FT2_FONT_MAP(mFontMap); + pango_ft2_font_map_set_resolution(fontmap, horiz_dpi, vert_dpi); +} + +void LLFontManagerPangoGL::setLanguage(const std::string &language) +{ + // We must not free/release an old (if any) PangoLanguage object. + // See pango document for the point. + mPangoLanguage = pango_language_from_string(language.c_str()); +} + +LLFontGL *LLFontManagerPangoGL::createFont(const std::string &face, F32 size, bool console) +{ + // We don't use FallbackFace and FallbackScale. Pango, getting + // helped by fontconfig, takes care of these in more complete + // ways. (The feature may depend on the backend, however...) + + PangoFT2FontMap *fontmap = PANGO_FT2_FONT_MAP(mFontMap); + PangoContext *context = pango_ft2_font_map_create_context(fontmap); + LLFontPangoGL *fontp = new LLFontPangoGL(this, context); + fontp->loadFace(face, size, console); + return fontp; +} + +LLFontPangoGL::LLFontPangoGL(const LLFontManagerPangoGL *manager, PangoContext *context) + : mFontManager(manager), + mPangoContext(context) +{ + mTweakDigits = false; +} + +LLFontPangoGL::~LLFontPangoGL() +{ + if (mPangoContext) + { + g_object_unref(G_OBJECT(mPangoContext)); + } + // The glyph image cache may have a reference to a PangoFont + // instance that belongs to us, so we need to invalidate them. + // Resetting is the easiest way to do so. Font manager itself is + // shared by other instances, so we shouldn't delete it. + mFontManager->mGlyphImageManager->reset(); +} + +void LLFontPangoGL::reset() +{ + mFontManager->mGlyphImageManager->initGLTexture(); + mEllipsesWidth = getWidthInternal(sEllipses, strlen(sEllipses), FALSE); +} + +void LLFontPangoGL::destroyGLTexture() +{ + mFontManager->mGlyphImageManager->destroyGLTexture(); +} + +const char LLFontManager::OPTION_SUBSTITUTE_FONTNAMES[] = "SUBSTITUTE"; +const char LLFontManager::OPTION_TWEAK_MONOSPACE[] = "TWEAK_MONOSPACE"; +const char LLFontManager::OPTION_TWEAK_ALL[] = "TWEAK_ALL"; +const char LLFontManager::OPTION_RTL[] = "RTL"; + +void LLFontPangoGL::loadFace(const std::string& face, F32 size, bool console) +{ + std::string real_face = face; + + if (LLFontManager::debugGetBoolOption(LLFontManager::OPTION_SUBSTITUTE_FONTNAMES)) + { + // A hack to replace LL standard font file names with + // fontconfig/gtk standard face names. This should disappear when + // LL viewer completed a full transitioin to pango. + if (face == "profontwindows.ttf") + { + real_face = "monospace"; + } + else if (face == "MtBkLfRg.ttf") + { + real_face = "sansserif"; + } + else if (face == "MtBdLfRg.ttf") + { + real_face = "sansserif bold"; + } + } + + mTweakDigits = false; + if (console + && LLFontManager::debugGetBoolOption(LLFontManager::OPTION_TWEAK_MONOSPACE)) + { + // Debug consoles are special that they often show rapidly + // changing numbers. It can rapidly fill up the cache space. + // A _console_hack_ is a special caching technique to + // compensate the case. It is turned on by default, but you + // can suppress it through a debug option on the Advanced + // menu. If you know you never show any debug console, + // suppressing the console hack slightly increases the frame + // rate. (Don't use Statistics Bar in that case; it is a + // typical debug console! Use some external tool to compare + // the performance.) See LLFontPangoGL::itemize for details + // of the technique. + mTweakDigits = true; + } + else if (LLFontManager::debugGetBoolOption(LLFontManager::OPTION_TWEAK_ALL)) + { + // I don't think the mTweakDigits is useful for non-console + // fonts, but such a debug option is also provided for + // testing and comparison. + mTweakDigits = true; + } + + llinfos << "Loading font \"" << real_face << "\" for \"" << face << "\"" << llendl; + + pango_context_set_language(mPangoContext, mFontManager->mPangoLanguage); + + const char *rtl = NULL; + LLFontManager::debugFindOption(LLFontManager::OPTION_RTL, &rtl); + if (rtl == LLFontManager::OPTION_TRUE) + { + pango_context_set_base_dir(mPangoContext, PANGO_DIRECTION_RTL); + } + else if (rtl == LLFontManager::OPTION_FALSE) + { + pango_context_set_base_dir(mPangoContext, PANGO_DIRECTION_LTR); + } + else + { + // Should we refer to the current language (mPangoLanguage) to + // select between WL and WR? FIXME. + pango_context_set_base_dir(mPangoContext, PANGO_DIRECTION_WEAK_LTR); + } + + PangoFontDescription *description = pango_font_description_from_string(real_face.c_str()); + pango_font_description_set_size(description, llround(size * PANGO_SCALE * PANGO_FONT_SIZE_ADJUSTER)); + pango_context_set_font_description(mPangoContext, description); + + PangoFontMetrics *metrics = pango_context_get_metrics(mPangoContext, description, mFontManager->mPangoLanguage); + mAscender = (F32)pango_font_metrics_get_ascent(metrics) / PANGO_SCALE; + mDescender = (F32)pango_font_metrics_get_descent(metrics) / PANGO_SCALE; + mLineHeight = (mAscender + mDescender) * PANGO_LINE_HEIGHT_ADJUSTER; + // PangoFontMetrics does not provide line height or line gap. + // In pango, it is a PangoLayout's function to adjust the line + // spacing, and it adjusts line space dynamically, i.e., based + // on the actual hights of the glyph clusters appearing in the + // line. Just as an estimate, we slightly extend the height + // from the _standard_ metrics. + pango_font_metrics_unref(metrics); + pango_font_description_free(description); + + reset(); +} + +// Returns true if the given bidi level (as stored in +// PangoAnalysis::level) is for left to right segments. Returns false +// if it is for right to left. +static inline bool ll_bidi_level_is_left_to_right(guint8 bidi_level) +{ + // Even levels are for left to right segments. Odd levels are for + // right to left segments. See UAX#9 to know ideas behind this. + return (bidi_level & 1) == 0; +} + +static std::string ll_utf8str_from_wchar_array(const llwchar *wchars, S32 max_chars) +{ + if (max_chars < 0) + { + max_chars = S32_MAX; + } + int i = 0; + while (wchars[i] && i < max_chars) + { + i++; + } + return wstring_to_utf8str(LLWString(wchars, i)); +} + +// Returns true if and only if a UTF-8 character stored in string is +// for embedded object. This function is intended to be very fast, +// and it does minimum testing. + +//static +inline bool LLFontPangoGL::isEmbeddedChar(const gchar *string) +{ + return EMBEDDED_CHAR_PREFIX == *string; +} + +inline const LLFontGL::embedded_data_t *LLFontPangoGL::getEmbeddedCharData(const gchar *utf8string, bool use_embedded) const +{ + if (use_embedded && isEmbeddedChar(utf8string)) + { + return LLFontGL::getEmbeddedCharData(g_utf8_get_char(utf8string)); + } + return NULL; +} + +F32 LLFontPangoGL::getWidthF32(const std::string& utf8text, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const +{ + if (max_chars <= 0) + { + // getWidth family member functions consider a negative + // max_chars value as a zero, i.e., no characters to examine. + return 0; + } + + const gchar * data = (const gchar *)utf8text.data(); + glong length_in_chars = g_utf8_strlen(data, utf8text.length()); + if (begin_offset >= length_in_chars) + { + return 0; + } + data = g_utf8_offset_to_pointer(data, begin_offset); + length_in_chars -= begin_offset; + if (max_chars < length_in_chars) + { + length_in_chars = max_chars; + } + S32 length_in_bytes = g_utf8_offset_to_pointer(data, length_in_chars) - data; + return getWidthInternal(data, length_in_bytes, use_embedded); +} + + +F32 LLFontPangoGL::getWidthF32(const llwchar* wchars, const S32 begin_offset, S32 max_chars, BOOL use_embedded) const +{ + if (max_chars <= 0) + { + // getWidth family member functions consider a negative + // max_chars value as a zero, i.e., no characters to examine. + return 0; + } + + const llwchar *start = wchars + begin_offset; + int i = 0; + while (start[i] && i < max_chars) + { + i++; + } + std::string utf8str = wstring_to_utf8str(LLWString(start, i)); + return getWidthInternal((const gchar *)utf8str.data(), utf8str.length(), use_embedded); +} + +F32 LLFontPangoGL::getWidthInternal(const gchar *text, S32 length, BOOL use_embedded_BOOL) const +{ +// LLFastTimer timer(LLFastTimer::FTM_TEMP1); + + const bool use_embedded = use_embedded_BOOL && hasEmbeddedChars(); // a minor optimization + PangoGlyphString * const glyphs = pango_glyph_string_new(); + S32 units = 0; + +#if 0 + + // A simple version. + + ll_glyph_item_list_t list = itemize(text, length, use_embedded); + for (ll_glyph_item_list_t::const_iterator p = list.begin(); p != list.end(); p++) + { + const PangoItem *const item = p->item; + const embedded_data_t *const embedded_data = getEmbeddedCharData(text + item->offset, use_embedded); + if (embedded_data) + { + units += llround(getEmbeddedCharAdvance(embedded_data) * PANGO_SCALE); + } + else + { + pango_shape(text + item->offset, item->length, &item->analysis, glyphs); + units += pango_glyph_string_get_width(glyphs); + } + } + std::for_each(list.begin(), list.end(), LLGlyphItem::freeGlyphItem); + +#else + + // A slightly faster version that uses raw pango APIs directly. + + PangoAttrList *attrs = mFontManager->mEmptyPangoAttrList; + GList * const list = pango_itemize(mPangoContext, text, 0, length, attrs, NULL); + for (GList *p = list; p; p = g_list_next(p)) + { + PangoItem * const item = static_cast(p->data); + p->data = NULL; + + if (use_embedded) + { + const gchar * s = text + item->offset; + const gchar * const end = s + item->length; + while (s < end) + { + const gchar * const t = s; + while (s < end && !isEmbeddedChar(s)) + { + s = g_utf8_next_char(s); + } + if (s > t) + { + // An ordinary subitem. + pango_shape(t, s - t, &item->analysis, glyphs); + units += pango_glyph_string_get_width(glyphs); + } + else + { + // An embedded object. + const embedded_data_t *const embedded_data = LLFontGL::getEmbeddedCharData(g_utf8_get_char(s)); + if (embedded_data) + { + units += llround(getEmbeddedCharAdvance(embedded_data) * PANGO_SCALE); + } + else + { + // This should not happen. I suspect a bug in + // the caller... Anyway, simulate the old + // behaviour of FreeType version, i.e, draw an + // open box as an unknown glyph. (Or whatever + // the underlying font defins.) + pango_shape(s, EMBEDDED_CHAR_BYTES, &item->analysis, glyphs); + units += pango_glyph_string_get_width(glyphs); + } + s = g_utf8_next_char(s); + } + } + } + else + { + pango_shape(text + item->offset, item->length, &item->analysis, glyphs); + units += pango_glyph_string_get_width(glyphs); + } + + pango_item_free(item); + } + g_list_free(list); + +#endif + + pango_glyph_string_free(glyphs); + + return (F32)units / (F32)PANGO_SCALE / sScaleX; +} + +// Returns the max number of complete characters from text (up to +// max_chars) that can be drawn in max_pixels. If +// end_on_word_boundary is set, this method looks for preferrable line +// break points of the language/script the text is in instead of +// simple character boundaries. (Note that the name of the parameter +// is named after the fact that the preferrable line break points of +// modern English texts written in Latin alphabets are the word +// boundary.) However, when the text does not contain a good line +// break point before the specified pixels, this method ignores +// end_on_word_boundary. This method may return a zero if the first +// character is wider than the specified pixels. The pixels that will +// be occupied with the fist maxDrawableChars characters will be +// stored in a variable that is pointed to by drawn_pixels, if it is +// not a NULL. Note that the value returned in the variable pointed +// to by drawn_pixels is in _scaled_pixels_, although the value given +// in max_pixels is in _logical_ (i.e., unscaled) pixels. + +S32 LLFontPangoGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, + BOOL end_on_word_boundary, const BOOL use_embedded, + F32* drawn_pixels) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + llassert(max_pixels >= 0.f); + llassert(max_chars >= 0); + + // + // The current implementation is not efficient if a very long whcars + // is given. We need to revise the interface eventually. + // + // Also, current implementation can't handle embedded objects. We + // need to revisit here. + + const std::string utf8str = ll_utf8str_from_wchar_array(wchars, max_chars); + const gchar *text = static_cast(utf8str.data()); + const S32 length = utf8str.length(); + + gint chars; + ll_glyph_item_list_t list = itemize(text, length, use_embedded); + fill(text, list, max_pixels * sScaleX, 0, end_on_word_boundary, use_embedded, drawn_pixels, NULL, &chars, NULL); + std::for_each(list.begin(), list.end(), LLGlyphItem::freeGlyphItem); + + return chars; +} + +// Find and return the smallest number n such that the characters +// between n'th position and start_pos'th positions fit in the +// specified pixel width. Positions count from 0. Both ends of the +// range are inclusive, i.e., widths of chracters at the position n +// and start_pos are included in the pixel width. This function is +// used only by LLLineEditor to find an appropriate part of the text +// to show after horizontal scrolling. + +// This function does not have use_embedded parameter. In Linden's +// original codes, it always consider embedded objects, for whatever +// reason. I feel something like it is a bug. I.e., this function +// should never consider embedded thing. FIXME. + +S32 LLFontPangoGL::firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const +{ + bool use_embedded = hasEmbeddedChars(); + + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + // In the current versions of SL viewer, there is only one + // invokation of this function, and it omits max_chars. + + llassert(S32_MAX == max_chars); + + if (start_pos < text_len) + { + start_pos++; + } + else + { + start_pos = text_len; + } + + // Implicitly _truncating_ the given text at the (end of) + // start_pos makes result inaccurate because it may not be on a + // char boundary, and taking care of the following characters + // after start_pos may affect the pixel width. However, we need + // to do a lot more to work correctly. Just for an example, we + // need to work on display order (as opposed to logical orderer on + // which we are currently working) to horizontal-scroll a bidi + // text. We will fix it in a future. FIXME. + + const std::string utf8str = ll_utf8str_from_wchar_array(wchars, start_pos); + const gchar *text = static_cast(utf8str.data()); + const S32 length = utf8str.length(); + + ll_glyph_item_list_t list = itemize(text, length, use_embedded); + S32 num_chars = 0; + for (ll_glyph_item_list_t::const_iterator p = list.begin(); p != list.end(); p++) + { + num_chars += p->item->num_chars; + } + if (num_chars != start_pos) + { + llwarns << "num_chars == " << num_chars << ", start_pos == " << start_pos << llendl; + } + + PangoGlyphString *const glyphs = pango_glyph_string_new(); + int scaled_max_units = llfloor(max_pixels * sScaleX * PANGO_SCALE); + S32 pos = start_pos + 1; + ll_glyph_item_list_t::const_iterator p = list.end(); + do + { + --p; + PangoItem * const item = p->item; + pos -= item->num_chars; + int units; + const embedded_data_t *const embedded_data = getEmbeddedCharData(text + item->offset, use_embedded); + if (embedded_data) + { + units = llround(getEmbeddedCharAdvance(embedded_data) * PANGO_SCALE); + } + else + { + pango_shape(text + item->offset, item->length, &item->analysis, glyphs); + units = pango_glyph_string_get_width(glyphs); + } + scaled_max_units -= units; + } + while (p != list.begin() && scaled_max_units > 0); + + if (scaled_max_units < 0) + { + PangoItem * const item = p->item; + if (use_embedded && isEmbeddedChar(text + item->offset)) + { + // We can't break an embedded object any further. + pos += 1; // item->num_chars == 1 in this case. + } + else + { + char *ptr = const_cast(text); // XXX + int index = 0; + pango_glyph_string_x_to_index(glyphs, ptr + item->offset, item->length, &item->analysis, -scaled_max_units, &index, NULL); + std::vector log_attrs(item->num_chars + 1); + pango_get_log_attrs(text + item->offset, item->length, -1, mFontManager->mPangoLanguage, &log_attrs[0], item->num_chars + 1); + int n = g_utf8_strlen(text + item->offset, index); + while (n < item->num_chars && !log_attrs[n].is_char_break) + { + n++; + } + pos += n; + } + } + + pango_glyph_string_free(glyphs); + std::for_each(list.begin(), list.end(), LLGlyphItem::freeGlyphItem); + + return pos; +} + + +// Given a text (as an array of llwchar's) and a horizontal pixel +// offset target_x, return the character index (counts from 0) at the +// pixel offset. In other words, returned is the number of characters +// to the left of the given pixel offset. begin_offset and max_chars +// define the source text; in the array wchars, only llwchar's +// starting at begin_offsets and up to max_chars characters are +// considered. The function's return value counts from begin_offset, +// e.g., the return value of 0 means the character at the +// begin_offset. max_pixels also limits the text; any characters in +// the text at or to the right of the pixel offset specified by +// max_pixels are considered truncated. Round flag affects the +// hitting criteria. When false (default), a character at the given +// pixel offset is considered hit. When true, a character is +// considered hit if the given pixel offset is on the left half of the +// character's occupancy; if it is on the right half, the function +// considers the next character is hit. You can think setting round +// true makes the function to hit against the boundaries between +// characters but characters themselves, and the return value +// indicates the most closest character boundary to the given pixel +// offset. use_embedded flag affects the behaviour as always. Note +// that the entire space occupied by the embedded object, +// i.e. including the image and the associated caption text, is +// onsidered as a character, for the purpose of hitting by this +// function. All pixel values, i.e., target_x and max_pixels are +// given as virtual (unscaled) pixels. + +S32 LLFontPangoGL::charFromPixelOffset(const llwchar* wchars, S32 begin_offset, F32 target_x, F32 max_pixels, S32 max_chars, BOOL round, BOOL use_embedded) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + gint target_units; + if (target_x < max_pixels) + { + target_units = llround(target_x * sScaleX * PANGO_SCALE); + } + else + { + round = FALSE; + target_units = llround(max_pixels * sScaleX * PANGO_SCALE); + } + + const std::string utf8str = ll_utf8str_from_wchar_array(wchars + begin_offset, max_chars); + const gchar *text = static_cast(utf8str.data()); + const S32 length = utf8str.length(); + + PangoGlyphString *glyphs = pango_glyph_string_new(); + gint current_units = 0; + ll_glyph_item_list_t list = itemize(text, length, use_embedded); + + const embedded_data_t * embedded_data = NULL; + int units = 0; + ll_glyph_item_list_t::const_iterator p; + for (p = list.begin(); p != list.end(); p++) + { + PangoItem * const item = p->item; + embedded_data = getEmbeddedCharData(text + item->offset, use_embedded); + if (embedded_data) + { + units = llround(getEmbeddedCharAdvance(embedded_data) * PANGO_SCALE); + } + else + { + pango_shape(text + item->offset, item->length, &item->analysis, glyphs); + units = pango_glyph_string_get_width(glyphs); + } + if (current_units + units >= target_units) + { + break; + } + current_units += units; + } + + // When we come here: + // + // p is at the _current_ item where target_unit is on (or, list.end() when it passed over all items), + // embedded_data points to the embedded data for the current item when current item is an embedding character, or NULL otherwise, + // units holds the display width of the current item (unless p is at list.end()), + // current_units holds the display width of all items before the current item, + + int index; + if (p == list.end()) + { + // Entire text is before (on the left of) the target point. + index = length; + } + else if (embedded_data) + { + index = p->item->offset; + if (round + && target_units > units / 2 + current_units + && max_pixels * sScaleX * PANGO_SCALE > units + current_units) + { + index += EMBEDDED_CHAR_BYTES; + } + } + else + { +// int trailing = 0; +// char *const ptr = const_cast(text); // XXX +// pango_glyph_string_x_to_index(glyphs, ptr + item->offset, item->length, &item->analysis, +// target_units - current_units, &index, &trailing); +// if (round && trailing) +// { +// // The target_units is on the right half of the character. +// // Let's round up... Well, wait! +// // +// // Assume target_x and max_pixels are very close, target_x +// // is slightly smaller than max_pixels, and both pixels +// // are on a right half of an ASCII character. What +// // happens if this function is called with round set to 1? +// // In the original Linden version, test for the max_pixels +// // hits first, and the character that both target_x and +// // max_pixels are on is returned. However, in this +// // version, the equivalent test done in the above call to +// // pango_glyph_string_x_to_index is for the target_x, and +// // if we simply round up here, the next character would be +// // returned. I believe Linden's behaviour is not an +// // accident. round flag makes the hit against character +// // boundaries, and under the condition described above, +// // the _next_boundary_ is beyond the max_pixels, so we +// // should not consider it is hit. We need to detect the +// // case and compensate properly. +// +// int rounded_index = g_utf8_next_char(text + item->offset + index) - text - item->offset; +// int x_pos; +// pango_glyph_string_index_to_x(glyphs, ptr + item->offset, item->length, &item->analysis, rounded_index, TRUE, &x_pos); +// if (llround(max_pixels * sScaleX * PANGO_SCALE) > x_pos + current_units) +// { +// index = rounded_index; +// } +// } + + // I was wrong. The trailing returned from + // pango_glyph_string_x_to_index indicates whether the target + // position is on a leading half of a character's logical + // occupation (or on a trailing half), that is not very useful + // for our purpose. We first recognize the occupation of the + // graphical cluster at the target, and decides whether the + // target is on the first half of the cluster's occupation. A + // graphical cluster, in this context is a series of Unicode + // character that a text cursor naturally go through with a + // stroke of an horizontal arrow key. + + PangoItem *const item = p->item; + char *const item_text = const_cast(text + item->offset); // some pango functions require a non-const pointer. + pango_glyph_string_x_to_index(glyphs, item_text, item->length, &item->analysis, target_units - current_units, &index, NULL); + std::vector log_attrs(item->num_chars + 1); + pango_get_log_attrs(item_text, item->length, -1, mFontManager->mPangoLanguage, &log_attrs[0], item->num_chars + 1); + + int leading_ch, trailing_ch; + int const index_ch = g_utf8_strlen(item_text, index); + for (leading_ch = index_ch; leading_ch > 0 && !log_attrs[leading_ch ].is_cursor_position; --leading_ch ) ; + for (trailing_ch = index_ch + 1; trailing_ch < item->num_chars && !log_attrs[trailing_ch].is_cursor_position; ++trailing_ch) ; + + int const leading_bytes = g_utf8_offset_to_pointer(item_text, leading_ch ) - item_text; + int const trailing_bytes = g_utf8_offset_to_pointer(item_text, trailing_ch) - item_text; + + int leading_units, trailing_units; + pango_glyph_string_index_to_x(glyphs, item_text, item->length, &item->analysis, leading_bytes, FALSE, &leading_units); + pango_glyph_string_index_to_x(glyphs, item_text, item->length, &item->analysis, trailing_bytes, FALSE, &trailing_units); + + if (round + && target_units > current_units + (leading_units + trailing_units) / 2 + && max_pixels * sScaleX * PANGO_SCALE > trailing_units) + { + index = item->offset + trailing_bytes; + } + else + { + index = item->offset + leading_bytes; + } + + // NOTE: In the above code, we need trailing_* values only + // when round is TRUE. We could conditionalize the + // calculation of trailing_* to reduce the CPU overhead. + // However, if we do so, the clear symmetries of leading_* and + // trailing_* would sink down in the source. Fortunately, + // this function is not so frequently called, and I considered + // the execution speed is less important than the readability + // of the source here. + } + + pango_glyph_string_free(glyphs); + std::for_each(list.begin(), list.end(), LLGlyphItem::freeGlyphItem); + + return g_utf8_strlen(text, index); +} + +S32 LLFontPangoGL::renderUTF8(const std::string &text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + LLFontGL::HAlign halign, + LLFontGL::VAlign valign, + U8 style, + S32 max_chars, + S32 max_pixels, + F32* right_x, + BOOL use_embedded, + BOOL use_ellipses) const +{ + if(!sDisplayFont) //do not display texts + { + // As of 1.20.15, the Lindens' LLFontGL::render always returns + // the whole length of the given string, ignoring the offset, + // max_chars, and other parameters, and it is always in number + // of characters even for the renderUTF8 family... I'm not + // sure it is the expected behaviour, although I'm blindly + // following the manner. FIXME. + + return g_utf8_strlen(text.c_str(), -1); + } + + // render* family member functions consider a negative max_pixels + // value as zero, i.e., to draw nothing, while a negative + // max_chars value as an infinite, i.e., to draw everything. I + // don't know why. + + if (max_pixels <= 0) + { + // Shouldn't we set *right_x? FIXME. + return 0; + } +// if (max_chars < 0) +// { +// max_chars = S32_MAX; +// } + + // max_chars and begin_offset are given in chars. We have no easy + // way to know how many bytes corresponds to the those char counts + // in the given text. We need to count over the corresponding + // bytes. + + gchar const *bytes = (gchar const *)text.data(); + + gchar const *p1, *p2, *p3; + p2 = g_utf8_offset_to_pointer(bytes, begin_offset); + for (p1 = p2; p1 > bytes && p1[-1] != '\n'; --p1) ; + + if (max_chars == S32_MAX || max_chars < 0 || + max_chars >= g_utf8_strlen(p2, bytes + text.length() - p2)) + { + p3 = bytes + text.length(); + } + else + { + p3 = g_utf8_offset_to_pointer(p2, max_chars); + } + + return renderInternal(p1, p2 - p1, p3 - p1, x, y, color, halign, valign, style, max_pixels, right_x, use_embedded, use_ellipses); +} + +S32 +LLFontPangoGL::render(const LLWString &text, + const S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + LLFontGL::HAlign halign, + LLFontGL::VAlign valign, + U8 style, + S32 max_chars, + S32 max_pixels, + F32* right_x, + BOOL use_embedded, + BOOL use_ellipses) const +{ + if(!sDisplayFont) + { + // See a comment in the above method for this. + return text.length(); + } + + // render* family member functions consider a negative max_pixels + // value as zero, i.e., to draw nothing, while a negative + // max_chars value as an infinite, i.e., to draw everything. I + // don't know why. + + if (max_pixels <= 0) + { + // Shouldn't we set *right_x? FIXME. + return 0; + } + if (max_chars < 0) + { + max_chars = S32_MAX; + } + + S32 paragraph_offset; + for (paragraph_offset = begin_offset; paragraph_offset > 0 && text[paragraph_offset - 1] != '\n'; --paragraph_offset) ; + S32 adjusted_begin_offset = begin_offset - paragraph_offset; +// S32 paragraph_offset = begin_offset; +// S32 adjusted_begin_offset = 0; + + std::string utf8str; + if (S32_MAX == max_chars) + { + if (0 == paragraph_offset) + { + utf8str = wstring_to_utf8str(text); + } + else + { + utf8str = wstring_to_utf8str(text.substr(paragraph_offset)); + } + } + else + { + const S32 length_in_chars = llmin(adjusted_begin_offset + max_chars, (S32)text.length() - paragraph_offset); + if (0 == paragraph_offset) + { + utf8str = wstring_to_utf8str(text, length_in_chars); + } + else + { + utf8str = wstring_to_utf8str(text.substr(paragraph_offset, length_in_chars)); + } + } + + const gchar *bytes = (const gchar *)utf8str.data(); + const glong offset = g_utf8_offset_to_pointer(bytes, adjusted_begin_offset) - bytes; + const glong length = utf8str.length(); + return renderInternal(bytes, offset, length, x, y, color, halign, valign, style, max_pixels, right_x, use_embedded, use_ellipses); +} + +// static +void LLFontPangoGL::LLGlyphItem::freeGlyphItem(LLFontPangoGL::LLGlyphItem &glyph_item) +{ + if (glyph_item.item) + { + pango_item_free(glyph_item.item); + glyph_item.item = NULL; + } + if (glyph_item.glyphs) + { + pango_glyph_string_free(glyph_item.glyphs); + glyph_item.glyphs = NULL; + } +} + +LLFontPangoGL::ll_glyph_item_list_t LLFontPangoGL::itemize(const gchar *text, glong length, bool use_embedded) const +{ + use_embedded &= hasEmbeddedChars(); // a minor optimization. + ll_glyph_item_list_t list; + + GList * items = pango_itemize(mPangoContext, text, 0, length, mFontManager->mEmptyPangoAttrList, NULL); + for (GList * p = items; p; p = g_list_next(p)) + { + PangoItem *const item = static_cast(p->data); + if (item->analysis.extra_attrs) + { + static S32 warning_thinner; + if (--warning_thinner <= 0) + { + llwarns << "Pango items have extra attrs!" << llendl; + warning_thinner = 1000; + } + } + if (use_embedded) + { + const gchar *s = text + item->offset; + const gchar *const end_of_item = s + item->length; + for (;;) + { + const gchar *const start_of_item = s; + while (s < end_of_item && !isEmbeddedChar(s)) + { + s = g_utf8_next_char(s); + } + if (s == end_of_item) + { + break; + } + if (s == start_of_item) + { + // this means the text has an embedded object at s. + // make it a singleton item. + s = g_utf8_next_char(s); + if (s == end_of_item) + { + break; + } + } + const gint new_length = s - start_of_item; + const gint new_num_chars = g_utf8_strlen(start_of_item, new_length); + PangoItem *const new_item = pango_item_split(item, new_length, new_num_chars); + list.push_back(LLGlyphItem(new_item)); + } + } + else if (mTweakDigits) + { + // Debug console views show various numeric values that + // change every frame. Caching glyph images from string + // of those numbers (possibly along with leading labels) + // can be very inefficient. To avoid the situation, we + // split an item containing digits into several pieces so + // that a digit forms a singleton. Downsides are an + // increased resource usage and extra execution time + // handling items (including the following code)... Let's + // see what happens! + + const gchar *s = text + item->offset; + const gchar *const end_of_item = s + item->length; + for (;;) + { + const gchar *const start_of_item = s; + while (s < end_of_item && (*s < '0' || *s > '9')) + { + s = g_utf8_next_char(s); + } + if (s == end_of_item) + { + break; + } + if (s == start_of_item) + { + // this means the text contained a digit. make it + // a singleton item. + s = g_utf8_next_char(s); + if (s == end_of_item) + { + break; + } + } + const gint new_length = s - start_of_item; + const gint new_num_chars = g_utf8_strlen(start_of_item, new_length); + PangoItem *const new_item = pango_item_split(item, new_length, new_num_chars); + list.push_back(LLGlyphItem(new_item)); + } + } + list.push_back(LLGlyphItem(item)); + } + g_list_free(items); + + return list; +} + + + + +// Given an original text and a list of items generated from the text, +// find the maximum runs of characters from the beginning of the text +// that fits in the target width (given as number of pixels.) The +// target width may be F32_MAX if no limit. +// +// If the given text was wider than the target width, this function +// returns true, indicating a truncation occured. An exact truncation +// point is optionally returned in this case. If the entire text fits +// in the target width, the function returns false. +// +// If a non-zero truncation margin is specified, and truncation +// occured, the truncation point is changed so that it fits in the +// narrower width. +// +// Unless natural breaking is set to true, this function may truncate +// at any reasonable boundary. If natural breaking is set to true, +// the truncation occures only at a natural line folding point per an +// applicable writing system. (In case of English, a natural line +// folding point is at the word separating whitespace. In case of +// Japanese, a natural line folding point is at any character except +// for some specific prohivited points. PangoLogAttr gives +// information on writing system dependent line folding points.) +// +// This funcation may optionally return the point of truncation in up +// to four forms. If width is non-null, the variable pointed to by +// width receives the pixel width to the truncation point. If bytes +// is non-null, the variable pointed to by bytes receives the number +// of bytes from the beginning of text to the truncation point. If +// chars is non-null, the variable pointed to by chars receives the +// number of characters from the beginning of text to the truncation +// point. If next_glyph_item is non-null, the variable pointed to by +// next_glyph_item receives an iterator to a glyph item immediately +// after the truncation point. An LLGlyphItem may be split in the +// given list at the truncation point so that an item doesn't span +// over the truncation point. + +bool LLFontPangoGL::fill(const gchar *text, ll_glyph_item_list_t &list, + F32 target_width, F32 truncation_margin, bool natural_breaking, bool use_embedded, + F32 *width, glong *bytes, gint *chars, ll_glyph_item_list_t::iterator *next_glyph_item) const +{ + // Some pango functions wants writable text, although they never write into the text... + char *const no_const_text = const_cast(text); + + if (list.empty()) + { + llwarns << "An empty glyph item list!" << llendl; + if (width) + { + *width = 0; + } + if (bytes) + { + *bytes = 0; + } + if (chars) + { + *chars = 0; + } + if (next_glyph_item) + { + *next_glyph_item = list.end(); + } + return false; + } + + S32 target_units = (target_width == F32_MAX) ? S32_MAX : llfloor(target_width * PANGO_SCALE); + S32 units = 0; + glong num_bytes = 0; + gint num_chars = 0; + ll_glyph_item_list_t::iterator p; + + for (p = list.begin(); p != list.end() && units <= target_units; p++) + { + PangoItem *const item = p->item; + const embedded_data_t *const embedded_data = getEmbeddedCharData(text + item->offset, use_embedded); + if (embedded_data) + { + units += llround(getEmbeddedCharAdvance(embedded_data) * PANGO_SCALE); + } + else + { + PangoGlyphString *const glyphs = pango_glyph_string_new(); + pango_shape(text + item->offset, item->length, &item->analysis, glyphs); + units += pango_glyph_string_get_width(glyphs); + p->glyphs = glyphs; + } + num_chars += item->num_chars; + } + + const bool truncate = (units > target_units); + + if (truncate) + { + target_units -= llceil(truncation_margin * PANGO_SCALE); + if (target_units < PANGO_SCALE) + { + // We always allow one phisical pixel. + target_units = PANGO_SCALE; + } + + // If we are to _truncate_, as opposed to _fold_, a bidi text + // with a given width, we need to work on the _display_ order. + // ... How do we know we are truncating or folding? FIXME. + + do + { + // May repeat when truncation margin is wider than items. + --p; + num_chars -= p->item->num_chars; + units -= pango_glyph_string_get_width(p->glyphs); + } + while (units > target_units); + + // At this moment, p points to a valid item, num_chars holds + // the number of characters before the item, and units holds + // the PANGO_SCALE'ed number of pixels before the item. + + int units_from_left = target_units - units; + if (!ll_bidi_level_is_left_to_right(p->item->analysis.level)) + { + // _Flip_ the remnant units if it is a right to left item, + // because pango_glyph_string_get_width expects the units + // are counted visually, i.e., from the left. However, we + // are now looking for an appropriate line folding point, + // and the target_units are counted logically, i.e., from + // the beginning. + units_from_left = pango_glyph_string_get_width(p->glyphs) - units_from_left; + } + int index_bytes; + pango_glyph_string_x_to_index(p->glyphs, no_const_text + p->item->offset, p->item->length, &p->item->analysis, + units_from_left, &index_bytes, NULL); + + gint index_chars = g_utf8_strlen(text + p->item->offset, index_bytes); + std::vector log_attrs(num_chars + index_chars + 2); + const gint bytes_to_examine = g_utf8_next_char(text + p->item->offset + index_bytes) - (text + list.begin()->item->offset); + pango_get_log_attrs(text + list.begin()->item->offset, bytes_to_examine, -1, mFontManager->mPangoLanguage, &log_attrs[0], log_attrs.size()); + + gint pos = 0; + if (natural_breaking) + { + for (pos = num_chars + index_chars; pos > 0 && !log_attrs[pos].is_line_break; --pos) ; + } + if (pos == 0) + { + for (pos = num_chars + index_chars; pos > 0 && !log_attrs[pos].is_char_break; --pos) ; + } +// if (pos == 0 && truncation_margin == 0) +// { +// pos = 1; +// } + num_chars = pos; + num_bytes = g_utf8_offset_to_pointer(text + list.begin()->item->offset, num_chars) - (text + list.begin()->item->offset); + + units = 0; + for (p = list.begin(); p != list.end(); p++) + { + if (p->item->offset + p->item->length > num_bytes) + { + break; + } + units += pango_glyph_string_get_width(p->glyphs); + } + if (p != list.end()) + { + int x_pos; + pango_glyph_string_index_to_x(p->glyphs, + no_const_text + p->item->offset, p->item->length, + &p->item->analysis, + num_bytes - p->item->offset, FALSE, + &x_pos); + units += x_pos; + } + } + else + { + ll_glyph_item_list_t::iterator q = p; + --q; + num_bytes = q->item->offset + q->item->length; + } + + if (width) + { + *width = (F32)units / PANGO_SCALE; + } + if (bytes) + { + *bytes = num_bytes; + } + if (chars) + { + *chars = num_chars; + } + + if (next_glyph_item && truncate && p->item->offset < num_bytes) + { + const gint split_bytes = num_bytes - p->item->offset; + const gint split_chars = g_utf8_strlen(text + p->item->offset, split_bytes); + if (split_bytes <= 0 || split_bytes >= p->item->length || + split_chars <= 0 || split_chars >= p->item->num_chars) + { + llwarns << "split(..., " << split_bytes << ", " << split_chars << ")" << llendl; + } + + PangoItem * const new_item = pango_item_split(p->item, split_bytes, split_chars); + PangoGlyphString * const new_glyphs = p->glyphs; + pango_shape(text + new_item->offset, new_item->length, &new_item->analysis, new_glyphs); + p->glyphs = NULL; + list.insert(p, LLGlyphItem(new_item, new_glyphs)); + } + if (next_glyph_item) + { + *next_glyph_item = p; + } + + return truncate; +} + +// Given a list of items in logical order, reorder the list in visual +// order. This is our own version of pango_reorder_items(). +void LLFontPangoGL::reorder(ll_glyph_item_list_t &list) const +{ + // We could call pango_reorder_items(), but we don't, because the + // pango document implies so. (And the function is actually not + // very convenient, as the document says.) + + // The bidi level of the segment under processing. + guint8 current_level = 0; + + // stack holds pending lists of items, that are logically before + // the current item; items on the stack may be before or after the + // current item in visual order. + std::vector stack; + + // reordered holds the reordered glyph items. Its front() is on + // the left and its back() is on the right in display order. + ll_glyph_item_list_t reordered; + + // We process items in a different order than UAX#9. The result + // is same. If you found any difference, please let me know. + + for (ll_glyph_item_list_t::const_iterator p = list.begin();; p++) + { + llassert(stack.size() == current_level); + + const guint8 next_level = (p == list.end() ? 0 : p->item->analysis.level); + while (current_level < next_level) + { + ++current_level; + stack.push_back(reordered); + reordered.clear(); + } + while (current_level > next_level) + { + --current_level; + ll_glyph_item_list_t &previous = stack.back(); + if (ll_bidi_level_is_left_to_right(current_level)) + { + // We are in a middle of LtR segment. Append the + // newly collected items (currently stored in + // reordered) at the end (right) of the previous list + // of items (stored at the top of the stack) and + // considere the result as the new reordered list of + // items. + reordered.splice(reordered.begin(), previous); + } + else + { + // We are in a middle of RtL segment. Append the + // newly collected item on the left of the previous + // items. + reordered.splice(reordered.end(), previous); + } + stack.pop_back(); + } + + if (p == list.end()) + { + break; + } + + if (ll_bidi_level_is_left_to_right(current_level)) + { + // We are in a middle of LtR segment. Append this item + // at the end of (i.e., to the right of) the current list + // of items. + reordered.push_back(*p); + } + else + { + // We are in a middle of RtL segment. Append this item + // from the left. + reordered.push_front(*p); + } + } + + list = reordered; +} + + +// renderInternal is an internal common implementation of render functions. +// +// text should point to the beginning of the paragraph where the part +// of the text we are to draw belongs to. Even in case we are drawing +// a part of a paragraph, we need to examine earlier texts in the +// paragraph, because it may affect the appearance of the middle text, +// especially to the ordering of the text items in bi-directional +// context. +// +// length is the number of bytes after text. (It is more than the +// number of bytes after the offset position in text when offset > 0.) +// length must be 0 or positive. Passing -1 is not allowed. Caller +// needs to know the length in bytes of the text. renderInternal will +// draw characters after the offset bytes and to the length in text +// unless truncated by max_pixels. + +S32 LLFontPangoGL::renderInternal(const gchar *text, glong offset, glong length, + F32 x, F32 y, const LLColor4 &color, + LLFontGL::HAlign halign, LLFontGL::VAlign valign, + U8 style, S32 max_pixels, + F32* right_x, bool use_embedded, bool use_ellipses) const +{ + if (offset < 0 || offset >= length) // this condition includes length == 0. + { + // Shouldn't we set *right_x? FIXME. + return 0; + } + use_embedded &= hasEmbeddedChars(); + + LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS); + + // We need to itemize from the beginning of the paragraph to find + // the correct PangoAnalysis, especially bidi level. After + // itemized, we can discard items corresponding to the preceeding + // text. + + // By the way, what should we do if the offset is in a middle of a + // word consisting of a cursive script? Well, there are two + // typical cases: If we are filling an Arabic paragraph into + // lines, the previous line contained a long word that couldn't + // fit in the speified width, and a new line started in a middle + // of a word, then the first (rght most) character in the new line + // is rendered as the initial form, because it is the first + // character on the line. If an Arabic text is shown in a line, + // and a user selects several characters n a middle of a word, + // LLUI components (e.g., LLLineEditor) need to make a separate + // call to LLFontGL::render* function to draw the selected portion + // of the text in distinguished appearance, and in the case the + // first (right most) character in the selected portion should be + // rendered as the medial form. But, wait! Currently + // LLLineEditor draws selected text to the right of the part of + // the text logically before the selected portion. The worde in + // the second example above is never rendered appropriately... + // So, for the moment, we always follow the requirements of the + // first example. We will come back here in a future. + + ll_glyph_item_list_t list = itemize(text, length, use_embedded); + ll_glyph_item_list_t::iterator first; + for (first = list.begin(); first != list.end() && first->item->offset <= offset; first++) ; + --first; + if (first->item->offset < offset) + { + // The subtext to be rendered starts in a middle of an item. + // Discard the preceeding part. + const int split_bytes = offset - first->item->offset; + const int split_chars = g_utf8_strlen(text + first->item->offset, split_bytes); + pango_item_free(pango_item_split(first->item, split_bytes, split_chars)); + } + if (first != list.begin()) + { + std::for_each(list.begin(), first, LLGlyphItem::freeGlyphItem); + list.erase(list.begin(), first); + } + + // Fill the line. + + F32 display_width; + bool truncated; + if (S32_MAX == max_pixels) + { + if (use_ellipses) + { + llwarns << "use_ellipses doesn't work with unlimited max_pixels." << llendl; + use_ellipses = false; + } + fill(text, list, F32_MAX, 0, false, use_embedded, &display_width, NULL, NULL, NULL); + truncated = false; + } + else + { + const F32 ellipses_width = (use_ellipses ? mEllipsesWidth : 0); + const F32 scaled_max_width = (F32)max_pixels * sScaleX; + ll_glyph_item_list_t::iterator truncate_at; + truncated = fill(text, list, scaled_max_width, ellipses_width, false, use_embedded, &display_width, NULL, NULL, &truncate_at); + if (truncated) + { + std::for_each(truncate_at, list.end(), LLGlyphItem::freeGlyphItem); + list.erase(truncate_at, list.end()); + } + } + + // We have just truncated a long line (or a paragraph) at the + // specified with. So, this is the timing to reorder items for + // bidi. Implimentors generally assume that the reordering + // process doesn't change the display width. We follow it, too. + + reorder(list); + + // Take care of drop shadow. We can do it now, before, or later. + // We simply follow the similar location to the original LL codes. + + F32 drop_shadow_strength = 0.f; + if (style & (LLFontGL::DROP_SHADOW | LLFontGL::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 & ~(LLFontGL::DROP_SHADOW | LLFontGL::DROP_SHADOW_SOFT); + } + } + + // cur_x and cur_y tracks the display coordinates where glyph + // images are drawn. They are initialized to the caller-specified + // reference point, then is moved to the starting point in the + // drawn text at the baseline. + + F32 cur_x = (F32)x * sScaleX; + F32 cur_y = (F32)y * sScaleY; + + // Offset y by vertical alignment. + switch (valign) + { + case LLFontGL::TOP: + cur_y -= mAscender; + break; + case LLFontGL::BOTTOM: + cur_y += mDescender; + break; + case LLFontGL::VCENTER: + cur_y -= ((mAscender - mDescender)/2.f); + break; + case LLFontGL::BASELINE: + // Baseline, do nothing. + break; + default: + llwarns << "unknown VAlign: " << valign << llendl; + break; + } + + switch (halign) + { + case LLFontGL::LEFT: + break; + case LLFontGL::RIGHT: + cur_x -= display_width; + break; + case LLFontGL::HCENTER: + cur_x -= display_width / 2; + break; + default: + llwarns << "unknown HAlign: " << halign << llendl; + break; + } + + mFontManager->mGlyphImageManager->unbindGL(); + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + + gGL.pushMatrix(); + glLoadIdentity(); + gGL.translatef(floorf(LLFontGL::sCurOrigin.mX*sScaleX), floorf(LLFontGL::sCurOrigin.mY*sScaleY), LLFontGL::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); + + // gGL.color4fv(color.mV); + + // Not guaranteed to be set correctly + gGL.setSceneBlendType(LLRender::BT_ALPHA); + + const F32 start_x = cur_x; + + for (ll_glyph_item_list_t::iterator p = list.begin(); p != list.end(); p++) + { + const embedded_data_t *const ext_data = getEmbeddedCharData(text + p->item->offset, use_embedded); + if (ext_data) + { + mFontManager->mGlyphImageManager->unbindGL(); + + LLImageGL *const ext_image = ext_data->mImage; + + const F32 ext_height = (F32)ext_image->getHeight(); // * sScaleY; + const F32 ext_width = (F32)ext_image->getWidth(); // * sScaleX; + const F32 ext_advance = getEmbeddedCharAdvance(ext_data); + const F32 ext_x = cur_x + (ext_data->mXBearing * sScaleX); + const F32 ext_y = cur_y + (ext_data->mYBearing * sScaleY + mAscender - mLineHeight); + + gGL.getTexUnit(0)->bind(ext_image); + const LLRectf uv_rect(0.f, 1.f, 1.f, 0.f); // Entire texture. + const LLRectf screen_rect((F32)llround(ext_x), (F32)llround(ext_y) + ext_height, + (F32)llround(ext_x) + ext_width, (F32)llround(ext_y)); + drawGlyph(screen_rect, uv_rect, LLColor4::white, style, drop_shadow_strength); + + if (!ext_data->mLabel.empty()) + { + // Why we don't call renderInternal directly? Because + // the label (embedded_data_t::mLabel) is in + // LLWString. We could eventually fix it. + + //gGL.pushMatrix(); + //glLoadIdentity(); + //gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); + //glScalef(sScaleX, sScaleY, 1.f); + ext_data->mFont->render(ext_data->mLabel, 0, + ((ext_x + (F32)ext_image->getWidth() + ext_data->mXBearing) / sScaleX), + (cur_y / sScaleY), + color, halign); + //gGL.popMatrix(); + } + + // gGL.color4fv(color.mV); + cur_x += ext_advance; + } + else + { + const LLPangoGlyphImageInfo *info = mFontManager->mGlyphImageManager->lookupGlyphImage(*p); + if (NULL == info) + { + // Not cached. Need to render the glyph item into an image. + + PangoFont *const font = p->item->analysis.font; + PangoRectangle ink; + pango_glyph_string_extents(p->glyphs, font, &ink, NULL); + pango_extents_to_pixels(&ink, NULL); + F32 advance = (F32)pango_glyph_string_get_width(p->glyphs) / (F32)PANGO_SCALE; + + int image_stride; + unsigned char *image_data = renderGlyphImage(font, p->glyphs, ink, &image_stride); + info = mFontManager->mGlyphImageManager->addGlyphImage(image_data, image_stride, ink, advance); + mFontManager->mGlyphImageManager->cacheGlyphImage(*p, info); + delete [] image_data; + } + + // Pixel fit the glyph image, + S32 render_x = llround(cur_x) ; + S32 render_y = llround(cur_y); + cur_x += info->mAdvance; + + while (info && info->mWidth) + { + mFontManager->mGlyphImageManager->bindGL(info); + const LLRectf uv_rect(mFontManager->mGlyphImageManager->getInvertedTextureWidth() * (info->mXBitmapOffset - PAD_AMT), + mFontManager->mGlyphImageManager->getInvertedTextureHeight() * (info->mYBitmapOffset + info->mHeight + PAD_AMT), + mFontManager->mGlyphImageManager->getInvertedTextureWidth() * (info->mXBitmapOffset + info->mWidth + PAD_AMT), + mFontManager->mGlyphImageManager->getInvertedTextureHeight() * (info->mYBitmapOffset - PAD_AMT)); + const LLRectf screen_rect((F32)(render_x + info->mXBearing) - PAD_AMT, + (F32)(render_y + info->mYBearing) + PAD_AMT, + (F32)(render_x + info->mXBearing + info->mWidth) + PAD_AMT, + (F32)(render_y + info->mYBearing - info->mHeight) - PAD_AMT); + drawGlyph(screen_rect, uv_rect, color, style, drop_shadow_strength); + info = mFontManager->mGlyphImageManager->getNextGlyphImage(info); + } + } + } + + if (right_x) + { + *right_x = cur_x / sScaleX; + } + + if (style & LLFontGL::UNDERLINE) + { + mFontManager->mGlyphImageManager->unbindGL(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.begin(LLRender::LINES); + gGL.vertex2f(start_x, cur_y - (mDescender)); + gGL.vertex2f(start_x + display_width, cur_y - (mDescender)); + gGL.end(); + } + + gGL.popMatrix(); + + S32 chars_drawn = 0; + for (ll_glyph_item_list_t::iterator p = list.begin(); p != list.end(); p++) + { + chars_drawn += p->item->num_chars; + LLGlyphItem::freeGlyphItem(*p); + } + + if (truncated && use_ellipses) + { + // XXX: This can be a simple call to drawGlyph if we prepare a + // pre-rendered glyph image (but a raw string) of the + // ellipses. + renderInternal(sEllipses, 0, strlen(sEllipses), cur_x / sScaleX, cur_y / sScaleY, color, LEFT, BASELINE, style, S32_MAX, NULL, FALSE, FALSE); + } + + return chars_drawn; +} + +// Allocate a new raw byte buffer (an array of unsigned byte) through +// C++ new operator that has a sufficient size for the specified ink +// rectangle, render the glyph image onto it, and return the raw byte +// buffer. The raw byte buffer's stride (aka pitch) that may be +// larger than ink.width will be returned in the location pointed to +// by image_stride. Note that we always use top-to-bottom bitmap, +// i.e., the pointer returned by this function points to the upper +// left corner of the image, and the stride is positive. This design +// is primarily because font handling libraries usually assume this +// bitmap layout. However, LLImageRaw (and LLImageGL) assumes +// bottom-to-top layout. LLPangoGlyphImageManager::addGlyphImage +// takes care of it. +unsigned char *LLFontPangoGL::renderGlyphImage(PangoFont *font, PangoGlyphString *glyphs, const PangoRectangle &ink, int *image_stride) const +{ + // Allocate a temporary FreeType bitmap buffer and render the text + // into it. We need to clear the buffer before use, since pango + // doesn't fill untouched background pixels. Note that unlike + // LLImageRaw, our buffer is top-to-bottom. + + const int bitmap_buffer_size = ink.width * ink.height; + unsigned char * bitmap_buffer = new unsigned char[bitmap_buffer_size]; + memset(bitmap_buffer, 0, bitmap_buffer_size); + + FT_Bitmap bitmap; + memset(&bitmap, 0, sizeof bitmap); + bitmap.rows = ink.height; + bitmap.width = ink.width; + bitmap.pitch = ink.width; + bitmap.buffer = bitmap_buffer; + bitmap.num_grays = 256; + bitmap.pixel_mode = FT_PIXEL_MODE_GRAY; + + pango_ft2_render(&bitmap, font, glyphs, -ink.x, -ink.y); + + *image_stride = bitmap.pitch; + return bitmap_buffer; +} + +LLPangoGlyphImageCacheEntry::LLPangoGlyphImageCacheEntry(PangoFont *font, const PangoGlyphString *glyphs) +{ + // We delay g_object_ref(mFont) until retain(). Before + // retain(), an LLPangoGlyphImageCacheEntry object is considered + // temporary. + mFont = font; + mNumberOfGlyphs = glyphs->num_glyphs; + mGlyphInfo = glyphs->glyphs; + mGlyphImageInfoId = -1; +} + +void LLPangoGlyphImageCacheEntry::retain() +{ + g_object_ref(mFont); + PangoGlyphInfo *new_glyph_info = new PangoGlyphInfo[mNumberOfGlyphs]; + memcpy(new_glyph_info, mGlyphInfo, mNumberOfGlyphs * sizeof(PangoGlyphInfo)); + mGlyphInfo = new_glyph_info; +} + +bool operator<(const LLPangoGlyphImageCacheEntry &a, const LLPangoGlyphImageCacheEntry &b) +{ + if (a.mNumberOfGlyphs < b.mNumberOfGlyphs) + { + return true; + } + if (a.mNumberOfGlyphs > b.mNumberOfGlyphs) + { + return false; + } + PangoGlyphInfo const *p = a.mGlyphInfo, *q = b.mGlyphInfo; + for (int i = 0; i < a.mNumberOfGlyphs; i++) + { + if (p->glyph < q->glyph) + { + return true; + } + if (p->glyph > q->glyph) + { + return false; + } + // Should we compare other members of PangoGlyphInfo? FIXME. + p++, q++; + } + + if (a.mFont < b.mFont) + { + return true; + } + if (a.mFont > b.mFont) + { + return false; + } + + // a and b are same. Hence, not less. + return false; +} + +// static +void LLPangoGlyphImageCacheEntry::freeInfo(const LLPangoGlyphImageCacheEntry &a) +{ + delete[] a.mGlyphInfo; + g_object_unref(a.mFont); +} + +LLPangoGlyphImageManager::LLPangoGlyphImageManager() +{ + mNoCaching = false; + mInitialized = false; + initGLTexture(); +} + +LLPangoGlyphImageManager::~LLPangoGlyphImageManager() +{ + destroyGLTexture(); + mImageGLp = NULL; + mImageRawp = NULL; +} + +void LLPangoGlyphImageManager::destroyGLTexture() +{ + reset(); + if (!mImageGLp.isNull()) + { + mImageGLp->destroyGLTexture(); + } +} + +void LLPangoGlyphImageManager::initGLTexture() +{ + if (mImageRawp.isNull()) + { + mImageRawp = new LLImageRaw(); + mImageRawp->resize(TEXTURE_WIDTH, TEXTURE_HEIGHT, 1); + } +// mImageRawp->clear(0); + + if (mImageGLp.isNull()) + { + mImageGLp = new LLImageGL(FALSE); + } + if (!mImageGLp->getHasGLTexture()) + { + mImageGLp->setExplicitFormat(GL_ALPHA8, GL_ALPHA); + mImageGLp->createGLTexture(0, mImageRawp); + } + gGL.getTexUnit(0)->bind(mImageGLp); + mImageGLp->setMipFilterNearest(TRUE, TRUE); + + stop_glerror(); + + // Recalculate the inverted dimensions so that it survive the + // texture size extention. (NOT implemented yet.) + mInvertedTextureWidth = 1.f / (F32)mImageGLp->getWidth(); + mInvertedTextureHeight = 1.f / (F32)mImageGLp->getHeight(); + + reset(); +} + +const char LLFontManager::OPTION_NO_GLYPH_CACHE[] = "NO-GLYPH-CACHE"; + +void LLPangoGlyphImageManager::reset() +{ + // We don't want to reset the cache successively, because the + // associated logging will disturb the events around text engine + // replacement for example. + if (mGlyphImageInfoPool.empty() && mInitialized) + { + return; + } + + // For debugging. + mNoCaching = gFontManagerp->debugGetBoolOption(LLFontManager::OPTION_NO_GLYPH_CACHE); + +#if 1 // Log cache usage. + + // Collect the usage stats of glyph image cache and report them + // before resetting. We suppress it when the glyph caching is + // disabled, because this function is called very frequently in + // the case, and the overhead of logging can be significant. + if (!mInitialized) + { + llinfos << "initializing " + << mImageGLp->getWidth() << "x" << mImageGLp->getHeight() + << " glyph image cache." << llendl; + } + else if (!mNoCaching) + { + const F32 total = (F32)(mImageGLp->getHeight() * mImageGLp->getWidth()); + const F32 used = (F32)(mAllocateY * mImageGLp->getWidth() + (mMaxAllocatedHeight + 1) * mAllocateX); + const S32 unused_per_cents = llround((1.f - used / total) * 100.f); + const S32 wasted_per_cents = llround((F32)mWasted / total * 100.f); + llinfos << "resetting " + << mImageGLp->getWidth() << "x" << mImageGLp->getHeight() + << " glyph image cache occupied by " + << mGlyphImageInfoPool.size() << " rectangles with " + << unused_per_cents << "% unused and " + << wasted_per_cents << "% wasted space." << llendl; + } + +#endif + + // Perform all pending glyph drawing requests before the texture + // is invalidated. + gGL.flush(); + + // Discard all cached entries. + std::for_each(mGlyphImageCache.begin(), mGlyphImageCache.end(), LLPangoGlyphImageCacheEntry::freeInfo); + mGlyphImageCache.clear(); + mGlyphImageInfoPool.clear(); + + // Lower left corner is reserved for an empty glyph (i.e., a glyph + // from a single SPACE character.) It has 0x0 dimension but it + // needs a one pixel margin around it as ordinary glyph images. + mAllocateX = 2; + mAllocateY = 1; + mMaxAllocatedHeight = 1; + mWasted = 0; + + mActiveImageGL = NULL; + + mInitialized = true; +} + +const LLPangoGlyphImageInfo *LLPangoGlyphImageManager::lookupGlyphImage(const PangoGlyphItem &glyph_item) const +{ + if (mNoCaching) + { + return NULL; + } + + LLPangoGlyphImageCacheEntry entry(glyph_item.item->analysis.font, glyph_item.glyphs); + ll_glyph_image_cache_t::const_iterator f = mGlyphImageCache.find(entry); + if (mGlyphImageCache.end() == f) + { + return NULL; + } + return &mGlyphImageInfoPool[f->mGlyphImageInfoId]; +} + +void LLPangoGlyphImageManager::cacheGlyphImage(const PangoGlyphItem &glyph_item, const LLPangoGlyphImageInfo *info) +{ + if (mNoCaching) + { + return; + } + + LLPangoGlyphImageCacheEntry entry(glyph_item.item->analysis.font, glyph_item.glyphs); + llassert(info >= &mGlyphImageInfoPool.front() && info <= &mGlyphImageInfoPool.back()); + entry.mGlyphImageInfoId = info - &mGlyphImageInfoPool[0]; + entry.retain(); + mGlyphImageCache.insert(entry); +} + +static const S32 ROW_USE_FACTOR = 20; // Determined by experiences. + +// Find a series of rectangular areas in the free spaces on the +// caching image that collectively cover the rectangle of specified +// width and height. Rectangular areas are allocated with one pixel +// margines around them. The allocated rectangular areas are returned +// in the contiguous elements in mGlyphImageInfoPool. The index to +// the first element is returned. The last element in the series is +// identified by its mIsLast set to TRUE. + +S32 LLPangoGlyphImageManager::allocateRects(S32 width, S32 height) +{ + if (mNoCaching) + { + reset(); + } + + // We are solving a variation of online two dimensional packing + // problem. Unlike most variations, we can break given rectangles + // into several sub pieces, but breaking into too many pieces may + // not be optimal since we need one texel margin around the + // pieces. Also, each piece require its own GL primitive to draw, + // and breaking may adds more graphics overhead. + // + // Anyway, in this version, we use a straight forward algorithm. + // We break a given rectangle only horizontally and lay those + // strips into the cache left to right, bottom to top, in a + // row-oriented approach. This algorithm works fine enough for + // our purpose, since our rectangles often have very large widths + // with small uniform heights. + + again: + const S32 first_index = mGlyphImageInfoPool.size(); + + if (width == 0 || height == 0) + { + // Some Unicode character, such as SPACE (0x20) has invisible + // graphics associated with it. If a string consisting only + // of such characters is given to pango, it forms an empty ink + // extents. We need to allocate a _rectangle_ even in that + // case, since otherwise, the glyph image caching code + // misbehaves. Let's take care of it. + + LLPangoGlyphImageInfo info; + info.mXBitmapOffset = 1; + info.mYBitmapOffset = 1; + info.mWidth = 0; + info.mHeight = 0; + info.mXBearing = 0; + info.mYBearing = 0; + info.mAdvance = 0.f; + info.mIsLast = TRUE; + mGlyphImageInfoPool.push_back(info); + return first_index; + } + + for (S32 strip_offset = 0, strip_width; strip_offset < width; strip_offset += strip_width) + { + const S32 free_width = mImageGLp->getWidth() - mAllocateX - 1; + + // We have three criteria to use the free space to the right + // of the current allocation row. (1) The requested width is + // less than or equal to the free width. (2) Putting the + // first part of the requested width does not increase the + // required number of strips. (3) The free width is too wide + // to just waste. The condition in the following if statement + // is an opposite to the above criteria. That is, the + // condition we don't use the free width. Note that we don't + // take the height of the requested rectangle into account, so + // filling as much space to the right as possible may not + // cause the maximum usage... + + if (free_width < mImageGLp->getWidth() / ROW_USE_FACTOR + && (width - strip_offset) % (mImageGLp->getWidth() - 2) + 1 >= free_width) + { + mWasted += free_width * (mMaxAllocatedHeight + 1); + mAllocateX = 1; + mAllocateY += mMaxAllocatedHeight + 1; + mMaxAllocatedHeight = height; + } + if (mAllocateY + height + 1 >= mImageGLp->getHeight()) + { + // No enough room to allocate the requested area. We + // flush all the cached glyph images and try again. We + // could do better by dividing the cache into several + // blocks or introducing LRU mechanism, but this simple + // algorith works fine for our purpose, as LL's original + // implementation for FreeType did. + + if (first_index == 0) + { + // OOPS! We used up entire image even if we started + // from the empty cahce. What happened? One + // possibility is that the caller requested huge area + // that we can't hold. More likely, the caching code + // has a bug... Anyway we need to avoid infinite loop. + // First we try to reduce the size of the rectangle. If + // it doesn't work, we give up. + + // We have margine pixels on both ends and we reserve + // a 0 by 0 rectangle for an empty glyph image at + // (1,1). So, the maximum rectangle we can hold is (w + // - 3) by (h - 2) where w and h are the width and + // height of the texture. Yes, we can hold many + // rectangles that is wider than it or taller than it + // (but not both). Never mind. This is just a safe + // guard and will never be executed in an actual run. + // (Unless you have a strange font.) + + llwarns << "Too large rectangle for a glyph image: " << width << "x" << height << llendl; + if (width <= mImageGLp->getWidth() - 3 && height < mImageGLp->getHeight() - 2) + { + llwarns << "A possibly infinite loop condition is detected." << llendl; + width = 0; + height = 0; + } + if (width > mImageGLp->getWidth() - 3) + { + width = mImageGLp->getWidth() - 3; + } + if (height > mImageGLp->getHeight() - 2) + { + height = mImageGLp->getHeight() - 2; + } + } + reset(); + goto again; + } + strip_width = llmin(mImageGLp->getWidth() - mAllocateX - 1, width - strip_offset); + + LLPangoGlyphImageInfo info; + info.mXBitmapOffset = mAllocateX; + info.mYBitmapOffset = mAllocateY; + info.mWidth = strip_width; + info.mHeight = height; + info.mXBearing = strip_offset; + info.mYBearing = 0; + info.mAdvance = 0.f; + info.mIsLast = FALSE; + mGlyphImageInfoPool.push_back(info); + + if (height < mMaxAllocatedHeight) + { + mWasted += (mMaxAllocatedHeight - height) * (strip_width + 1); + } + else + { + mWasted += (height - mMaxAllocatedHeight) * mAllocateX; + mMaxAllocatedHeight = height; + } + mAllocateX += strip_width + 1; + } + + // Mark the last one as the last one and return the first index. + mGlyphImageInfoPool.back().mIsLast = TRUE; + return first_index; +} + +const LLPangoGlyphImageInfo *LLPangoGlyphImageManager::addGlyphImage(const unsigned char *image_data, S32 image_stride, PangoRectangle &ink, F32 advance) +{ + // Allocate a (series of) rectangles on the cache plane. + const S32 index = allocateRects(ink.width, ink.height); + + llassert(mImageRawp->getComponents() == 1); + + const S32 stride = mImageRawp->getWidth(); + + for (std::vector::iterator info = mGlyphImageInfoPool.begin() + index; info != mGlyphImageInfoPool.end(); info++) + { + // Copy a portion of glyph image bitmap into one of the + // allocated rectangular area. Our FreeType bitmap buffer has + // the origin at the upper left corner, and the LLImageRaw has + // the origin at the lower left corner. We copy rows top to + // bottom. X and Y bearings pointed to by info assumes the + // reference point is at the upper right corner of the whole + // rectangle. (We adjust them later.) + + // Set up both src and dst to point to the upper left corner + // of the rectangular areas to copy. + + const unsigned char *src = image_data - info->mYBearing * image_stride + info->mXBearing; + U8 *dst = mImageRawp->getData() + (info->mYBitmapOffset + info->mHeight - 1) * stride + info->mXBitmapOffset; + + // Move the data. + + memset(dst + stride - 1, 0, info->mWidth + 2); // Clear the top margin. + for (int i = 0; i < info->mHeight; i++) + { + dst[-1] = 0; // Clear the left margin of this raw. + memcpy(dst, src, info->mWidth); + dst[info->mWidth] = 0; // Clear the right margin of this raw. + dst -= stride; + src += image_stride; + } + memset(dst - 1, 0, info->mWidth + 2); // Clear the bottom margin. + + // ... and reflect it to the GL texture. + + mImageGLp->setSubImage(mImageRawp, info->mXBitmapOffset - 1, info->mYBitmapOffset - 1, info->mWidth + 2, info->mHeight + 2); + + // allocateRects set the bearing values so that the reference + // point is at the upper left corner of the requested + // rectangle. Adjust it so that the reference point matches + // with the one passed from pango. + + info->mXBearing += ink.x; + info->mYBearing -= ink.y; + } + + // The advance value is stored only in the first info. Following + // info has an advance value of zero. It is easier to distribute + // the advance value to all the infos. + mGlyphImageInfoPool[index].mAdvance = advance; + + return &mGlyphImageInfoPool[index]; +} + +const LLPangoGlyphImageInfo *LLPangoGlyphImageManager::getNextGlyphImage(const LLPangoGlyphImageInfo *info) const +{ + if (info->mIsLast) + { + return NULL; + } + return info + 1; +} + +// The bindGL and unbindGL interface are designed with such future +// possibility in mind that we allocate multiple GL textures for glyph +// image cache, and each LLPangoGlyphImageInfo points to the texture +// where the cached glyph image is on. Currently, we have only one +// texture. + +void LLPangoGlyphImageManager::bindGL(const LLPangoGlyphImageInfo *info) +{ + if (!mActiveImageGL) + { + gGL.getTexUnit(0)->bind(mImageGLp); + mActiveImageGL = mImageGLp; + } +} + +void LLPangoGlyphImageManager::unbindGL() +{ + mActiveImageGL = NULL; +} + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llrender/llfontpangogl.h b/indra/llrender/llfontpangogl.h new file mode 100644 index 0000000..f63b6d6 --- /dev/null +++ b/indra/llrender/llfontpangogl.h @@ -0,0 +1,169 @@ +/** + * @file llfontpangogl.h + * @brief pango-over-freetype based text renderer + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFONTPANGOGL_H +#define LL_LLFONTPANGOGL_H + +#include "llfontmanager.h" +#include "llfontgl.h" +#include "llimagegl.h" +#include "v2math.h" +#include "llcoord.h" +#include "llrect.h" + +#include + +struct LLPangoGlyphImageInfo; +class LLPangoGlyphImageManager; + +class LLFontManagerPangoGL : public LLFontManager +{ +public: + LLFontManagerPangoGL(); + ~LLFontManagerPangoGL(); + /*virtual*/ void reset(); + /*virtual*/ void setDPIs(F32 horiz_dpi, F32 vert_dpi); + /*virtual*/ void setLanguage(const std::string &language); + /*virtual*/ LLFontGL *createFont(const std::string &face, F32 size, bool console); +protected: + PangoAttrList *const mEmptyPangoAttrList; + LLPangoGlyphImageManager *const mGlyphImageManager; + PangoFontMap *mFontMap; + PangoLanguage *mPangoLanguage; + + friend class LLFontPangoGL; +}; + +class LLFontPangoGL : public LLFontGL +{ +public: + + LLFontPangoGL(const LLFontManagerPangoGL *manager, PangoContext *context); + ~LLFontPangoGL(); + + /*virtual*/ void reset(); + /*virtual*/ void destroyGLTexture(); + + void loadFace(const std::string &face, F32 point_size, bool console); + + /*virtual*/ S32 renderUTF8(const std::string &utf8text, S32 begin_offset, + F32 x, F32 y, const LLColor4 &color, + HAlign halign, VAlign valign, U8 style, + S32 max_chars, S32 max_pixels, F32 *right_x, + BOOL use_embedded, BOOL use_ellipses) const; + + /*virtual*/ S32 render(const LLWString &text, S32 begin_offset, + F32 x, F32 y, const LLColor4 &color, + HAlign halign, VAlign valign, U8 style, + S32 max_chars, S32 max_pixels, F32 *right_x, + BOOL use_embedded, BOOL use_ellipses) const; + + /*virtual*/ F32 getLineHeight() const { return (F32)llround(mLineHeight / sScaleY); } + /*virtual*/ F32 getAscenderHeight() const { return (F32)llround(mAscender / sScaleY); } + /*virtual*/ F32 getDescenderHeight() const { return (F32)llround(mDescender / sScaleY); } + + /*virtual*/ F32 getWidthF32(const std::string& text, S32 offset = 0, S32 max_chars = S32_MAX, BOOL use_embedded = FALSE) const; + /*virtual*/ F32 getWidthF32(const llwchar* wchars, S32 offset = 0, S32 max_chars = S32_MAX, BOOL use_embedded = FALSE) const; + + // The following are called often, frequently with large buffers, so do not use a string interface + + // Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels + /*virtual*/ S32 maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, + BOOL end_on_word_boundary, const BOOL use_embedded, + F32* drawn_pixels) const; + + // Returns the index of the first complete characters from text that can be drawn in max_pixels + // starting on the right side (at character start_pos). + /*virtual*/ S32 firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const; + + // Returns the index of the character closest to pixel position x (ignoring text to the right of max_pixels and max_chars) + /*virtual*/ S32 charFromPixelOffset(const llwchar* wchars, const S32 char_offset, + F32 x, F32 max_pixels, S32 max_chars, + BOOL round, BOOL use_embedded) const; + +protected: + + struct LLGlyphItem : PangoGlyphItem + { + LLGlyphItem(PangoItem *item = NULL, PangoGlyphString *glyphs = NULL) + { + this->item = item; + this->glyphs = glyphs; + } + static void freeGlyphItem(LLGlyphItem &glyph_item); + }; + typedef std::list ll_glyph_item_list_t; + + ll_glyph_item_list_t itemize(const gchar *text, glong length, bool use_embedded) const; + bool fill(const gchar *text, ll_glyph_item_list_t &list, + F32 target_width, F32 truncation_margin, bool natural_breaking, bool use_embedded, + F32 *width, glong *bytes, gint *chars, ll_glyph_item_list_t::iterator *next_glyph_item) const; + void reorder(ll_glyph_item_list_t &list) const; + + F32 getWidthInternal(const gchar *text, S32 length, BOOL use_embedded) const; + S32 renderInternal(const gchar *text, glong offset, glong length, + F32 x, F32 y, const LLColor4 &color, + LLFontGL::HAlign halign, LLFontGL::VAlign valign, + U8 style, S32 max_pixels, + F32* right_x, bool use_embedded, bool use_ellipses) const; + + virtual unsigned char *renderGlyphImage(PangoFont *font, PangoGlyphString *glyphs, const PangoRectangle &ink, int *image_stride) const; + +private: + + static bool isEmbeddedChar(const gchar *string); + const embedded_data_t *getEmbeddedCharData(const gchar *string, bool use_embedded) const; + +protected: + + const LLFontManagerPangoGL *const mFontManager; + PangoContext *const mPangoContext; + F32 mAscender; + F32 mDescender; + F32 mLineHeight; + F32 mEllipsesWidth; + bool mTweakDigits; + +protected: + + static const char sEllipses[]; + + friend class LLFontManagerPangoGL; +}; + + +#endif + +// +// Local Variables: +// mode: C++ +// tab-width: 4 +// End: diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 6842ac7..e47ff0e 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -48,7 +48,6 @@ #include "llmath.h" #include "llrender.h" #include "llfocusmgr.h" -#include "llfont.h" #include "llcoord.h" #include "llwindow.h" #include "llcriticaldamp.h" diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index 8dc1a40..da53fe2 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -34,7 +34,6 @@ #include "v4color.h" #include "llresmgr.h" -#include "llfont.h" #include "llui.h" class LLStyle : public LLRefCount diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index ef0d77b..6be3a12 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -1892,11 +1892,12 @@ void LLTextEditor::paste() if( mAllowEmbeddedItems ) { const llwchar LF = 10; + const llwchar FIRST_CHAR = 32; S32 len = clean_string.length(); for( S32 i = 0; i < len; i++ ) { llwchar wc = clean_string[i]; - if( (wc < LLFont::FIRST_CHAR) && (wc != LF) ) + if( (wc < FIRST_CHAR) && (wc != LF) ) { clean_string[i] = LL_UNKNOWN_CHAR; } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 6078c9f..1f23aca 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1468,6 +1468,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLMESSAGE_LIBRARIES} ${LLPRIMITIVE_LIBRARIES} ${LLRENDER_LIBRARIES} + ${PANGO_LIBRARIES} ${FREETYPE_LIBRARIES} ${LLUI_LIBRARIES} ${LLVFS_LIBRARIES} diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 1006773..1fe93fe 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -46,6 +46,7 @@ #include "llfeaturemanager.h" #include "llfocusmgr.h" #include "llfontgl.h" +#include "llfontmanager.h" #include "llinstantmessage.h" #include "llpermissionsflags.h" #include "llrect.h" @@ -217,6 +218,7 @@ void init_debug_world_menu(LLMenuGL* menu); void init_debug_rendering_menu(LLMenuGL* menu); void init_debug_ui_menu(LLMenuGL* menu); void init_debug_xui_menu(LLMenuGL* menu); +void init_debug_text_menu(LLMenuGL* menu); void init_debug_avatar_menu(LLMenuGL* menu); void init_debug_baked_texture_menu(LLMenuGL* menu); @@ -837,6 +839,10 @@ void init_client_menu(LLMenuGL* menu) init_debug_xui_menu(sub_menu); menu->appendMenu(sub_menu); + sub_menu = new LLMenuGL("Text"); + init_debug_text_menu(sub_menu); + menu->appendMenu(sub_menu); + sub_menu = new LLMenuGL("Character"); init_debug_avatar_menu(sub_menu); menu->appendMenu(sub_menu); @@ -1308,6 +1314,132 @@ void init_debug_rendering_menu(LLMenuGL* menu) menu->createJumpKeys(); } +#if 1 // Alissa Sabre + +static void debug_text_refresh(void *data) +{ + gViewerWindow->refreshFont(); +} + +static void debug_text_renderer_select(void *data) +{ + LLFontManager::debugSelectTextRenderer(data, debug_text_refresh, NULL); +} + +void debug_text_set_option_to_true(void *option) +{ + LLFontManager::debugSetOption((const char *)option, LLFontManager::OPTION_TRUE); + debug_text_refresh(NULL); +} + +void debug_text_set_option_to_false(void *option) +{ + LLFontManager::debugSetOption((const char *)option, LLFontManager::OPTION_FALSE); + debug_text_refresh(NULL); +} + +void debug_text_unset_option(void *option) +{ + LLFontManager::debugSetOption((const char *)option, NULL); + debug_text_refresh(NULL); +} + +static void debug_text_toggle_bool_option(void *option) +{ + char const *value = NULL; + LLFontManager::debugFindOption((const char *)option, &value); + if (value == LLFontManager::OPTION_TRUE) + { + debug_text_unset_option(option); + } + else + { + debug_text_set_option_to_true(option); + } +} + +static BOOL debug_text_is_option_set_to_true(void *option) +{ + return LLFontManager::debugOptionValueCallback((const char *)option, LLFontManager::OPTION_TRUE); +} + +static BOOL debug_text_is_option_set_to_false(void *option) +{ + return LLFontManager::debugOptionValueCallback((const char *)option, LLFontManager::OPTION_FALSE); +} + +static BOOL debug_text_is_option_unset(void *option) +{ + return LLFontManager::debugOptionValueCallback((const char *)option, NULL); +} + +//static BOOL debug_text_is_renderer_pango(void *) +//{ +// return LLFontManager::debugTextRendererInUseCallback(LLFontManager::PANGO); +//} + +static void debug_text_menu_engine_option(LLMenuGL *menu, const std::string &label, void *data) +{ + menu->append(new LLMenuItemCheckGL(label, + debug_text_renderer_select, + NULL, + LLFontManager::debugTextRendererInUseCallback, + data)); +} + +static void debug_text_menu_triway_option(LLMenuGL *menu, const std::string &label, const char *option, + const std::string enable_label = "Force Enabled", + const std::string disable_label = "Force Disabled", + const std::string default_label = "Follow Default") +{ + LLMenuGL *sub_menu = new LLMenuGL(label); + void *const data = const_cast(option); + sub_menu->append(new LLMenuItemCheckGL(enable_label, debug_text_set_option_to_true, NULL, debug_text_is_option_set_to_true, data)); + sub_menu->append(new LLMenuItemCheckGL(disable_label, debug_text_set_option_to_false, NULL, debug_text_is_option_set_to_false, data)); + sub_menu->append(new LLMenuItemCheckGL(default_label, debug_text_unset_option, NULL, debug_text_is_option_unset, data)); + menu->appendMenu(sub_menu); +} + +static void debug_text_menu_toggle_option(LLMenuGL *menu, const std::string &label, const char *option, enabled_callback enabled = NULL) +{ + menu->append(new LLMenuItemCheckGL(label, + debug_text_toggle_bool_option, + enabled, + debug_text_is_option_set_to_true, + (void *)option)); +} + +void init_debug_text_menu(LLMenuGL *menu) +{ + debug_text_menu_engine_option(menu, "Use FreeType", LLFontManager::FREETYPE); + debug_text_menu_engine_option(menu, "Use Pango + FreeType", LLFontManager::PANGO); + debug_text_menu_engine_option(menu, "Use Pango + Cairo", LLFontManager::PANGOCAIRO); + + menu->appendSeparator(); + + debug_text_menu_triway_option(menu, "Antialias", LLFontManager::OPTION_ANTIALIAS); + debug_text_menu_triway_option(menu, "Hinting", LLFontManager::OPTION_HINTING); + debug_text_menu_triway_option(menu, "Auto Hint", LLFontManager::OPTION_AUTOHINT); + + debug_text_menu_toggle_option(menu, "Adjust Auto Hint Default", LLFontManager::OPTION_AUTOHINT_DEFAULT); + debug_text_menu_toggle_option(menu, "Substitute Font Names", LLFontManager::OPTION_SUBSTITUTE_FONTNAMES); + debug_text_menu_toggle_option(menu, "Tweak Monospace Digits", LLFontManager::OPTION_TWEAK_MONOSPACE); + debug_text_menu_toggle_option(menu, "Tweak All Digits", LLFontManager::OPTION_TWEAK_ALL); + debug_text_menu_toggle_option(menu, "Disable Glyph Caching", LLFontManager::OPTION_NO_GLYPH_CACHE); + + debug_text_menu_triway_option(menu, "Base Direction", LLFontManager::OPTION_RTL, "Right to Left", "Left to Right", "Guess"); + + menu->appendSeparator(); + + debug_text_menu_toggle_option(menu, "Show Glyph Rectangles", LLFontManager::OPTION_GLYPH_RECT); + + menu->appendSeparator(); + + menu->append(new LLMenuItemCallGL("Reload Font", debug_text_refresh, NULL)); +} + +#endif // Alissa Sabre + extern BOOL gDebugAvatarRotation; void init_debug_avatar_menu(LLMenuGL* menu) diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index b60ea1d..2612574 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -50,6 +50,7 @@ #include "llrender.h" #include "llvoiceclient.h" // for push-to-talk button handling +#include "llfontmanager.h" // @@ -4696,7 +4697,8 @@ void LLViewerWindow::initFonts(F32 zoom_factor) gSavedSettings.getF32("FontSizeHuge"), gSavedSettings.getString("FontSansSerifBold"), gSavedSettings.getF32("FontSizeMedium"), - gDirUtilp->getAppRODataDir() + gDirUtilp->getAppRODataDir(), + LLUI::getLanguage() ); } void LLViewerWindow::toggleFullscreen(BOOL show_progress) @@ -5034,6 +5036,21 @@ S32 LLViewerWindow::getChatConsoleBottomPad() return offset; } +#if 1 // Alissa +void LLViewerWindow::refreshFont() +{ + initFonts(); + + LLView::sForceReshape = TRUE; + mRootView->reshape( + llceil((F32)mWindowRect.getWidth() / mDisplayScale.mV[VX]), + llceil((F32)mWindowRect.getHeight() / mDisplayScale.mV[VY])); + LLView::sForceReshape = FALSE; + + LLHUDText::reshape(); +} +#endif // Alissa + //---------------------------------------------------------------------------- // static diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index 510df9f..74d7777 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -414,6 +414,8 @@ public: void drawPickBuffer() const; + void refreshFont(); + LLAlertDialog* alertXml(const std::string& xml_filename, LLAlertDialog::alert_callback_t callback = NULL, void* user_data = NULL); LLAlertDialog* alertXml(const std::string& xml_filename, const LLStringUtil::format_map_t& args,