F32 LLFontGL::getWidthF32(const llwchar* wchars, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const
{
	if (!wchars || !wchars[0] || max_chars == 0)
	{
		return 0;
	}

	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 && wchars[i] != 0; i++)
	{
		llwchar wch = wchars[i];
		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);
	}

	if (cur_x == 0)
	{
		return cur_x;
	}

	return cur_x / sScaleX;
}
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);
		F32 char_width = ext_data ? getEmbeddedCharAdvance(ext_data) : 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 += ext_data ? (EXT_KERNING * sScaleX) : getXKerning(wchars[i-1], wch);
		}

		// Round after kerning.
		total_width = llround(total_width);
	}

	return start_pos - 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;
}
// 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::render(const LLWString &wstr, 
					 const S32 begin_offset,
					 const F32 x, const F32 y,
					 const LLColor4 &color,
					 const HAlign halign, const VAlign valign,
					 U8 style,
					 const S32 max_chars, S32 max_pixels,
					 F32* right_x,
					 BOOL use_embedded,
					 BOOL use_ellipses) const
{
	if(!sDisplayFont) //do not display texts
	{
		return wstr.length() ;
	}

	if (wstr.empty())
	{
		return 0;
	} 

	gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE);

	S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX);

	// Strip off any style bits that are already accounted for by the font.
	style = style & (~getFontDesc().getStyle());

	F32 drop_shadow_strength = 0.f;
	if (style & (DROP_SHADOW | DROP_SHADOW_SOFT))
	{
		F32 luminance;
		color.calcHSL(NULL, NULL, &luminance);
		drop_shadow_strength = clamp_rescale(luminance, 0.35f, 0.6f, 0.f, 1.f);
		if (luminance < 0.35f)
		{
			style = style & ~(DROP_SHADOW | DROP_SHADOW_SOFT);
		}
	}

	gGL.pushMatrix();
	glLoadIdentity();
	gGL.translatef(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ);

	// this code snaps the text origin to a pixel grid to start with
	F32 pixel_offset_x = llround((F32)sCurOrigin.mX) - (sCurOrigin.mX);
	F32 pixel_offset_y = llround((F32)sCurOrigin.mY) - (sCurOrigin.mY);
	gGL.translatef(-pixel_offset_x, -pixel_offset_y, 0.f);

	LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS);

	gGL.color4fv( color.mV );

	S32 chars_drawn = 0;
	S32 i;
	S32 length;

	if (-1 == max_chars)
	{
		length = (S32)wstr.length() - begin_offset;
	}
	else
	{
		length = llmin((S32)wstr.length() - begin_offset, max_chars );
	}

	F32 cur_x, cur_y, cur_render_x, cur_render_y;

 	// Not guaranteed to be set correctly
	gGL.setSceneBlendType(LLRender::BT_ALPHA);
	
	cur_x = ((F32)x * sScaleX);
	cur_y = ((F32)y * sScaleY);

	// Offset y by vertical alignment.
	switch (valign)
	{
	case TOP:
		cur_y -= mAscender;
		break;
	case BOTTOM:
		cur_y += mDescender;
		break;
	case VCENTER:
		cur_y -= ((mAscender - mDescender)/2.f);
		break;
	case BASELINE:
		// Baseline, do nothing.
		break;
	default:
		break;
	}

	switch (halign)
	{
	case LEFT:
		break;
	case RIGHT:
	  	cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX));
		break;
	case HCENTER:
	    cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2;
		break;
	default:
		break;
	}

	cur_render_y = cur_y;
	cur_render_x = cur_x;

	F32 start_x = cur_x;

	F32 inv_width = 1.f / mFontBitmapCachep->getBitmapWidth();
	F32 inv_height = 1.f / mFontBitmapCachep->getBitmapHeight();

	const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL;


	BOOL draw_ellipses = FALSE;
	if (use_ellipses && halign == LEFT)
	{
		// check for too long of a string
		if (getWidthF32(wstr.c_str(), 0, max_chars) * sScaleX > scaled_max_pixels)
		{
			// use four dots for ellipsis width to generate padding
			const LLWString dots(utf8str_to_wstring(std::string("....")));
			scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str())));
			draw_ellipses = TRUE;
		}
	}


	// Remember last-used texture to avoid unnecesssary bind calls.
	LLImageGL *last_bound_texture = NULL;

	for (i = begin_offset; i < begin_offset + length; i++)
	{
		llwchar wch = wstr[i];

		// Handle embedded characters first, if they're enabled.
		// Embedded characters are a hack for notecards
		const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL;
		if (ext_data)
		{
			LLImageGL* ext_image = ext_data->mImage;
			const LLWString& label = ext_data->mLabel;

			F32 ext_height = (F32)ext_image->getHeight() * sScaleY;

			F32 ext_width = (F32)ext_image->getWidth() * sScaleX;
			F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width;

			if (!label.empty())
			{
				ext_advance += (EXT_X_BEARING + getFontExtChar()->getWidthF32( label.c_str() )) * sScaleX;
			}

			if (start_x + scaled_max_pixels < cur_x + ext_advance)
			{
				// Not enough room for this character.
				break;
			}

			if (last_bound_texture != ext_image)
			{
				gGL.getTexUnit(0)->bind(ext_image);
				last_bound_texture = ext_image;
			}

			// snap origin to whole screen pixel
			const F32 ext_x = (F32)llround(cur_render_x + (EXT_X_BEARING * sScaleX));
			const F32 ext_y = (F32)llround(cur_render_y + (EXT_Y_BEARING * sScaleY + mAscender - mLineHeight));

			LLRectf uv_rect(0.f, 1.f, 1.f, 0.f);
			LLRectf screen_rect(ext_x, ext_y + ext_height, ext_x + ext_width, ext_y);
			drawGlyph(screen_rect, uv_rect, LLColor4::white, style, drop_shadow_strength);

			if (!label.empty())
			{
				gGL.pushMatrix();
				//glLoadIdentity();
				//gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f);
				//glScalef(sScaleX, sScaleY, 1.f);
				getFontExtChar()->render(label, 0,
									 /*llfloor*/((ext_x + (F32)ext_image->getWidth() + EXT_X_BEARING) / sScaleX), 
									 /*llfloor*/(cur_y / sScaleY),
									 color,
									 halign, BASELINE, NORMAL, S32_MAX, S32_MAX, NULL,
									 TRUE );
				gGL.popMatrix();
			}

			gGL.color4fv(color.mV);

			chars_drawn++;
			cur_x += ext_advance;
			if (((i + 1) < length) && wstr[i+1])
			{
				cur_x += EXT_KERNING * sScaleX;
			}
			cur_render_x = cur_x;
		}
		else
		{
			if (!hasGlyph(wch))
			{
				addChar(wch);
			}

			const LLFontGlyphInfo* fgi= getGlyphInfo(wch);
			if (!fgi)
			{
				llerrs << "Missing Glyph Info" << llendl;
				break;
			}
			// Per-glyph bitmap texture.
			LLImageGL *image_gl = mFontBitmapCachep->getImageGL(fgi->mBitmapNum);
			if (last_bound_texture != image_gl)
			{
				gGL.getTexUnit(0)->bind(image_gl);
				last_bound_texture = image_gl;
			}

			if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth))
			{
				// Not enough room for this character.
				break;
			}

			// Draw the text at the appropriate location
			//Specify vertices and texture coordinates
			LLRectf uv_rect((fgi->mXBitmapOffset) * inv_width,
					(fgi->mYBitmapOffset + fgi->mHeight + PAD_UVY) * inv_height,
					(fgi->mXBitmapOffset + fgi->mWidth) * inv_width,
					(fgi->mYBitmapOffset - PAD_UVY) * inv_height);
			// snap glyph origin to whole screen pixel
			LLRectf screen_rect(llround(cur_render_x + (F32)fgi->mXBearing),
					    llround(cur_render_y + (F32)fgi->mYBearing),
					    llround(cur_render_x + (F32)fgi->mXBearing) + (F32)fgi->mWidth,
					    llround(cur_render_y + (F32)fgi->mYBearing) - (F32)fgi->mHeight);
			
			drawGlyph(screen_rect, uv_rect, color, style, drop_shadow_strength);

			chars_drawn++;
			cur_x += fgi->mXAdvance;
			cur_y += fgi->mYAdvance;

			llwchar next_char = wstr[i+1];
			if (next_char && (next_char < LAST_CHARACTER))
			{
				// Kern this puppy.
				if (!hasGlyph(next_char))
				{
					addChar(next_char);
				}
				cur_x += getXKerning(wch, next_char);
			}

			// Round after kerning.
			// Must do this to cur_x, not just to cur_render_x, otherwise you
			// will squish sub-pixel kerned characters too close together.
			// For example, "CCCCC" looks bad.
			cur_x = (F32)llfloor(cur_x + 0.5f);
			//cur_y = (F32)llfloor(cur_y + 0.5f);

			cur_render_x = cur_x;
			cur_render_y = cur_y;
		}
	}

	if (right_x)
	{
		*right_x = cur_x / sScaleX;
	}

	if (style & UNDERLINE)
	{
		gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
		gGL.begin(LLRender::LINES);
		gGL.vertex2f(start_x, cur_y - (mDescender));
		gGL.vertex2f(cur_x, cur_y - (mDescender));
		gGL.end();
	}

	// *FIX: get this working in all alignment cases, etc.
	if (draw_ellipses)
	{
		// recursively render ellipses at end of string
		// we've already reserved enough room
		gGL.pushMatrix();
		//glLoadIdentity();
		//gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f);
		//glScalef(sScaleX, sScaleY, 1.f);
		renderUTF8(std::string("..."), 
				0,
				cur_x / sScaleX, (F32)y,
				color,
				LEFT, valign,
				style,
				S32_MAX, max_pixels,
				right_x,
				FALSE); 
		gGL.popMatrix();
	}

	gGL.popMatrix();

	return chars_drawn;
}
Exemple #6
0
S32 LLFontGL::render(const LLWString &wstr, 
					 const S32 begin_offset,
					 const F32 x, const F32 y,
					 const LLColor4 &color,
					 const HAlign halign, const VAlign valign,
					 U8 style,
					 const S32 max_chars, S32 max_pixels,
					 F32* right_x,
					 BOOL use_embedded,
					 BOOL use_ellipses) const
{
	if(!sDisplayFont) //do not display texts
	{
		return wstr.length() ;
	}

	LLGLEnable tex(GL_TEXTURE_2D);

	if (wstr.empty())
	{
		return 0;
	} 

	S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX);

	// HACK for better bolding
	if (style & BOLD)
	{
		if (this == LLFontGL::sSansSerif)
		{
			return LLFontGL::sSansSerifBold->render(
				wstr, begin_offset,
				x, y,
				color,
				halign, valign, 
				(style & ~BOLD),
				max_chars, max_pixels,
				right_x, use_embedded);
		}
	}

	F32 drop_shadow_strength = 0.f;
	if (style & (DROP_SHADOW | DROP_SHADOW_SOFT))
	{
		F32 luminance;
		color.calcHSL(NULL, NULL, &luminance);
		drop_shadow_strength = clamp_rescale(luminance, 0.35f, 0.6f, 0.f, 1.f);
		if (luminance < 0.35f)
		{
			style = style & ~(DROP_SHADOW | DROP_SHADOW_SOFT);
		}
	}

	gGL.pushMatrix();
	glLoadIdentity();
	gGL.translatef(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ);
	//glScalef(sScaleX, sScaleY, 1.0f);
	
	// avoid half pixels
	// RN: if we're going to this trouble, might as well snap to nearest pixel all the time
	// but the plan is to get rid of this so that fonts "just work"
	//F32 half_pixel_distance = llabs(fmodf(sCurOrigin.mX * sScaleX, 1.f) - 0.5f);
	//if (half_pixel_distance < PIXEL_BORDER_THRESHOLD)
	//{
		gGL.translatef(PIXEL_CORRECTION_DISTANCE*sScaleX, 0.f, 0.f);
	//}

	// this code would just snap to pixel grid, although it seems to introduce more jitter
	//F32 pixel_offset_x = llround(sCurOrigin.mX * sScaleX) - (sCurOrigin.mX * sScaleX);
	//F32 pixel_offset_y = llround(sCurOrigin.mY * sScaleY) - (sCurOrigin.mY * sScaleY);
	//gGL.translatef(-pixel_offset_x, -pixel_offset_y, 0.f);

	// scale back to native pixel size
	//glScalef(1.f / sScaleX, 1.f / sScaleY, 1.f);
	//glScaled(1.0 / (F64) sScaleX, 1.0 / (F64) sScaleY, 1.0f);
	LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS);

	gGL.color4fv( color.mV );

	S32 chars_drawn = 0;
	S32 i;
	S32 length;

	if (-1 == max_chars)
	{
		length = (S32)wstr.length() - begin_offset;
	}
	else
	{
		length = llmin((S32)wstr.length() - begin_offset, max_chars );
	}

	F32 cur_x, cur_y, cur_render_x, cur_render_y;

	// Bind the font texture
	
	mImageGLp->bind(0);
	
 	// Not guaranteed to be set correctly
	gGL.setSceneBlendType(LLRender::BT_ALPHA);
	
	cur_x = ((F32)x * sScaleX);
	cur_y = ((F32)y * sScaleY);

	// Offset y by vertical alignment.
	switch (valign)
	{
	case TOP:
		cur_y -= mAscender;
		break;
	case BOTTOM:
		cur_y += mDescender;
		break;
	case VCENTER:
		cur_y -= ((mAscender - mDescender)/2.f);
		break;
	case BASELINE:
		// Baseline, do nothing.
		break;
	default:
		break;
	}

	switch (halign)
	{
	case LEFT:
		break;
	case RIGHT:
	  	cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX));
		break;
	case HCENTER:
	    cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2;
		break;
	default:
		break;
	}

	// Round properly.
	//cur_render_y = (F32)llfloor(cur_y/sScaleY + 0.5f)*sScaleY;
	//cur_render_x = (F32)llfloor(cur_x/sScaleX + 0.5f)*sScaleX;
	
	cur_render_y = cur_y;
	cur_render_x = cur_x;

	F32 start_x = cur_x;

	F32 inv_width = 1.f / mImageGLp->getWidth();
	F32 inv_height = 1.f / mImageGLp->getHeight();

	const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL;


	BOOL draw_ellipses = FALSE;
	if (use_ellipses && halign == LEFT)
	{
		// check for too long of a string
		if (getWidthF32(wstr.c_str(), 0, max_chars) * sScaleX > scaled_max_pixels)
		{
			// use four dots for ellipsis width to generate padding
			const LLWString dots(utf8str_to_wstring(std::string("....")));
			scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str())));
			draw_ellipses = TRUE;
		}
	}


	for (i = begin_offset; i < begin_offset + length; i++)
	{
		llwchar wch = wstr[i];

		// Handle embedded characters first, if they're enabled.
		// Embedded characters are a hack for notecards
		const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL;
		if (ext_data)
		{
			LLImageGL* ext_image = ext_data->mImage;
			const LLWString& label = ext_data->mLabel;

			F32 ext_height = (F32)ext_image->getHeight() * sScaleY;

			F32 ext_width = (F32)ext_image->getWidth() * sScaleX;
			F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width;

			if (!label.empty())
			{
				ext_advance += (EXT_X_BEARING + gExtCharFont->getWidthF32( label.c_str() )) * sScaleX;
			}

			if (start_x + scaled_max_pixels < cur_x + ext_advance)
			{
				// Not enough room for this character.
				break;
			}

			ext_image->bind();
			const F32 ext_x = cur_render_x + (EXT_X_BEARING * sScaleX);
			const F32 ext_y = cur_render_y + (EXT_Y_BEARING * sScaleY + mAscender - mLineHeight);

			LLRectf uv_rect(0.f, 1.f, 1.f, 0.f);
			LLRectf screen_rect(ext_x, ext_y + ext_height, ext_x + ext_width, ext_y);
			drawGlyph(screen_rect, uv_rect, LLColor4::white, style, drop_shadow_strength);

			if (!label.empty())
			{
				gGL.pushMatrix();
				//glLoadIdentity();
				//gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f);
				//glScalef(sScaleX, sScaleY, 1.f);
				gExtCharFont->render(label, 0,
									 /*llfloor*/((ext_x + (F32)ext_image->getWidth() + EXT_X_BEARING) / sScaleX), 
									 /*llfloor*/(cur_y / sScaleY),
									 color,
									 halign, BASELINE, NORMAL, S32_MAX, S32_MAX, NULL,
									 TRUE );
				gGL.popMatrix();
			}

			gGL.color4fv(color.mV);

			chars_drawn++;
			cur_x += ext_advance;
			if (((i + 1) < length) && wstr[i+1])
			{
				cur_x += EXT_KERNING * sScaleX;
			}
			cur_render_x = cur_x;

			// Bind the font texture
			mImageGLp->bind();
		}
		else
		{
			if (!hasGlyph(wch))
			{
				(const_cast<LLFontGL*>(this))->addChar(wch);
			}

			const LLFontGlyphInfo* fgi= getGlyphInfo(wch);
			if (!fgi)
			{
				llerrs << "Missing Glyph Info" << llendl;
				break;
			}
			if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth))
			{
				// Not enough room for this character.
				break;
			}

			// Draw the text at the appropriate location
			//Specify vertices and texture coordinates
			LLRectf uv_rect((fgi->mXBitmapOffset - PAD_AMT) * inv_width,
							(fgi->mYBitmapOffset + fgi->mHeight + PAD_AMT) * inv_height,
							(fgi->mXBitmapOffset + fgi->mWidth + PAD_AMT) * inv_width,
							(fgi->mYBitmapOffset - PAD_AMT) * inv_height);
			LLRectf screen_rect(cur_render_x + (F32)fgi->mXBearing - PAD_AMT,
								cur_render_y + (F32)fgi->mYBearing + PAD_AMT,
								cur_render_x + (F32)fgi->mXBearing + (F32)fgi->mWidth + PAD_AMT,
								cur_render_y + (F32)fgi->mYBearing - (F32)fgi->mHeight - PAD_AMT);

			drawGlyph(screen_rect, uv_rect, color, style, drop_shadow_strength);

			chars_drawn++;
			cur_x += fgi->mXAdvance;
			cur_y += fgi->mYAdvance;

			llwchar next_char = wstr[i+1];
			if (next_char && (next_char < LAST_CHARACTER))
			{
				// Kern this puppy.
				if (!hasGlyph(next_char))
				{
					(const_cast<LLFontGL*>(this))->addChar(next_char);
				}
				cur_x += getXKerning(wch, next_char);
			}

			// Round after kerning.
			// Must do this to cur_x, not just to cur_render_x, otherwise you
			// will squish sub-pixel kerned characters too close together.
			// For example, "CCCCC" looks bad.
			cur_x = (F32)llfloor(cur_x + 0.5f);
			//cur_y = (F32)llfloor(cur_y + 0.5f);

			cur_render_x = cur_x;
			cur_render_y = cur_y;
		}
	}

	if (right_x)
	{
		*right_x = cur_x / sScaleX;
	}

	if (style & UNDERLINE)
	{
		LLGLSNoTexture no_texture;
		gGL.begin(LLVertexBuffer::LINES);
		gGL.vertex2f(start_x, cur_y - (mDescender));
		gGL.vertex2f(cur_x, cur_y - (mDescender));
		gGL.end();
	}

	// *FIX: get this working in all alignment cases, etc.
	if (draw_ellipses)
	{
		// recursively render ellipses at end of string
		// we've already reserved enough room
		gGL.pushMatrix();
		//glLoadIdentity();
		//gGL.translatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f);
		//glScalef(sScaleX, sScaleY, 1.f);
		renderUTF8(std::string("..."), 
				0,
				cur_x / sScaleX, (F32)y,
				color,
				LEFT, valign,
				style,
				S32_MAX, max_pixels,
				right_x,
				FALSE); 
		gGL.popMatrix();
	}

	gGL.popMatrix();

	return chars_drawn;
}