This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/ww3d2/render2dsentence.cpp

1600 lines
38 KiB
C++

/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : WW3D *
* *
* $Archive:: /Commando/Code/ww3d2/render2dsentence.cpp $*
* *
* $Author:: Steve_t $*
* *
* $Modtime:: 8/26/02 3:18p $*
* *
* $Revision:: 27 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "render2dsentence.h"
#include "surfaceclass.h"
#include "texture.h"
#include "wwprofile.h"
#include "wwmemlog.h"
#include "dx8wrapper.h"
////////////////////////////////////////////////////////////////////////////////////
// Local constants
////////////////////////////////////////////////////////////////////////////////////
const int CHAR_TEXTURE_SIZE = 256;
const int CHAR_BUFFER_LEN = 32768;
// Macros.
// NOTE 0: Word wrap logic does not apply to Han characters (Chinese, Japanese & Korean).
// Therefore treat each of these characters as a word which can be preceeded by a line break.
// NOTE 1: This is a simplification. Some Korean characters should not be line break characters.
#define IS_BREAK_CHAR(ch) ((ch == L' ') || ((ch >= 0x3000) && (ch <= 0xdfff)))
////////////////////////////////////////////////////////////////////////////////////
//
// Render2DSentenceClass
//
////////////////////////////////////////////////////////////////////////////////////
Render2DSentenceClass::Render2DSentenceClass (void) :
Font (NULL),
Location (0.0F,0.0F),
Cursor (0.0F,0.0F),
TextureOffset (0, 0),
TextureStartX (0),
CurSurface (NULL),
CurrTextureSize (0),
MonoSpaced (false),
IsClippedEnabled (false),
ClipRect (0, 0, 0, 0),
BaseLocation (0, 0),
LockedPtr (NULL),
LockedStride (0),
TextureSizeHint (0),
WrapWidth (0),
TabStop (5.0),
DrawExtents (0, 0, 0, 0),
Renderers(sizeof(PreAllocatedRenderers)/sizeof(RendererDataStruct),PreAllocatedRenderers)
{
Shader = Render2DClass::Get_Default_Shader ();
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// ~Render2DSentenceClass
//
////////////////////////////////////////////////////////////////////////////////////
Render2DSentenceClass::~Render2DSentenceClass (void)
{
REF_PTR_RELEASE (Font);
Reset ();
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Set_Font
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Set_Font (FontCharsClass *font)
{
Reset ();
REF_PTR_SET (Font, font);
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Reset_Polys
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Reset_Polys (void)
{
for (int index = 0; index < Renderers.Count (); index ++) {
Renderers[index].Renderer->Reset ();
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Reset
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Reset (void)
{
//
// Make sure we unlock the current surface (if necessary)
//
if (LockedPtr != NULL) {
CurSurface->Unlock ();
LockedPtr = NULL;
}
//
// Release our hold on the current surface
//
REF_PTR_RELEASE (CurSurface);
//
// Free each renderer
//
for (int i=0;i<Renderers.Count();++i) {
delete Renderers[i].Renderer;
}
Renderers.Reset_Active();
Cursor.Set (0, 0);
MonoSpaced = false;
Release_Pending_Surfaces ();
Reset_Sentence_Data ();
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Make_Additive
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Make_Additive (void)
{
Shader.Set_Dst_Blend_Func (ShaderClass::DSTBLEND_ONE);
Shader.Set_Src_Blend_Func (ShaderClass::SRCBLEND_ONE);
Shader.Set_Primary_Gradient (ShaderClass::GRADIENT_MODULATE);
Shader.Set_Secondary_Gradient (ShaderClass::SECONDARY_GRADIENT_DISABLE);
Set_Shader (Shader);
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Make_Additive
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Set_Shader (ShaderClass shader)
{
Shader = shader;
//
// Change each renderer's shader
//
for (int i = 0; i < Renderers.Count (); i ++) {
ShaderClass *curr_shader = Renderers[i].Renderer->Get_Shader ();
(*curr_shader) = Shader;
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Render
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Render (void)
{
if (DX8Wrapper::Is_Device_Lost() || !DX8Wrapper::Is_Initted()) return;
//
// Build any textures that are pending
//
Build_Textures ();
//
// Ask each renderer to draw its contents
//
for (int i = 0; i < Renderers.Count (); i ++) {
Renderers[i].Renderer->Render ();
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Set_Base_Location
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Set_Base_Location (const Vector2 &loc)
{
Vector2 dif = loc - BaseLocation;
BaseLocation = loc;
for (int i = 0; i < Renderers.Count (); i ++) {
Renderers[i].Renderer->Move (dif);
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Set_Location
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Set_Location (const Vector2 &loc)
{
Location = loc;
return ;
}
void
Render2DSentenceClass::Set_Tabstop(float stop)
{
if (stop > 0.0) {
TabStop = stop;
} else {
TabStop = 1.0;
}
}
////////////////////////////////////////////////////////////////////////////////////
//
// Get_Text_Extents
//
////////////////////////////////////////////////////////////////////////////////////
Vector2
Render2DSentenceClass::Get_Text_Extents (const WCHAR *text)
{
if (!DX8Wrapper::Is_Initted()) {
Vector2 temp(0,0);
return(temp);
}
Vector2 extent (0, Font->Get_Char_Height());
while (*text) {
WCHAR ch = *text++;
if ( ch != (WCHAR)'\n' ) {
extent.X += Font->Get_Char_Spacing( ch );
}
}
return extent;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Find_Row_Start
//
////////////////////////////////////////////////////////////////////////////////////
const WCHAR *
Render2DSentenceClass::Find_Row_Start( const WCHAR * text, int row_index )
{
if (row_index == 0) {
return text;
}
if (!DX8Wrapper::Is_Initted()) {
return text;
}
const WCHAR *retval = NULL;
float max_x_pos = 0;
float x_pos = 0;
float y_pos = Font->Get_Char_Height ();
int row_counter = 0;
while (*text) {
WCHAR ch = *text++;
bool is_wrapped = false;
//
// Check to see if we need to wrap on this word-break
//
if (IS_BREAK_CHAR (ch) && WrapWidth > 0) {
//
// Find the width of the next word
//
const WCHAR *word = text;
float word_width = Font->Get_Char_Spacing (ch);
while ((*word != 0) && ((*word > L' ') && !IS_BREAK_CHAR (*word))) {
word_width += Font->Get_Char_Spacing (*word++);
}
//
// Did the word extend past the wrap width?
//
if ((x_pos + word_width) >= WrapWidth) {
is_wrapped = true;
}
} else if (ch == L'\n') {
is_wrapped = true;
}
//
// Handle line wrapping
//
if (is_wrapped) {
max_x_pos = max (max_x_pos, x_pos);
x_pos = 0;
y_pos += Font->Get_Char_Height ();
//
// Is this the line we're looking for?
//
row_counter ++;
if (row_counter == row_index) {
retval = (ch == L' ' || ch == L'\n') ? text : text - 1;
break;
}
}
if (ch != (WCHAR)'\n') {
x_pos += Font->Get_Char_Spacing (ch);
}
}
return retval;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Get_Formatted_Text_Extents
//
////////////////////////////////////////////////////////////////////////////////////
Vector2
Render2DSentenceClass::Get_Formatted_Text_Extents (const WCHAR *text, int *row_count)
{
if (!DX8Wrapper::Is_Initted()) {
Vector2 temp(0,0);
return(temp);
}
float max_x_pos = 0;
float x_pos = 0;
float y_pos = Font->Get_Char_Height ();
int row_counter = 0;
while (*text) {
WCHAR ch = *text++;
bool is_wrapped = false;
//
// Check to see if we need to wrap on this word-break
//
if (IS_BREAK_CHAR (ch) && WrapWidth > 0) {
//
// Find the width of the next word
//
const WCHAR *word = text;
float word_width = Font->Get_Char_Spacing (ch);
while ((*word != 0) && ((*word > L' ') && !IS_BREAK_CHAR (*word))) {
word_width += Font->Get_Char_Spacing (*word++);
}
//
// Did the word extend past the wrap width?
//
if ((x_pos + word_width) >= WrapWidth) {
is_wrapped = true;
}
} else if (ch == L'\n') {
is_wrapped = true;
}
//
// Handle line wrapping
//
if (is_wrapped) {
max_x_pos = max (max_x_pos, x_pos);
x_pos = 0;
y_pos += Font->Get_Char_Height ();
row_counter ++;
}
if (ch != (WCHAR)'\n') {
x_pos += Font->Get_Char_Spacing (ch);
}
}
//
// Build a Vector2 out of our extents
//
Vector2 extent;
extent.X = max (max_x_pos, x_pos);
extent.Y = y_pos;
//
// Return the row count to the caller (if necessary)
//
if (row_count != NULL) {
(*row_count) = row_counter + 1;
}
return extent;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Reset_Sentence_Data
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Reset_Sentence_Data (void)
{
//
// Release our hold on each texture used in the sentence
//
for (int index = 0; index < SentenceData.Count (); index ++) {
REF_PTR_RELEASE (SentenceData[index].Surface);
}
SentenceData.Reset_Active();
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Release_Pending_Surfaces
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Release_Pending_Surfaces (void)
{
//
// Release our hold on each pending surface
//
for (int index = 0; index < PendingSurfaces.Count (); index ++) {
SurfaceClass *curr_surface = PendingSurfaces[index].Surface;
REF_PTR_RELEASE (curr_surface);
}
PendingSurfaces.Reset_Active();
return;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Build_Textures
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Build_Textures (void)
{
WWMEMLOG(MEM_TEXTURE);
//
// Make sure we unlock the current surface
//
if (LockedPtr != NULL) {
CurSurface->Unlock ();
LockedPtr = NULL;
}
//
// Release our hold on the current surface
//
REF_PTR_RELEASE (CurSurface);
TextureOffset.Set (0, 0);
TextureStartX = 0;
//
// Convert all pending surfaces to textures
//
for (int index = 0; index < PendingSurfaces.Count (); index ++) {
PendingSurfaceStruct &surface_info = PendingSurfaces[index];
SurfaceClass *curr_surface = surface_info.Surface;
//
// Get the dimensions of the surface
//
SurfaceClass::SurfaceDescription desc;
curr_surface->Get_Description (desc);
//
// Create the new texture
//
TextureClass *new_texture = new TextureClass (desc.Width, desc.Width, WW3D_FORMAT_A4R4G4B4, TextureClass::MIP_LEVELS_1);
SurfaceClass *texture_surface = new_texture->Get_Surface_Level ();
//
// Copy the contents of the texture from the surface
//
DX8Wrapper::_Copy_DX8_Rects (curr_surface->Peek_D3D_Surface (), NULL, 0, texture_surface->Peek_D3D_Surface (), NULL);
REF_PTR_RELEASE (texture_surface);
//
// Assign this texture to any renderers that need it
//
for (int renderer_index = 0; renderer_index < surface_info.Renderers.Count (); renderer_index ++) {
Render2DClass *renderer = surface_info.Renderers[renderer_index];
renderer->Set_Texture (new_texture);
}
//
// Release our hold on the objects
//
REF_PTR_RELEASE (new_texture);
REF_PTR_RELEASE (curr_surface);
}
//
// Reset the list
//
PendingSurfaces.Reset_Active();
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Draw_Sentence
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Draw_Sentence (uint32 color)
{
Render2DClass *curr_renderer = NULL;
SurfaceClass *curr_surface = NULL;
DrawExtents.Set (0, 0, 0, 0);
//
// Loop over all the parts of the sentence
//
for (int index = 0; index < SentenceData.Count (); index ++) {
SentenceDataStruct &data = SentenceData[index];
//
// Has the surface changed?
//
if (data.Surface != curr_surface) {
curr_surface = data.Surface;
//
// Try to find a renderer that uses the same "texture"
//
bool found = false;
for (int renderer_index = 0; renderer_index < Renderers.Count (); renderer_index ++) {
if (Renderers[renderer_index].Surface == curr_surface) {
found = true;
curr_renderer = Renderers[renderer_index].Renderer;
break;
}
}
//
// Create a new renderer if we couldn't find an appropriate one
//
if (found == false) {
//
// Allocate a new renderer
//
curr_renderer = new Render2DClass;
curr_renderer->Set_Coordinate_Range (Render2DClass::Get_Screen_Resolution ());
ShaderClass *curr_shader = curr_renderer->Get_Shader ();
(*curr_shader) = Shader;
//
// Add it to our list
//
RendererDataStruct render_info;
render_info.Renderer = curr_renderer;
render_info.Surface = curr_surface;
Renderers.Add (render_info);
//
// Now, add this renderer to the surface pending list
//
for (int surface_index = 0; surface_index < PendingSurfaces.Count (); surface_index ++) {
PendingSurfaceStruct &surface_info = PendingSurfaces[surface_index];
if (surface_info.Surface == curr_surface) {
surface_info.Renderers.Add (curr_renderer);
}
}
}
}
//
// Get the dimensions of the surface
//
SurfaceClass::SurfaceDescription desc;
curr_surface->Get_Description (desc);
//
// Add a quad that contains this sentence chunk
//
RectClass screen_rect = data.ScreenRect;
screen_rect += Location;
RectClass uv_rect = data.UVRect;
//
// Clip the quad (as necessary)
//
bool add_quad = true;
if (IsClippedEnabled) {
//
// Check for completely clipped
//
if ( screen_rect.Right <= ClipRect.Left ||
screen_rect.Bottom <= ClipRect.Top)
{
add_quad = false;
/*} else if ( screen_rect.Top < ClipRect.Top ||
screen_rect.Bottom > ClipRect.Bottom)
{
add_quad = false;*/
} else {
//
// Clip the polygons to the specified area
//
RectClass clipped_rect;
clipped_rect.Left = max (screen_rect.Left, ClipRect.Left);
clipped_rect.Right = min (screen_rect.Right, ClipRect.Right);
clipped_rect.Top = max (screen_rect.Top, ClipRect.Top);
clipped_rect.Bottom = min (screen_rect.Bottom, ClipRect.Bottom);
//
// Clip the texture to the specified area
//
RectClass clipped_uv_rect;
float percent = ((clipped_rect.Left - screen_rect.Left) / screen_rect.Width ());
clipped_uv_rect.Left = uv_rect.Left + (uv_rect.Width () * percent);
percent = ((clipped_rect.Right - screen_rect.Left) / screen_rect.Width ());
clipped_uv_rect.Right = uv_rect.Left + (uv_rect.Width () * percent);
percent = ((clipped_rect.Top - screen_rect.Top) / screen_rect.Height ());
clipped_uv_rect.Top = uv_rect.Top + (uv_rect.Height () * percent);
percent = ((clipped_rect.Bottom - screen_rect.Top) / screen_rect.Height ());
clipped_uv_rect.Bottom = uv_rect.Top + (uv_rect.Height () * percent);
//
// Use the clipped rectangles to render
//
screen_rect = clipped_rect;
uv_rect = clipped_uv_rect;
}
}
if (add_quad) {
uv_rect *= 1.0F / ((float)desc.Width);
curr_renderer->Add_Quad (screen_rect, uv_rect, color);
//
// Add this rectangle to the total draw extents
//
if (DrawExtents.Width () == 0) {
DrawExtents = screen_rect;
} else {
DrawExtents += screen_rect;
}
}
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Record_Sentence_Chunk
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Record_Sentence_Chunk (void)
{
//
// Do we have anything to store?
//
int width = TextureOffset.I - TextureStartX;
if (width > 0) {
float char_height = Font->Get_Char_Height ();
//
// Build a structure that contains enough information
// to hold this portion of the sentence
//
SentenceDataStruct sentence_data;
sentence_data.Surface = CurSurface;
sentence_data.Surface->Add_Ref ();
sentence_data.ScreenRect.Left = Cursor.X;
sentence_data.ScreenRect.Right = Cursor.X + width;
sentence_data.ScreenRect.Top = Cursor.Y;
sentence_data.ScreenRect.Bottom = Cursor.Y + char_height;
sentence_data.UVRect.Left = TextureStartX;
sentence_data.UVRect.Top = TextureOffset.J;
sentence_data.UVRect.Right = TextureOffset.I;
sentence_data.UVRect.Bottom = TextureOffset.J + char_height;
//
// Add this information to our list
//
SentenceData.Add (sentence_data);
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Allocate_New_Surface
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Allocate_New_Surface (const WCHAR *text)
{
//
// Unlock the last surface (if necessary)
//
if (LockedPtr != NULL) {
CurSurface->Unlock ();
LockedPtr = NULL;
}
//
// Calculate the width of the text
//
int text_width = 0;
for (int index = 0; text[index] != 0; index ++) {
text_width += Font->Get_Char_Spacing (text[index]);
}
int char_height = Font->Get_Char_Height ();
//
// Find the best texture size for the remaining text
//
CurrTextureSize = 256;
int best_tex_mem_usage = 999999999;
for (int pow2 = 6; pow2 <= 8; pow2 ++) {
int size = 1 << pow2;
int row_count = (text_width / size) + 1;
int rows_per_texture = size / (char_height + 1);
//
// Can we even fit one character on this texture?
//
if (rows_per_texture > 0) {
//
// How many textures (at this size) would it take to render
// the remaining text?
//
int texture_count = row_count / rows_per_texture;
texture_count = max (texture_count, 1);
//
// Is this the best usage of texture memory we've found yet?
//
int texture_mem_usage = (texture_count * size * size);
if (texture_mem_usage < best_tex_mem_usage) {
CurrTextureSize = size;
best_tex_mem_usage = texture_mem_usage;
}
}
}
//
// Use whichever is larger, the hint or the calculated size
//
CurrTextureSize = max (TextureSizeHint, CurrTextureSize);
//
// Release our extra hold on the old surface
//
REF_PTR_RELEASE (CurSurface);
//
// Create the new surface
//
CurSurface = NEW_REF (SurfaceClass, (CurrTextureSize, CurrTextureSize, WW3D_FORMAT_A4R4G4B4));
WWASSERT (CurSurface != NULL);
CurSurface->Add_Ref ();
//
// Add this surface to our list
//
PendingSurfaceStruct surface_info;
surface_info.Surface = CurSurface;
PendingSurfaces.Add (surface_info);
//
// Reset to the upper left corner
//
TextureOffset.Set (0, 0);
TextureStartX = 0;
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Build_Sentence
//
////////////////////////////////////////////////////////////////////////////////////
void
Render2DSentenceClass::Build_Sentence (const WCHAR *text)
{
if (text == NULL) {
return ;
}
if (!DX8Wrapper::Is_Initted()) {
return;
}
//
// Start fresh
//
Reset_Sentence_Data ();
Cursor.Set (0, 0);
//
// Ensure we have a surface to start with
//
if (CurSurface == NULL) {
Allocate_New_Surface (text);
}
float char_height = Font->Get_Char_Height ();
//
// Loop over all the characters in the string
//
while (text != NULL) {
WCHAR ch = *text++;
//
// Determine how much horizontal space this character requires
//
float char_spacing = Font->Get_Char_Spacing (ch);
bool exceeded_texture_width = ((TextureOffset.I + char_spacing) >= CurrTextureSize);
bool encountered_break_char = (IS_BREAK_CHAR (ch) || ch == L'\n' || ch == 0 || ch == L'\t');
//
// Do we need to record this portion of the sentence to its own chunk?
//
if (exceeded_texture_width || encountered_break_char) {
Record_Sentence_Chunk ();
//
// Adjust the positions
//
Cursor.X += (TextureOffset.I - TextureStartX);
TextureStartX = TextureOffset.I;
//
// Adjust the output coordinates
//
if (IS_BREAK_CHAR (ch)) {
if (ch == L' ') {
Cursor.X += char_spacing;
}
//
// Check to see if we need to wrap on this word-break
//
if (WrapWidth > 0) {
//
// Find the length of the next word
//
const WCHAR *word = text;
float word_width = (ch == L' ') ? 0 : char_spacing;
while ((*word != 0) && ((*word > L' ') && !IS_BREAK_CHAR (*word))) {
word_width += Font->Get_Char_Spacing (*word++);
}
//
// Should we wrap the next word?
//
if ((Cursor.X + word_width) >= WrapWidth) {
Cursor.X = 0;
Cursor.Y += char_height;
}
}
} else if (ch == L'\n') {
Cursor.X = 0;
Cursor.Y += char_height;
} else if (ch == 0) {
break;
} else if (ch == L'\t') {
float tab_spacing = (char_spacing * TabStop);
float tab_pos = (floor(Cursor.X / tab_spacing) * tab_spacing);
Cursor.X = (tab_pos + tab_spacing);
}
//
// Did the text extend past the edge of the texture?
//
if (exceeded_texture_width) {
TextureStartX = 0;
TextureOffset.I = TextureStartX;
TextureOffset.J += char_height;
//
// Did the text extent completely off the texture?
//
if ((TextureOffset.J + char_height) >= CurrTextureSize) {
Allocate_New_Surface (text);
}
}
}
if (ch != L'\n' && ch != L' ' && ch != L'\t') {
//
// Ensure the surface is locked
//
if (LockedPtr == NULL) {
LockedPtr = (uint16 *)CurSurface->Lock (&LockedStride);
WWASSERT (LockedPtr != NULL);
}
//
// Check to ensure the text will fit on this texture
//
WWASSERT (((TextureOffset.I + char_spacing) < CurrTextureSize) && ((TextureOffset.J + char_height) < CurrTextureSize));
//
// Blit the character to the surface
//
Font->Blit_Char (ch, LockedPtr, LockedStride, TextureOffset.I, TextureOffset.J);
TextureOffset.I += char_spacing;
}
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
void Render2DSentenceClass::Force_Alpha( float alpha )
{
for (int i = 0; i < Renderers.Count (); i ++) {
Renderers[i].Renderer->Force_Alpha( alpha );
}
}
////////////////////////////////////////////////////////////////////////////////////
//
// FontCharsClass
//
////////////////////////////////////////////////////////////////////////////////////
FontCharsClass::FontCharsClass (void) :
OldGDIFont( NULL ),
OldGDIBitmap( NULL ),
GDIFont( NULL ),
GDIBitmap( NULL ),
GDIBitmapBits ( NULL ),
MemDC( NULL ),
CurrPixelOffset( 0 ),
PointSize( 0 ),
CharHeight( 0 ),
UnicodeCharArray( NULL ),
FirstUnicodeChar( 0xFFFF ),
LastUnicodeChar( 0 ),
IsBold (false),
BufferList(sizeof(PreAllocatedBufferList)/sizeof(uint16*),PreAllocatedBufferList)
{
::memset( ASCIICharArray, 0, sizeof (ASCIICharArray) );
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// ~FontCharsClass
//
////////////////////////////////////////////////////////////////////////////////////
FontCharsClass::~FontCharsClass (void)
{
for (int i=0;i<BufferList.Count(); ++i) {
delete [] BufferList[i];
}
BufferList.Reset_Active();
Free_GDI_Font();
Free_Character_Arrays();
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Get_Char_Data
//
////////////////////////////////////////////////////////////////////////////////////
const FontCharsClass::CharDataStruct *
FontCharsClass::Get_Char_Data (WCHAR ch)
{
const CharDataStruct *retval = NULL;
if ( ch < 256 ) {
retval = ASCIICharArray[ch];
} else {
Grow_Unicode_Array( ch );
retval = UnicodeCharArray[ch - FirstUnicodeChar];
}
//
// If the character wasn't found, then add it to our list
//
if ( retval == NULL ) {
retval = Store_GDI_Char( ch );
}
WWASSERT( retval->Value == ch );
return retval;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Get_Char_Width
//
////////////////////////////////////////////////////////////////////////////////////
int
FontCharsClass::Get_Char_Width (WCHAR ch)
{
const CharDataStruct * data = Get_Char_Data( ch );
if ( data != NULL ) {
return data->Width;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Get_Char_Spacing
//
////////////////////////////////////////////////////////////////////////////////////
int
FontCharsClass::Get_Char_Spacing (WCHAR ch)
{
const CharDataStruct * data = Get_Char_Data( ch );
if ( data != NULL ) {
if ( data->Width != 0 ) {
return data->Width + 1;
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Blit_Char
//
////////////////////////////////////////////////////////////////////////////////////
void
FontCharsClass::Blit_Char (WCHAR ch, uint16 *dest_ptr, int dest_stride, int x, int y)
{
const CharDataStruct * data = Get_Char_Data( ch );
if ( data != NULL && data->Width != 0 ) {
//
// Setup the src and destination pointers
//
int dest_inc = (dest_stride >> 1);
uint16 *src_ptr = data->Buffer;
dest_ptr += (dest_inc * y) + x;
//
// Simply copy the data from the src buffer to the destination
//
for ( int row = 0; row < CharHeight; row ++ ) {
for ( int col = 0; col < data->Width; col ++ ) {
dest_ptr[col] = *src_ptr++;
}
dest_ptr += dest_inc;
}
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Store_GDI_Char
//
////////////////////////////////////////////////////////////////////////////////////
const FontCharsClass::CharDataStruct *
FontCharsClass::Store_GDI_Char (WCHAR ch)
{
int width = PointSize * 2;
int height = PointSize * 2;
//
// Get the size of the character we just drew
//
SIZE char_size = { 0 };
::GetTextExtentPoint32W( MemDC, &ch, 1, &char_size );
int x_pos = 0;
//
// HACK HACK -- With the default font that Renegade uses the
// W and V characters need to be moved over one pixel.
//
if ( (ch == 'W' || ch == 'V') && (GDIFontName.Compare_No_Case ("Arial MT") == 0) ) {
x_pos = 1;
char_size.cx += 1;
}
//
// Draw the character into the memory DC
//
RECT rect = { 0, 0, width, height };
::ExtTextOutW( MemDC, x_pos, 0, ETO_OPAQUE, &rect, &ch, 1, NULL);
//
// Get a pointer to the surface that this character should use
//
Update_Current_Buffer( char_size.cx );
uint16 *curr_buffer = BufferList[BufferList.Count () - 1];
curr_buffer += CurrPixelOffset;
//
// Copy the BMP contents to the buffer
//
int stride = (((width * 3) + 3) & ~3);
for (int row = 0; row < char_size.cy; row ++) {
//
// Compute the indices into the BMP and surface
//
int index = (row * stride);
//
// Loop over each column
//
for (int col = 0; col < char_size.cx; col ++) {
//
// Get the pixel color at this location
//
uint8 pixel_value = GDIBitmapBits[index];
index += 3;
uint16 pixel_color = 0;
if (pixel_value != 0) {
pixel_color = 0x0FFF;
}
//
// Convert the pixel intensity from 8bit to 4bit and
// store it in our buffer
//
uint8 alpha_value = ((pixel_value >> 4) & 0xF);
*curr_buffer ++ = pixel_color | (alpha_value << 12);
}
}
//
// Save information about this character in our list
//
CharDataStruct *char_data = new CharDataStruct;
char_data->Value = ch;
char_data->Width = char_size.cx;
char_data->Buffer = BufferList[BufferList.Count () - 1] + CurrPixelOffset;
//
// Insert this character into our array
//
if ( ch < 256 ) {
ASCIICharArray[ch] = char_data;
} else {
UnicodeCharArray[ch - FirstUnicodeChar] = char_data;
}
//
// Advance the character position
//
CurrPixelOffset += (char_size.cx * CharHeight);
//
// Return the index of the entry we just added
//
return char_data;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Update_Current_Buffer
//
////////////////////////////////////////////////////////////////////////////////////
void
FontCharsClass::Update_Current_Buffer (int char_width)
{
//
// Check to see if we need to allocate a new buffer
//
bool needs_new_buffer = (BufferList.Count () == 0);
if (needs_new_buffer == false) {
//
// Would we extend past this buffer?
//
if ( (CurrPixelOffset + (char_width * CharHeight)) > CHAR_BUFFER_LEN ) {
needs_new_buffer = true;
}
}
//
// Do we need to create a new surface?
//
if (needs_new_buffer) {
uint16 *new_buffer = new uint16[CHAR_BUFFER_LEN];
BufferList.Add( new_buffer );
CurrPixelOffset = 0;
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Create_GDI_Font
//
////////////////////////////////////////////////////////////////////////////////////
void
FontCharsClass::Create_GDI_Font (const char *font_name)
{
HDC screen_dc = ::GetDC (NULL);
//
// Calculate the height of the font in logical units
//
int font_height = -MulDiv (PointSize, ::GetDeviceCaps (screen_dc, LOGPIXELSY), 72);
//
// Create the Windows font
//
DWORD bold = IsBold ? FW_BOLD : FW_NORMAL;
DWORD italic = 0;
DWORD charset;
// Map the current code page to a font character set.
switch (GetACP()) {
// Chinese.
case 936:
case 950:
charset = CHINESEBIG5_CHARSET;
break;
// Japanese.
case 932:
charset = SHIFTJIS_CHARSET;
break;
// Korean.
case 949:
charset = HANGUL_CHARSET;
break;
// Anything else.
default:
charset = DEFAULT_CHARSET;
break;
}
GDIFont = ::CreateFont (font_height, 0, 0, 0, bold, italic,
FALSE, FALSE, charset, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY,
VARIABLE_PITCH, font_name);
//
// Set-up the fields of the BITMAPINFOHEADER
// Note: Top-down DIBs use negative height in Win32.
//
BITMAPINFOHEADER bitmap_info = { 0 };
bitmap_info.biSize = sizeof (BITMAPINFOHEADER);
bitmap_info.biWidth = PointSize * 2;
bitmap_info.biHeight = -(PointSize * 2);
bitmap_info.biPlanes = 1;
bitmap_info.biBitCount = 24;
bitmap_info.biCompression = BI_RGB;
bitmap_info.biSizeImage = ((PointSize * PointSize * 4) * 3);
bitmap_info.biXPelsPerMeter = 0;
bitmap_info.biYPelsPerMeter = 0;
bitmap_info.biClrUsed = 0;
bitmap_info.biClrImportant = 0;
//
// Create a bitmap that we can access the bits directly of
//
GDIBitmap = ::CreateDIBSection ( screen_dc,
(const BITMAPINFO *)&bitmap_info,
DIB_RGB_COLORS,
(void **)&GDIBitmapBits,
NULL,
0L);
//
// Create a device context we can select the font and bitmap into
//
MemDC = ::CreateCompatibleDC (NULL);
//
// Now select the BMP and font into the DC
//
OldGDIBitmap = (HBITMAP)::SelectObject (MemDC, GDIBitmap);
OldGDIFont = (HFONT)::SelectObject (MemDC, GDIFont);
::SetBkColor (MemDC, RGB (0, 0, 0));
::SetTextColor (MemDC, RGB (255, 255, 255));
//
// Lookup the pixel height of the font
//
TEXTMETRIC text_metric = { 0 };
::GetTextMetrics (MemDC, &text_metric);
CharHeight = text_metric.tmHeight;
//
// Release our temporary screen DC
//
::ReleaseDC (NULL, screen_dc);
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Free_GDI_Font
//
////////////////////////////////////////////////////////////////////////////////////
void
FontCharsClass::Free_GDI_Font (void)
{
//
// Select the old font back into the DC and delete
// our font object
//
if ( GDIFont != NULL ) {
::SelectObject( MemDC, OldGDIFont );
::DeleteObject( GDIFont );
GDIFont = NULL;
}
//
// Select the old bitmap back into the DC and delete
// our bitmap object
//
if ( GDIBitmap != NULL ) {
::SelectObject( MemDC, OldGDIBitmap );
::DeleteObject( GDIBitmap );
GDIBitmap = NULL;
}
//
// Delete our memory DC
//
if ( MemDC != NULL ) {
::DeleteDC( MemDC );
MemDC = NULL;
}
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Initialize_GDI_Font
//
////////////////////////////////////////////////////////////////////////////////////
void
FontCharsClass::Initialize_GDI_Font (const char *font_name, int point_size, bool is_bold)
{
//
// Build a unique name from the font name and its size
//
Name.Format ("%s%d", font_name, point_size);
//
// Remember these settings
//
GDIFontName = font_name;
PointSize = point_size;
IsBold = is_bold;
//
// Create the actual font object
//
Create_GDI_Font (font_name);
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Is_Font
//
////////////////////////////////////////////////////////////////////////////////////
bool
FontCharsClass::Is_Font (const char *font_name, int point_size, bool is_bold)
{
bool retval = false;
//
// Check to see if both the name and height matches...
//
if ( (GDIFontName.Compare_No_Case (font_name) == 0) &&
(point_size == PointSize) &&
(is_bold == IsBold))
{
retval = true;
}
return retval;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Grow_Unicode_Array
//
////////////////////////////////////////////////////////////////////////////////////
void
FontCharsClass::Grow_Unicode_Array (WCHAR ch)
{
//
// Don't do anything if character is in the ASCII range
//
if ( ch < 256 ) {
return ;
}
//
// Don't do anything if character is in the currently allocated range
//
if ( ch >= FirstUnicodeChar && ch <= LastUnicodeChar ) {
return ;
}
uint16 first_index = min( FirstUnicodeChar, ch );
uint16 last_index = max( LastUnicodeChar, ch );
uint16 count = (last_index - first_index) + 1;
//
// Allocate enough memory to hold the new cells
//
CharDataStruct **new_array = new CharDataStruct *[count];
::memset (new_array, 0, sizeof (CharDataStruct *) * count);
//
// Copy the contents of the old array into the new array
//
if ( UnicodeCharArray != NULL ) {
int start_offset = (FirstUnicodeChar - first_index);
int old_count = (LastUnicodeChar - FirstUnicodeChar) + 1;
::memcpy (&new_array[start_offset], UnicodeCharArray, sizeof (CharDataStruct *) * old_count);
//
// Delete the old array
//
delete [] UnicodeCharArray;
UnicodeCharArray = NULL;
}
FirstUnicodeChar = first_index;
LastUnicodeChar = last_index;
UnicodeCharArray = new_array;
return ;
}
////////////////////////////////////////////////////////////////////////////////////
//
// Free_Character_Arrays
//
////////////////////////////////////////////////////////////////////////////////////
void
FontCharsClass::Free_Character_Arrays (void)
{
if ( UnicodeCharArray != NULL ) {
int count = (LastUnicodeChar - FirstUnicodeChar) + 1;
//
// Delete each member of the unicode array
//
for (int index = 0; index < count; index ++) {
if ( UnicodeCharArray[index] != NULL ) {
delete UnicodeCharArray[index];
UnicodeCharArray[index] = NULL;
}
}
//
// Delete the array itself
//
delete [] UnicodeCharArray;
UnicodeCharArray = NULL;
}
//
// Delete each member of the ascii character array
//
for (int index = 0; index < 256; index ++) {
if ( ASCIICharArray[index] != NULL ) {
delete ASCIICharArray[index];
ASCIICharArray[index] = NULL;
}
}
return ;
}