mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-29 23:22:01 +00:00
b03ef96f36
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5405 fc73d0e0-1445-4013-8a0c-d673dee63da5
3174 lines
81 KiB
C
3174 lines
81 KiB
C
#include "quakedef.h"
|
|
|
|
#ifndef SERVERONLY
|
|
#include "shader.h"
|
|
#include "gl_draw.h"
|
|
|
|
#ifdef _WIN32
|
|
#ifdef _XBOX
|
|
#include <xtl.h>
|
|
#else
|
|
#include <windows.h>
|
|
#endif
|
|
#endif
|
|
#include <ctype.h>
|
|
|
|
void Font_Init(void);
|
|
void Font_Shutdown(void);
|
|
struct font_s *Font_LoadFont(const char *fontfilename, float height);
|
|
void Font_Free(struct font_s *f);
|
|
void Font_BeginString(struct font_s *font, float vx, float vy, int *px, int *py);
|
|
void Font_BeginScaledString(struct font_s *font, float vx, float vy, float szx, float szy, float *px, float *py); /*avoid using*/
|
|
void Font_Transform(float vx, float vy, int *px, int *py);
|
|
int Font_CharHeight(void);
|
|
float Font_CharScaleHeight(void);
|
|
|
|
//FIXME: if we want to do emoji properly, then we're going to need support for variants somehow
|
|
// easiest is probably to make the next codepoint available too (and consumable)
|
|
// handling variants in the font cache is another issue. gah. not worth it.
|
|
int Font_CharWidth(unsigned int charflags, unsigned int codepoint);
|
|
float Font_CharScaleWidth(unsigned int charflags, unsigned int codepoint);
|
|
int Font_CharEndCoord(struct font_s *font, int x, unsigned int charflags, unsigned int codepoint);
|
|
int Font_DrawChar(int px, int py, unsigned int charflags, unsigned int codepoint);
|
|
float Font_DrawScaleChar(float px, float py, unsigned int charflags, unsigned int codepoint); /*avoid using*/
|
|
|
|
void Font_EndString(struct font_s *font);
|
|
int Font_LineBreaks(conchar_t *start, conchar_t *end, int maxpixelwidth, int maxlines, conchar_t **starts, conchar_t **ends);
|
|
struct font_s *font_default;
|
|
struct font_s *font_console;
|
|
struct font_s *font_tiny;
|
|
static int font_be_flags;
|
|
extern unsigned int r2d_be_flags;
|
|
|
|
#ifdef AVAIL_FREETYPE
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
|
|
#ifndef FT_LOAD_COLOR
|
|
#define FT_LOAD_COLOR (1L<<20)
|
|
#endif
|
|
|
|
static FT_Library fontlib;
|
|
|
|
#ifdef FREETYPE_STATIC
|
|
#define pFT_Init_FreeType FT_Init_FreeType
|
|
#define pFT_Load_Char FT_Load_Char
|
|
#define pFT_Get_Char_Index FT_Get_Char_Index
|
|
#define pFT_Set_Pixel_Sizes FT_Set_Pixel_Sizes
|
|
#define pFT_Select_Size FT_Select_Size
|
|
#define pFT_New_Face FT_New_Face
|
|
#define pFT_New_Memory_Face FT_New_Memory_Face
|
|
#define pFT_Done_Face FT_Done_Face
|
|
#else
|
|
qboolean triedtoloadfreetype;
|
|
dllhandle_t *fontmodule;
|
|
FT_Error (VARGS *pFT_Init_FreeType) (FT_Library *alibrary);
|
|
FT_Error (VARGS *pFT_Load_Char) (FT_Face face, FT_ULong char_code, FT_Int32 load_flags);
|
|
FT_UInt (VARGS *pFT_Get_Char_Index) (FT_Face face, FT_ULong charcode);
|
|
FT_Error (VARGS *pFT_Set_Pixel_Sizes) (FT_Face face, FT_UInt pixel_width, FT_UInt pixel_height);
|
|
FT_Error (VARGS *pFT_Select_Size) (FT_Face face, FT_Int strike_index);
|
|
FT_Error (VARGS *pFT_New_Face) (FT_Library library, const char *pathname, FT_Long face_index, FT_Face *aface);
|
|
FT_Error (VARGS *pFT_New_Memory_Face) (FT_Library library, const FT_Byte* file_base, FT_Long file_size, FT_Long face_index, FT_Face *aface);
|
|
FT_Error (VARGS *pFT_Done_Face) (FT_Face face);
|
|
#endif
|
|
#else
|
|
typedef unsigned int FT_Pixel_Mode; //for consistency even without freetype support.
|
|
#endif
|
|
|
|
#ifndef FT_PIXEL_MODE_GRAY
|
|
#define FT_PIXEL_MODE_GRAY 2
|
|
#endif
|
|
#ifndef FT_PIXEL_MODE_BGRA
|
|
#define FT_PIXEL_MODE_BGRA 7 //added in FT 2.5
|
|
#endif
|
|
#define FT_PIXEL_MODE_RGBA_SA (100) //RGBA, straight alpha. not in freetype.
|
|
#define FT_PIXEL_MODE_RGBA (101) //RGBA, premultiplied alpha. not in freetype.
|
|
|
|
#ifdef QUAKEHUD
|
|
static const char *imgs[] =
|
|
{
|
|
//0xe10X
|
|
"e100","e101",
|
|
"inv_shotgun",
|
|
"inv_sshotgun",
|
|
"inv_nailgun",
|
|
"inv_snailgun",
|
|
"inv_rlaunch",
|
|
"inv_srlaunch",
|
|
"inv_lightng", //8
|
|
"e109","e10a","e10b","e10c","e10d","e10e","e10f",
|
|
|
|
//0xe11X
|
|
"e110","e111",
|
|
"inv2_shotgun",
|
|
"inv2_sshotgun",
|
|
"inv2_nailgun",
|
|
"inv2_snailgun",
|
|
"inv2_rlaunch",
|
|
"inv2_srlaunch",
|
|
"inv2_lightng",
|
|
"e119","e11a","e11b","e11c","e11d","e11e","e11f",
|
|
|
|
//0xe12X
|
|
"sb_shells",
|
|
"sb_nails",
|
|
"sb_rocket",
|
|
"sb_cells",
|
|
|
|
"sb_armor1",
|
|
"sb_armor2",
|
|
"sb_armor3",
|
|
"e127","e128","e129","e12a","e12b","e12c","e12d","e12e","e12f",
|
|
|
|
//0xe13X
|
|
"sb_key1",
|
|
"sb_key2",
|
|
"sb_invis",
|
|
"sb_invuln",
|
|
"sb_suit",
|
|
"sb_quad",
|
|
|
|
"sb_sigil1",
|
|
"sb_sigil2",
|
|
"sb_sigil3",
|
|
"sb_sigil4",
|
|
"e13a","e13b","e13c","e13d","e13e","e13f",
|
|
|
|
//0xe14X
|
|
"face1",
|
|
"face_p1",
|
|
"face2",
|
|
"face_p2",
|
|
"face3",
|
|
"face_p3",
|
|
"face4",
|
|
"face_p4",
|
|
"face5",
|
|
"face_p5",
|
|
|
|
"face_invis",
|
|
"face_invul2",
|
|
"face_inv2",
|
|
"face_quad",
|
|
"e14e",
|
|
"e14f",
|
|
|
|
//0xe15X
|
|
"e150",
|
|
"e151",
|
|
"e152",
|
|
"e153",
|
|
"e154",
|
|
"e155",
|
|
"e156",
|
|
"e157",
|
|
"e158",
|
|
"e159",
|
|
"e15a",
|
|
"e15b",
|
|
"e15c",
|
|
"e15d",
|
|
"e15e",
|
|
"e15f",
|
|
|
|
//0xe16X
|
|
"e160",
|
|
"e161",
|
|
"e162",
|
|
"e163",
|
|
"e164",
|
|
"e165",
|
|
"e166",
|
|
"e167",
|
|
"e168",
|
|
"e169",
|
|
"e16a",
|
|
"e16b",
|
|
"e16c",
|
|
"e16d",
|
|
"e16e",
|
|
"e16f"
|
|
};
|
|
#endif
|
|
|
|
#define FONT_MAXCHARS 0x110000 //as defined by UTF-16, and thus applied to all unicode because UTF-16 is the crappy limited one.
|
|
#define FONTBLOCKS ((FONT_MAXCHARS+FONTBLOCKSIZE-1)/FONTBLOCKSIZE)
|
|
#define FONTBLOCKSIZE 0x100 //must be power-of-two
|
|
#define FONTBLOCKMASK (FONTBLOCKSIZE-1)
|
|
#define FONTIMAGES (1<<2) //this is total, not per font.
|
|
#define FIMAGEIDXTYPE unsigned char
|
|
#define CHARIDXTYPE unsigned int
|
|
|
|
#define INVALIDPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-1)
|
|
#define BITMAPPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-2)
|
|
#define DEFAULTPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-3)
|
|
#define SINGLEPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-4)
|
|
#define TRACKERIMAGE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-5)
|
|
#define FIMAGEWIDTH (1<<10)
|
|
#define FIMAGEHEIGHT FIMAGEWIDTH
|
|
|
|
#define FONTPLANES FONTIMAGES
|
|
#define PLANEWIDTH FIMAGEWIDTH
|
|
#define PLANEHEIGHT FIMAGEHEIGHT
|
|
|
|
//windows' font linking allows linking multiple extra fonts to a main font.
|
|
//this means that a single selected font can use chars from lots of different files if the first one(s) didn't provide that font.
|
|
//they're provided as fallbacks.
|
|
#define MAX_FACES 32
|
|
|
|
typedef struct fontface_s
|
|
{
|
|
struct fontface_s *fnext;
|
|
struct fontface_s **flink; //like prev, but not.
|
|
char name[MAX_OSPATH];
|
|
int refs;
|
|
|
|
struct
|
|
{
|
|
qbyte *data;
|
|
size_t width;
|
|
size_t height;
|
|
} horiz;
|
|
|
|
#ifdef HALFLIFEMODELS
|
|
struct
|
|
{
|
|
int fontheight1;
|
|
int imgheight;
|
|
int rows;
|
|
int fontheight2;
|
|
|
|
struct
|
|
{
|
|
qbyte x;
|
|
qbyte y;
|
|
qbyte width;
|
|
qbyte pad;
|
|
} chartab[256];
|
|
|
|
// int unk[4];
|
|
|
|
qbyte data[1];//[256*imgheight];
|
|
//int pad
|
|
//palette[256*3];
|
|
} *halflife;
|
|
#endif
|
|
|
|
#ifdef AVAIL_FREETYPE
|
|
struct
|
|
{
|
|
int activeheight; //needs reconfiguring when different sizes are used
|
|
int actualsize; //sometimes that activeheight isn't usable. :(
|
|
FT_Face face;
|
|
void *membuf;
|
|
} ft;
|
|
#endif
|
|
} fontface_t;
|
|
static fontface_t *faces;
|
|
|
|
|
|
#define GEN_CONCHAR_GLYPHS 0 //set to 0 or 1 to define whether to generate glyphs from conchars too, or if it should just draw them as glquake always used to
|
|
extern cvar_t cl_noblink;
|
|
extern cvar_t con_ocranaleds;
|
|
|
|
typedef struct font_s
|
|
{
|
|
//FIXME: use a hash table? will need it if we go beyond ucs-2.
|
|
//currently they're in a single block so the font can be checked from scanning the active chars when shutting down. we could instead scan all 65k chars in every font instead...
|
|
struct charcache_s
|
|
{
|
|
struct charcache_s *nextchar;
|
|
|
|
FIMAGEIDXTYPE texplane;
|
|
unsigned char advance; //how wide this char is, when drawn
|
|
unsigned short block; //to quickly find the char again
|
|
|
|
//texture offsets.
|
|
unsigned short bmx;
|
|
unsigned short bmy;
|
|
|
|
//texture/screen pixel sizes.
|
|
unsigned char bmw;
|
|
unsigned char bmh;
|
|
unsigned short flags;
|
|
#define CHARF_FORCEWHITE (1u<<0) //coloured emoji should not be tinted.
|
|
|
|
//screen offsets.
|
|
short top;
|
|
short left;
|
|
} *chars[FONTBLOCKS];
|
|
char name[MAX_OSPATH];
|
|
|
|
texid_t singletexture;
|
|
unsigned short charheight;
|
|
|
|
unsigned short faces;
|
|
fontface_t *face[MAX_FACES];
|
|
|
|
struct font_s *alt;
|
|
vec3_t tint;
|
|
vec3_t alttint;
|
|
} font_t;
|
|
|
|
union byte_vec4_u
|
|
{
|
|
byte_vec4_t rgba;
|
|
quint32_t c;
|
|
};
|
|
|
|
//shared between fonts.
|
|
typedef struct {
|
|
texid_t texnum[FONTIMAGES];
|
|
texid_t defaultfont;
|
|
texid_t trackerimage;
|
|
|
|
union byte_vec4_u plane[PLANEWIDTH*PLANEHEIGHT]; //tracks the current plane
|
|
FIMAGEIDXTYPE activeplane;
|
|
unsigned short planerowx;
|
|
unsigned short planerowy;
|
|
unsigned short planerowh;
|
|
qboolean planechanged;
|
|
|
|
struct charcache_s *oldestchar;
|
|
struct charcache_s *newestchar;
|
|
shader_t *shader;
|
|
shader_t *backshader;
|
|
} fontplanes_t;
|
|
static fontplanes_t fontplanes;
|
|
|
|
#define FONT_CHAR_BUFFER 512
|
|
static index_t font_indicies[FONT_CHAR_BUFFER*6];
|
|
static vecV_t font_coord[FONT_CHAR_BUFFER*4];
|
|
static vecV_t font_backcoord[FONT_CHAR_BUFFER*4];
|
|
static vec2_t font_texcoord[FONT_CHAR_BUFFER*4];
|
|
static union byte_vec4_u font_forecoloura[FONT_CHAR_BUFFER*4];
|
|
static union byte_vec4_u font_backcoloura[FONT_CHAR_BUFFER*4];
|
|
static mesh_t font_foremesh;
|
|
static mesh_t font_backmesh;
|
|
static texid_t font_texture;
|
|
static int font_colourmask;
|
|
static union byte_vec4_u font_forecolour;
|
|
static union byte_vec4_u font_backcolour;
|
|
static avec4_t font_foretint;
|
|
|
|
static struct font_s *curfont;
|
|
static float curfont_scale[2];
|
|
static qboolean curfont_scaled;
|
|
|
|
extern cvar_t r_font_linear;
|
|
|
|
//^Ue2XX
|
|
static struct
|
|
{
|
|
image_t *image;
|
|
char name[64];
|
|
} trackerimages[256];
|
|
static int numtrackerimages;
|
|
#define TRACKERFIRST 0xe200
|
|
int Font_RegisterTrackerImage(const char *image)
|
|
{
|
|
int i;
|
|
for (i = 0; i < numtrackerimages; i++)
|
|
{
|
|
if (!strcmp(trackerimages[i].name, image))
|
|
return TRACKERFIRST + i;
|
|
}
|
|
if (numtrackerimages == 256)
|
|
return 0;
|
|
trackerimages[i].image = NULL; //actually load it elsewhere, because we're lazy.
|
|
Q_strncpyz(trackerimages[i].name, image, sizeof(trackerimages[i].name));
|
|
numtrackerimages++;
|
|
return TRACKERFIRST + i;
|
|
}
|
|
//called from the font display code for tracker images
|
|
static image_t *Font_GetTrackerImage(unsigned int imid)
|
|
{
|
|
if (!trackerimages[imid].image)
|
|
{
|
|
if (!*trackerimages[imid].name)
|
|
return NULL;
|
|
trackerimages[imid].image = Image_GetTexture(trackerimages[imid].name, NULL, IF_PREMULTIPLYALPHA|IF_UIPIC, NULL, NULL, 0, 0, TF_INVALID);
|
|
}
|
|
if (!trackerimages[imid].image)
|
|
return NULL;
|
|
if (trackerimages[imid].image->status != TEX_LOADED)
|
|
return NULL;
|
|
return trackerimages[imid].image;
|
|
}
|
|
qboolean Font_TrackerValid(unsigned int imid)
|
|
{
|
|
imid -= TRACKERFIRST;
|
|
if (imid >= countof(trackerimages))
|
|
return false;
|
|
if (!trackerimages[imid].image)
|
|
{
|
|
if (!*trackerimages[imid].name)
|
|
return false;
|
|
trackerimages[imid].image = Image_GetTexture(trackerimages[imid].name, NULL, IF_PREMULTIPLYALPHA|IF_UIPIC, NULL, NULL, 0, 0, TF_INVALID);
|
|
}
|
|
if (!trackerimages[imid].image)
|
|
return false;
|
|
if (trackerimages[imid].image->status == TEX_LOADING)
|
|
COM_WorkerPartialSync(trackerimages[imid].image, &trackerimages[imid].image->status, TEX_LOADING);
|
|
if (trackerimages[imid].image->status != TEX_LOADED)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//called at load time - initalises font buffers
|
|
void Font_Init(void)
|
|
{
|
|
int i;
|
|
TEXASSIGN(fontplanes.defaultfont, r_nulltex);
|
|
|
|
//clear tracker images, just in case they were still set for the previous renderer context
|
|
for (i = 0; i < sizeof(trackerimages)/sizeof(trackerimages[0]); i++)
|
|
trackerimages[i].image = NULL;
|
|
|
|
font_foremesh.indexes = font_indicies;
|
|
font_foremesh.xyz_array = font_coord;
|
|
font_foremesh.st_array = font_texcoord;
|
|
font_foremesh.colors4b_array = &font_forecoloura->rgba;
|
|
|
|
font_backmesh.indexes = font_indicies;
|
|
font_backmesh.xyz_array = font_backcoord;
|
|
font_backmesh.st_array = font_texcoord;
|
|
font_backmesh.colors4b_array = &font_backcoloura->rgba;
|
|
|
|
for (i = 0; i < FONT_CHAR_BUFFER; i++)
|
|
{
|
|
font_indicies[i*6+0] = i*4+0;
|
|
font_indicies[i*6+1] = i*4+1;
|
|
font_indicies[i*6+2] = i*4+2;
|
|
font_indicies[i*6+3] = i*4+0;
|
|
font_indicies[i*6+4] = i*4+2;
|
|
font_indicies[i*6+5] = i*4+3;
|
|
}
|
|
|
|
for (i = 0; i < FONTPLANES; i++)
|
|
{
|
|
TEXASSIGN(fontplanes.texnum[i], Image_CreateTexture("***fontplane***", NULL, IF_UIPIC|(r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_NOPICMIP|IF_NOMIPMAP|IF_NOGAMMA));
|
|
}
|
|
|
|
fontplanes.shader = R_RegisterShader("ftefont", SUF_2D,
|
|
"{\n"
|
|
"if $nofixed\n"
|
|
"program default2d\n"
|
|
"endif\n"
|
|
"nomipmaps\n"
|
|
"{\n"
|
|
"if r_font_linear\n"
|
|
"map $linear:$diffuse\n"
|
|
"else\n"
|
|
"map $nearest:$diffuse\n"
|
|
"endif\n"
|
|
"rgbgen vertex\n"
|
|
"alphagen vertex\n"
|
|
"blendfunc gl_one gl_one_minus_src_alpha\n"
|
|
"}\n"
|
|
"}\n"
|
|
);
|
|
|
|
fontplanes.backshader = R_RegisterShader("ftefontback", SUF_2D,
|
|
"{\n"
|
|
"nomipmaps\n"
|
|
"{\n"
|
|
"map $whiteimage\n"
|
|
"rgbgen vertex\n"
|
|
"alphagen vertex\n"
|
|
"blendfunc gl_one gl_one_minus_src_alpha\n"
|
|
"}\n"
|
|
"}\n"
|
|
);
|
|
|
|
font_colourmask = ~0;
|
|
}
|
|
|
|
//flush the font buffer, by drawing it to the screen
|
|
static void Font_Flush(void)
|
|
{
|
|
R2D_Flush = NULL;
|
|
if (!font_foremesh.numindexes)
|
|
return;
|
|
if (fontplanes.planechanged)
|
|
{
|
|
Image_Upload(fontplanes.texnum[fontplanes.activeplane], TF_RGBA32, (void*)fontplanes.plane, NULL, PLANEWIDTH, PLANEHEIGHT, IF_UIPIC|IF_NEAREST|IF_NOPICMIP|IF_NOMIPMAP|IF_NOGAMMA);
|
|
|
|
fontplanes.planechanged = false;
|
|
}
|
|
font_foremesh.istrifan = (font_foremesh.numvertexes == 4);
|
|
if ((font_colourmask & (CON_RICHFORECOLOUR|CON_NONCLEARBG)) == CON_NONCLEARBG && font_foremesh.numindexes)
|
|
{
|
|
font_backmesh.numindexes = font_foremesh.numindexes;
|
|
font_backmesh.numvertexes = font_foremesh.numvertexes;
|
|
font_backmesh.istrifan = font_foremesh.istrifan;
|
|
|
|
BE_DrawMesh_Single(fontplanes.backshader, &font_backmesh, NULL, font_be_flags);
|
|
}
|
|
TEXASSIGN(fontplanes.shader->defaulttextures->base, font_texture);
|
|
BE_DrawMesh_Single(fontplanes.shader, &font_foremesh, NULL, font_be_flags);
|
|
font_foremesh.numindexes = 0;
|
|
font_foremesh.numvertexes = 0;
|
|
}
|
|
|
|
static int Font_BeginChar(texid_t tex)
|
|
{
|
|
int fvert;
|
|
|
|
if (font_foremesh.numindexes >= FONT_CHAR_BUFFER*6 || font_texture != tex)
|
|
{
|
|
Font_Flush();
|
|
TEXASSIGNF(font_texture, tex);
|
|
}
|
|
|
|
fvert = font_foremesh.numvertexes;
|
|
|
|
font_foremesh.numindexes += 6;
|
|
font_foremesh.numvertexes += 4;
|
|
|
|
return fvert;
|
|
}
|
|
|
|
//clear the shared planes and free memory etc
|
|
void Font_Shutdown(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < FONTPLANES; i++)
|
|
TEXASSIGN(fontplanes.texnum[i], r_nulltex);
|
|
fontplanes.activeplane = 0;
|
|
fontplanes.oldestchar = NULL;
|
|
fontplanes.newestchar = NULL;
|
|
fontplanes.planechanged = 0;
|
|
fontplanes.planerowx = 0;
|
|
fontplanes.planerowy = 0;
|
|
fontplanes.planerowh = 0;
|
|
}
|
|
|
|
//we got too many chars and switched to a new plane - purge the chars in that plane
|
|
void Font_FlushPlane(void)
|
|
{
|
|
/*
|
|
assumption:
|
|
oldest chars must be of the oldest plane
|
|
*/
|
|
|
|
//we've not broken anything yet, flush while we can
|
|
Font_Flush();
|
|
|
|
if (fontplanes.planechanged)
|
|
{
|
|
Image_Upload(fontplanes.texnum[fontplanes.activeplane], TF_RGBA32, (void*)fontplanes.plane, NULL, PLANEWIDTH, PLANEHEIGHT, IF_UIPIC|IF_NEAREST|IF_NOPICMIP|IF_NOMIPMAP|IF_NOGAMMA);
|
|
|
|
fontplanes.planechanged = false;
|
|
}
|
|
|
|
fontplanes.activeplane++;
|
|
fontplanes.activeplane = fontplanes.activeplane % FONTPLANES;
|
|
fontplanes.planerowh = 0;
|
|
fontplanes.planerowx = 0;
|
|
fontplanes.planerowy = 0;
|
|
|
|
while (fontplanes.oldestchar)
|
|
{
|
|
if (fontplanes.oldestchar->texplane != fontplanes.activeplane)
|
|
break;
|
|
|
|
//remove it from the list of active chars, and invalidate it
|
|
fontplanes.oldestchar->texplane = INVALIDPLANE;
|
|
fontplanes.oldestchar = fontplanes.oldestchar->nextchar;
|
|
}
|
|
if (!fontplanes.oldestchar)
|
|
fontplanes.newestchar = NULL;
|
|
}
|
|
|
|
static struct charcache_s *Font_GetCharIfLoaded(font_t *f, unsigned int charidx)
|
|
{
|
|
struct charcache_s *c = f->chars[charidx/FONTBLOCKSIZE];
|
|
if (c)
|
|
{
|
|
c += charidx&FONTBLOCKMASK;
|
|
if (c->texplane == INVALIDPLANE)
|
|
c = NULL;
|
|
}
|
|
return c;
|
|
}
|
|
static struct charcache_s *Font_GetCharStore(font_t *f, unsigned int charidx)
|
|
{ //should only be called if generating a char cache
|
|
struct charcache_s *c;
|
|
size_t i;
|
|
c = f->chars[charidx/FONTBLOCKSIZE];
|
|
if (!c)
|
|
{
|
|
c = Z_Malloc(sizeof(struct charcache_s) * FONTBLOCKSIZE);
|
|
f->chars[charidx/FONTBLOCKSIZE] = c;
|
|
for (i = 0; i < FONTBLOCKSIZE; i++)
|
|
{
|
|
c[i].texplane = INVALIDPLANE;
|
|
}
|
|
}
|
|
c += charidx&FONTBLOCKMASK;
|
|
c->block = charidx/FONTBLOCKSIZE;
|
|
return c;
|
|
}
|
|
static struct charcache_s *Font_CopyChar(font_t *f, unsigned int oldcharidx, unsigned int newcharidx)
|
|
{
|
|
struct charcache_s *new, *old = Font_GetCharIfLoaded(f, oldcharidx);
|
|
if (!old)
|
|
return NULL;
|
|
new = Font_GetCharStore(f, newcharidx);
|
|
*new = *old;
|
|
new->block = newcharidx/FONTBLOCKSIZE;
|
|
return new;
|
|
}
|
|
|
|
//loads a new image into a given character slot for the given font.
|
|
//note: make sure it doesn't already exist or things will get cyclic
|
|
//alphaonly says if its a greyscale image. false means rgba.
|
|
static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, FT_Pixel_Mode pixelmode, void *data, unsigned int bmw, unsigned int bmh, unsigned int pitch)
|
|
{
|
|
int x, y;
|
|
union byte_vec4_u *out;
|
|
struct charcache_s *c = Font_GetCharStore(f, charidx);
|
|
int pad = 0;
|
|
#define BORDERCOLOUR 0
|
|
|
|
if (fontplanes.texnum[0]->flags & IF_LINEAR)
|
|
pad += 1; //pad the image data to avoid sampling outside
|
|
|
|
if (fontplanes.planerowx + (int)bmw+pad*2 >= PLANEWIDTH)
|
|
{
|
|
fontplanes.planerowx = 0;
|
|
fontplanes.planerowy += fontplanes.planerowh;
|
|
fontplanes.planerowh = 0;
|
|
}
|
|
|
|
if (fontplanes.planerowy+(int)bmh+pad*2 >= PLANEHEIGHT)
|
|
Font_FlushPlane();
|
|
|
|
if (fontplanes.newestchar)
|
|
fontplanes.newestchar->nextchar = c;
|
|
else
|
|
fontplanes.oldestchar = c;
|
|
fontplanes.newestchar = c;
|
|
c->nextchar = NULL;
|
|
c->flags = 0;
|
|
|
|
c->texplane = fontplanes.activeplane;
|
|
c->bmx = fontplanes.planerowx+pad;
|
|
c->bmy = fontplanes.planerowy+pad;
|
|
c->bmw = bmw;
|
|
c->bmh = bmh;
|
|
|
|
if (fontplanes.planerowh < (int)bmh+pad*2)
|
|
fontplanes.planerowh = bmh+pad*2;
|
|
fontplanes.planerowx += bmw+pad*2;
|
|
|
|
out = &fontplanes.plane[c->bmx+((int)c->bmy-pad)*PLANEHEIGHT];
|
|
if (pixelmode == FT_PIXEL_MODE_GRAY)
|
|
{ //8bit font
|
|
for (y = -pad; y < 0; y++)
|
|
{
|
|
for (x = -pad; x < (int)bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
out += PLANEWIDTH;
|
|
}
|
|
for (; y < bmh; y++)
|
|
{
|
|
for (x = -pad; x < 0; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
for (; x < bmw; x++)
|
|
out[x].c = 0x01010101 * ((unsigned char*)data)[x];
|
|
for (; x < bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
data = (char*)data + pitch;
|
|
out += PLANEWIDTH;
|
|
}
|
|
for (; y < bmh+pad; y++)
|
|
{
|
|
for (x = -pad; x < (int)bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
out += PLANEWIDTH;
|
|
}
|
|
}
|
|
else if ((unsigned int)pixelmode == FT_PIXEL_MODE_RGBA_SA)
|
|
{ //rgba source using standard alpha.
|
|
//(we'll multiply out the alpha for the gpu)
|
|
for (y = -pad; y < 0; y++)
|
|
{
|
|
for (x = -pad; x < (int)bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
out += PLANEWIDTH;
|
|
}
|
|
for (; y < bmh; y++)
|
|
{
|
|
for (x = -pad; x < 0; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
for (; x < bmw; x++)
|
|
{
|
|
if (((unsigned char*)data)[x*4+3] == 255)
|
|
out[x] = ((union byte_vec4_u*)data)[x];
|
|
else
|
|
{
|
|
out[x].rgba[0] = (((unsigned char*)data)[x*4+3]*((unsigned char*)data)[x*4+0])<<8;
|
|
out[x].rgba[1] = (((unsigned char*)data)[x*4+3]*((unsigned char*)data)[x*4+1])<<8;
|
|
out[x].rgba[2] = (((unsigned char*)data)[x*4+3]*((unsigned char*)data)[x*4+2])<<8;
|
|
out[x].rgba[3] = ((unsigned char*)data)[x*4+3];
|
|
}
|
|
}
|
|
for (; x < bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
data = (char*)data + pitch;
|
|
out += PLANEWIDTH;
|
|
}
|
|
for (; y < (int)bmh+pad; y++)
|
|
{
|
|
for (x = -pad; x < (int)bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
out += PLANEWIDTH;
|
|
}
|
|
}
|
|
else if (pixelmode == FT_PIXEL_MODE_BGRA)
|
|
{ //bgra srgb font, already premultiplied
|
|
for (y = -pad; y < 0; y++)
|
|
{
|
|
for (x = -pad; x < (int)bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
out += PLANEWIDTH;
|
|
}
|
|
for (; y < bmh; y++)
|
|
{
|
|
for (x = -pad; x < 0; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
for (; x < bmw; x++)
|
|
{
|
|
out[x].rgba[0] = ((unsigned char*)data)[x*4+2];
|
|
out[x].rgba[1] = ((unsigned char*)data)[x*4+1];
|
|
out[x].rgba[2] = ((unsigned char*)data)[x*4+0];
|
|
out[x].rgba[3] = ((unsigned char*)data)[x*4+3];
|
|
}
|
|
for (; x < bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
data = (char*)data + pitch;
|
|
out += PLANEWIDTH;
|
|
}
|
|
for (; y < bmh+pad; y++)
|
|
{
|
|
for (x = -pad; x < (int)bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
out += PLANEWIDTH;
|
|
}
|
|
|
|
c->flags = CHARF_FORCEWHITE; //private glyph colours
|
|
}
|
|
else if ((unsigned int)pixelmode == FT_PIXEL_MODE_RGBA)
|
|
{ //bgra srgb font, already premultiplied
|
|
for (y = -pad; y < 0; y++)
|
|
{
|
|
for (x = -pad; x < (int)bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
out += PLANEWIDTH;
|
|
}
|
|
for (; y < bmh; y++)
|
|
{
|
|
for (x = -pad; x < 0; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
for (; x < bmw; x++)
|
|
out[x].c = ((unsigned int*)data)[x];
|
|
for (; x < bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
data = (char*)data + pitch;
|
|
out += PLANEWIDTH;
|
|
}
|
|
for (; y < bmh+pad; y++)
|
|
{
|
|
for (x = -pad; x < (int)bmw+pad; x++)
|
|
out[x].c = BORDERCOLOUR;
|
|
out += PLANEWIDTH;
|
|
}
|
|
|
|
c->flags = CHARF_FORCEWHITE; //private glyph colours
|
|
}
|
|
fontplanes.planechanged = true;
|
|
return c;
|
|
}
|
|
|
|
unsigned short hex[16] = {
|
|
/*0*/ (7<<0)|(5<<3)|(5<<6)|(5<<9)|(7<<12),
|
|
/*1*/ (2<<0)|(2<<3)|(2<<6)|(2<<9)|(2<<12),
|
|
/*2*/ (7<<0)|(1<<3)|(7<<6)|(4<<9)|(7<<12),
|
|
/*3*/ (7<<0)|(1<<3)|(3<<6)|(1<<9)|(7<<12),
|
|
/*4*/ (5<<0)|(5<<3)|(7<<6)|(1<<9)|(1<<12),
|
|
/*5*/ (7<<0)|(4<<3)|(7<<6)|(1<<9)|(7<<12),
|
|
/*6*/ (4<<0)|(4<<3)|(7<<6)|(5<<9)|(7<<12),
|
|
/*7*/ (7<<0)|(1<<3)|(1<<6)|(1<<9)|(1<<12),
|
|
/*8*/ (7<<0)|(5<<3)|(7<<6)|(5<<9)|(7<<12),
|
|
/*9*/ (7<<0)|(5<<3)|(7<<6)|(1<<9)|(1<<12),
|
|
/*A*/ (7<<0)|(5<<3)|(7<<6)|(5<<9)|(5<<12),
|
|
/*B*/ (6<<0)|(5<<3)|(7<<6)|(5<<9)|(6<<12),
|
|
/*C*/ (7<<0)|(5<<3)|(4<<6)|(5<<9)|(7<<12),
|
|
/*D*/ (6<<0)|(5<<3)|(5<<6)|(5<<9)|(6<<12),
|
|
/*E*/ (7<<0)|(4<<3)|(6<<6)|(4<<9)|(7<<12),
|
|
/*F*/ (7<<0)|(4<<3)|(6<<6)|(4<<9)|(4<<12)
|
|
};
|
|
static struct charcache_s *Font_LoadPlaceholderGlyph(font_t *f, CHARIDXTYPE charidx)
|
|
{
|
|
struct charcache_s *c;
|
|
unsigned int out[169*4], i, o, g, b, w, h, d, n;
|
|
if (charidx == 0xe080 || charidx == 0xe081 || charidx == 0xe082 || charidx == 0xe083)
|
|
{ //scrollbar glyps
|
|
w = min(16,f->charheight);
|
|
h = max(1,w/8);
|
|
d = 0;
|
|
|
|
if (charidx == 0xe083) //centre
|
|
{
|
|
h *= 3;
|
|
memset(out+w*h*0, 0xff, w*4*h);
|
|
}
|
|
else
|
|
{
|
|
memset(out+w*h*0, 0xff, w*4*h);
|
|
memset(out+w*h*1, 0x00, w*4*h);
|
|
memset(out+w*h*2, 0xff, w*4*h);
|
|
for (i = 0; i < h; i++)
|
|
{
|
|
if (charidx == 0xe080) //left
|
|
memset(out+w*(i+h*1), 0xff, h*4);
|
|
if (charidx == 0xe082) //right
|
|
memset(out+w*(i+h*1)+w-h, 0xff, h*4);
|
|
}
|
|
h *= 3;
|
|
}
|
|
}
|
|
else if (charidx == 0xe01d || charidx == 0xe01e || charidx == 0xe01f)
|
|
{ //horizontal separator
|
|
w = min(16,f->charheight);
|
|
h = max(1,w/8);
|
|
d = 0;
|
|
memset(out, 0xff, w*4*h);
|
|
}
|
|
else if (charidx == 0xe00b)
|
|
{ //console input cursor
|
|
h = min(16,f->charheight);
|
|
w = max(1,h/8);
|
|
d = 1;
|
|
memset(out, 0xff, w*4*h);
|
|
}
|
|
else if (charidx == 0xe00d)
|
|
{ //filled > arrow indicator (used by the menus)
|
|
h = min(16,f->charheight);
|
|
w = (h+1)/2;
|
|
d = 1;
|
|
memset(out, 0xff, w*4*h);
|
|
for (i = 0; i < w; i++)
|
|
{
|
|
memset(out + w*i + (i+1), 0x0, (w-i-1)*4);
|
|
memset(out + w*(h-i-1) + (i+1), 0x0, (w-i-1)*4);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
d = (f->charheight >= 11);
|
|
if (d)
|
|
{ //two rows
|
|
h = 11;
|
|
if (charidx > 0xffff)
|
|
n = 3;
|
|
else if (charidx > 0xff)
|
|
n = 2;
|
|
else
|
|
n = 1;
|
|
w = n*4-1;
|
|
n*=2;
|
|
}
|
|
else
|
|
{ //single row. bye bye fixed-width.
|
|
if (charidx > 0xfffff)
|
|
n = 6;
|
|
else if (charidx > 0xffff)
|
|
n = 5;
|
|
else if (charidx > 0xfff)
|
|
n = 4;
|
|
else if (charidx > 0xff)
|
|
n = 3;
|
|
else
|
|
n = 2;
|
|
w = n*4-1;
|
|
h = 5;
|
|
}
|
|
|
|
//figure out if we can get away with giving it a little more border to boost readability
|
|
b = (h+2 < f->charheight);
|
|
w += b*2;
|
|
h += b*2;
|
|
|
|
memset(out, 0xff, sizeof(out));
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
g = hex[0xf & (charidx>>(i<<2))];
|
|
o = b + w*b;
|
|
if (d)
|
|
{ //stradle them over the two rows.
|
|
if (i >= (n>>1))
|
|
o += 4*(n-i-1);
|
|
else
|
|
{
|
|
o += 4*((n>>1)-i-1);
|
|
o += w * 6;
|
|
}
|
|
}
|
|
else
|
|
o += 4*(n-i-1); //just arrange them in order
|
|
for (; g; g>>=3, o+=w)
|
|
{
|
|
if (g & 4) out[o+0] = 0xff0000ff;
|
|
if (g & 2) out[o+1] = 0xff0000ff;
|
|
if (g & 1) out[o+2] = 0xff0000ff;
|
|
}
|
|
}
|
|
|
|
d = 1;
|
|
}
|
|
c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out, w, h, w*4);
|
|
if (c)
|
|
{
|
|
c->advance = w+d;
|
|
c->left = 0;
|
|
c->top = (f->charheight-h-1)/2;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
//loads the given charidx for the given font, importing from elsewhere if needed.
|
|
static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx)
|
|
{
|
|
struct charcache_s *c;
|
|
|
|
#if GEN_CONCHAR_GLYPHS != 0
|
|
if (charidx >= 0xe000 && charidx <= 0xe0ff)
|
|
{
|
|
int cpos = charidx & 0xff;
|
|
unsigned int img[64*64], *d;
|
|
unsigned char *s;
|
|
int scale;
|
|
int x,y, ys;
|
|
qbyte *draw_chars = W_GetLumpName("conchars");
|
|
if (draw_chars)
|
|
{
|
|
d = img;
|
|
s = draw_chars + 8*(cpos&15)+128*8*(cpos/16);
|
|
|
|
scale = f->charheight/8;
|
|
if (scale < 1)
|
|
scale = 1;
|
|
if (scale > 64/8)
|
|
scale = 64/8;
|
|
|
|
for (y = 0; y < 8; y++)
|
|
{
|
|
for (ys = 0; ys < scale; ys++)
|
|
{
|
|
for (x = 0; x < 8*scale; x++)
|
|
d[x] = d_8to24rgbtable[s[x/scale]];
|
|
d+=8*scale;
|
|
}
|
|
s+=128;
|
|
}
|
|
c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA_SA, img, 8*scale, 8*scale, 8*scale*4);
|
|
if (c)
|
|
{
|
|
c->advance = 8*scale;
|
|
c->left = 0;
|
|
c->top = 7*scale;
|
|
}
|
|
return c;
|
|
}
|
|
if ((charidx&0x7f) > 0x20)
|
|
charidx &= 0x7f;
|
|
}
|
|
#endif
|
|
|
|
#ifdef QUAKEHUD
|
|
if (charidx >= 0xe100 && charidx <= 0xe1ff)
|
|
{
|
|
qpic_t *wadimg;
|
|
qbyte lumptype = 0;
|
|
unsigned char *src;
|
|
unsigned int img[64*64];
|
|
int nw, nh;
|
|
int x, y;
|
|
unsigned int stepx, stepy;
|
|
unsigned int srcx, srcy;
|
|
size_t lumpsize = 0;
|
|
|
|
if (charidx-0xe100 >= sizeof(imgs)/sizeof(imgs[0]))
|
|
wadimg = NULL;
|
|
else
|
|
wadimg = W_GetLumpName(imgs[charidx-0xe100], &lumpsize, &lumptype);
|
|
if (wadimg && lumptype == TYP_QPIC && lumpsize == 8+wadimg->height*wadimg->width)
|
|
{
|
|
nh = wadimg->height;
|
|
nw = wadimg->width;
|
|
while (nh < f->charheight)
|
|
{
|
|
nh *= 2;
|
|
nw *= 2;
|
|
}
|
|
if (nh > f->charheight)
|
|
{
|
|
nw = (nw * f->charheight)/nh;
|
|
nh = f->charheight;
|
|
}
|
|
stepy = 0x10000*((float)wadimg->height/nh);
|
|
stepx = 0x10000*((float)wadimg->width/nw);
|
|
if (nh > 64)
|
|
nh = 64;
|
|
if (nw > 64)
|
|
nw = 64;
|
|
srcy = 0;
|
|
for (y = 0; y < nh; y++)
|
|
{
|
|
src = (unsigned char *)(wadimg->data);
|
|
src += wadimg->width * (srcy>>16);
|
|
srcy += stepy;
|
|
srcx = 0;
|
|
for (x = 0; x < nw; x++)
|
|
{
|
|
img[x+y*64] = d_8to24rgbtable[src[srcx>>16]];
|
|
srcx += stepx;
|
|
}
|
|
}
|
|
|
|
c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA_SA, img, nw, nh, 64*4);
|
|
if (c)
|
|
{
|
|
c->flags = CHARF_FORCEWHITE; //private glyph colours
|
|
c->left = 0;
|
|
c->top = f->charheight - nh;
|
|
c->advance = nw;
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
/*make tab invisible*/
|
|
if (charidx == '\t' || charidx == '\n')
|
|
{
|
|
c = &f->chars[charidx/FONTBLOCKSIZE][charidx&FONTBLOCKMASK];
|
|
c->left = 0;
|
|
c->advance = f->charheight;
|
|
c->top = 0;
|
|
|
|
c->texplane = 0;
|
|
c->bmx = 0;
|
|
c->bmy = 0;
|
|
c->bmw = 0;
|
|
c->bmh = 0;
|
|
return c;
|
|
}
|
|
|
|
if (f->faces)
|
|
{
|
|
int file;
|
|
for (file = 0; file < f->faces; file++)
|
|
{
|
|
fontface_t *qface = f->face[file];
|
|
#ifdef AVAIL_FREETYPE
|
|
if (qface->ft.face)
|
|
{
|
|
FT_Face face = qface->ft.face;
|
|
|
|
if (qface->ft.activeheight != f->charheight)
|
|
{
|
|
qface->ft.activeheight = f->charheight;
|
|
if (FT_HAS_FIXED_SIZES(face) && !FT_IS_SCALABLE(face))
|
|
{ //freetype doesn't like scaling these for us, so we have to pick a usable size ourselves.
|
|
FT_Int best = 0, s;
|
|
int bestheight = 0, h;
|
|
for (s = 0; s < qface->ft.face->num_fixed_sizes; s++)
|
|
{
|
|
h = qface->ft.face->available_sizes[s].height;
|
|
//always try to pick the smallest size that is also >= our target size
|
|
if ((h > bestheight && bestheight < f->charheight) || (h >= f->charheight && h < bestheight))
|
|
{
|
|
bestheight = h;
|
|
best = s;
|
|
}
|
|
}
|
|
qface->ft.actualsize = qface->ft.face->available_sizes[best].height;
|
|
pFT_Select_Size(face, best);
|
|
}
|
|
else
|
|
{
|
|
pFT_Set_Pixel_Sizes(face, 0, f->charheight);
|
|
qface->ft.actualsize = f->charheight;
|
|
}
|
|
}
|
|
if (charidx == 0xfffe || pFT_Get_Char_Index(face, charidx)) //ignore glyph 0 (undefined)
|
|
if (pFT_Load_Char(face, charidx, FT_LOAD_RENDER|FT_LOAD_COLOR) == 0)
|
|
{
|
|
FT_GlyphSlot slot;
|
|
FT_Bitmap *bm;
|
|
|
|
slot = face->glyph;
|
|
bm = &slot->bitmap;
|
|
if (qface->ft.activeheight!=qface->ft.actualsize)
|
|
{
|
|
//I'm just going to assume full-height raster glyphs here. I'm sure I'll be proven wrong some time but w/e.
|
|
int nw, nh;
|
|
if (bm->rows)
|
|
{
|
|
nh = f->charheight;
|
|
nw = (bm->width*nh)/bm->rows;
|
|
}
|
|
else
|
|
nw = f->charheight, nh = 0;
|
|
if (!nw || !nh)
|
|
c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_GRAY, NULL, nw, nh, 0);
|
|
else if (bm->pixel_mode == FT_PIXEL_MODE_BGRA)
|
|
{
|
|
unsigned int *out = alloca(nw*nh*sizeof(*out));
|
|
Image_ResampleTexture((void*)bm->buffer, bm->width, bm->rows, out, nw, nh);
|
|
c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, out, nw, nh, nw*sizeof(*out));
|
|
}
|
|
else if (bm->pixel_mode == FT_PIXEL_MODE_GRAY)
|
|
{
|
|
unsigned char *out = alloca(nw*nh*sizeof(*out));
|
|
Image_ResampleTexture8((void*)bm->buffer, bm->width, bm->rows, out, nw, nh);
|
|
c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, out, nw, nh, nw*sizeof(*out));
|
|
}
|
|
else
|
|
c = NULL;
|
|
if (c)
|
|
{
|
|
c->advance = nw;
|
|
c->left = 0;
|
|
c->top = 0;
|
|
return c;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, bm->buffer, bm->width, bm->rows, bm->pitch);
|
|
|
|
if (c)
|
|
{
|
|
c->advance = slot->advance.x >> 6;
|
|
c->left = slot->bitmap_left;
|
|
c->top = f->charheight*3/4 - slot->bitmap_top;
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef HALFLIFEMODELS
|
|
if (qface->halflife)
|
|
{
|
|
size_t glyph = charidx;
|
|
if (glyph > 0xe000)
|
|
glyph -= 0xe000;
|
|
if (glyph < 0x100)
|
|
{
|
|
int gw = qface->halflife->chartab[glyph].width;
|
|
int gh = qface->halflife->fontheight1;
|
|
qbyte *in = qface->halflife->data + 256 * qface->halflife->chartab[glyph].y + qface->halflife->chartab[glyph].x;
|
|
qbyte *pal = qface->halflife->data + 256 * qface->halflife->imgheight+2;
|
|
qbyte *out = alloca(gw*gh*4);
|
|
int x, y;
|
|
for (y = 0; y < gh; y++, in += 256-gw)
|
|
for (x = 0; x < gw; x++, in++)
|
|
{
|
|
if (*in==0xff)
|
|
{
|
|
out[(x+y*gw)*4+0] = 0;
|
|
out[(x+y*gw)*4+1] = 0;
|
|
out[(x+y*gw)*4+2] = 0;
|
|
out[(x+y*gw)*4+3] = 0;
|
|
}
|
|
else
|
|
{
|
|
out[(x+y*gw)*4+0] = pal[*in*3+0];
|
|
out[(x+y*gw)*4+1] = pal[*in*3+1];
|
|
out[(x+y*gw)*4+2] = pal[*in*3+2];
|
|
out[(x+y*gw)*4+3] = 0xff;
|
|
}
|
|
}
|
|
|
|
if (f->charheight != gh)
|
|
{
|
|
int ngw = (gw * f->charheight) / gh;
|
|
int ngh = f->charheight;
|
|
qbyte *out2 = alloca(ngw*ngh*4);
|
|
if (ngw&&ngh)
|
|
Image_ResampleTexture((unsigned int *)out, gw, gh, (unsigned int *)out2, ngw, ngh);
|
|
c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out2, ngw, ngh, ngw*4);
|
|
gw = ngw;
|
|
}
|
|
else
|
|
c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out, gw, gh, gw*4);
|
|
if (c)
|
|
{
|
|
c->flags = 0; //private glyph colours
|
|
c->advance = gw;
|
|
c->left = 0;
|
|
c->top = 0;
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
if (qface->horiz.data)
|
|
{
|
|
#if 1
|
|
size_t maxchar = 256;
|
|
int gw = qface->horiz.width/maxchar;
|
|
#else
|
|
int gw = qface->horiz.height-(qface->horiz.height/12);
|
|
size_t maxchar = qface->horiz.width / gw;
|
|
#endif
|
|
size_t glyph = charidx /* - qface->horiz.firstcodepoint*/;
|
|
if (glyph < maxchar)
|
|
{
|
|
unsigned int *glyphdata = (unsigned int*)qface->horiz.data + glyph*gw;
|
|
int gh = qface->horiz.height;
|
|
int gs = qface->horiz.width;
|
|
unsigned int *out = glyphdata;
|
|
while (gw >= 1)
|
|
{
|
|
int y;
|
|
gw--; //see if we can strip this column
|
|
for (y = 0; y < gh; y++)
|
|
if (glyphdata[gw+y*gs] & 0x00ffffff)
|
|
break;
|
|
if (y < gh)
|
|
{
|
|
gw++;
|
|
break;
|
|
}
|
|
}
|
|
if (f->charheight != gh)
|
|
{
|
|
int ngw = (gw * f->charheight) / gh;
|
|
int ngh = f->charheight;
|
|
int x, y;
|
|
unsigned int *out2 = alloca(ngw*ngh*4);
|
|
if (ngw&&ngh)
|
|
{ //we need to repack the input, because Image_ResampleTexture can't handle strides
|
|
unsigned int *out1 = alloca(gw*gh*4);
|
|
for (y = 0; y < gh; y++)
|
|
for (x = 0; x < gw; x++)
|
|
out1[x+y*gw] = out[x+y*gs];
|
|
Image_ResampleTexture((unsigned int *)out1, gw, gh, (unsigned int *)out2, ngw, ngh);
|
|
}
|
|
c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out2, ngw, ngh, ngw*4);
|
|
gw = ngw;
|
|
}
|
|
else
|
|
c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out, gw, gh, gs*4);
|
|
if (!gw) //for invisble glyphs (eg: space), we attempt to ensure that there's some substance there. missing spaces is weird.
|
|
gw = gh/3;
|
|
if (c)
|
|
{
|
|
c->advance = gw;
|
|
c->left = 0;
|
|
c->top = 0;
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (charidx == '\r')
|
|
return Font_CopyChar(f, charidx|0xe000, charidx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//obtains a cached char, null if not cached
|
|
static struct charcache_s *Font_GetChar(font_t *f, unsigned int codepoint)
|
|
{
|
|
CHARIDXTYPE charidx;
|
|
struct charcache_s *c;
|
|
if (codepoint > FONT_MAXCHARS)
|
|
charidx = 0xfffd;
|
|
else
|
|
charidx = codepoint;
|
|
c = Font_GetCharIfLoaded(f, charidx);
|
|
if (!c)
|
|
{
|
|
if (charidx >= TRACKERFIRST && charidx < TRACKERFIRST+100)
|
|
{
|
|
static struct charcache_s tc;
|
|
tc.texplane = TRACKERIMAGE;
|
|
fontplanes.trackerimage = Font_GetTrackerImage(charidx-TRACKERFIRST);
|
|
if (!fontplanes.trackerimage)
|
|
return Font_GetChar(f, '?');
|
|
tc.advance = fontplanes.trackerimage->width * ((float)f->charheight / fontplanes.trackerimage->height);
|
|
return &tc;
|
|
}
|
|
|
|
//not cached, can't get.
|
|
c = Font_TryLoadGlyph(f, charidx);
|
|
|
|
if (!c && charidx >= 0x400 && charidx <= 0x45f)
|
|
{ //apparently there's a lot of russian players out there.
|
|
//if we just replace all their chars with a '?', they're gonna get pissed.
|
|
//so lets at least attempt to provide some default mapping that makes sense even if they don't have a full font.
|
|
//koi8-u is a mapping useful with 7-bit email because the message is still vaugely readable in latin if the high bits get truncated.
|
|
//not being a language specialist, I'm just going to use that mapping, with the high bit truncated to ascii (which mostly exists in the quake charset).
|
|
//this exact table is from ezquake. because I'm too lazy to figure out the proper mapping. (beware of triglyphs)
|
|
static char *wc2koi_table =
|
|
"?3??4?67??" "??" "??" ">?"
|
|
"abwgdevzijklmnop"
|
|
"rstufhc~{}/yx|`q"
|
|
"ABWGDEVZIJKLMNOP"
|
|
"RSTUFHC^[]_YX\\@Q"
|
|
"?#??$?&'??" "??" "??.?";
|
|
charidx = wc2koi_table[charidx - 0x400];
|
|
if (charidx != '?')
|
|
{
|
|
c = Font_GetCharIfLoaded(f, charidx);
|
|
if (!c)
|
|
c = Font_TryLoadGlyph(f, charidx);
|
|
}
|
|
}
|
|
if (!c)
|
|
c = Font_LoadPlaceholderGlyph(f, charidx);
|
|
if (!c)
|
|
{
|
|
charidx = 0xfffd; //unicode's replacement char
|
|
c = Font_GetCharIfLoaded(f, charidx);
|
|
if (!c)
|
|
c = Font_TryLoadGlyph(f, charidx);
|
|
}
|
|
if (!c)
|
|
{
|
|
charidx = '?'; //meh
|
|
c = Font_GetCharIfLoaded(f, charidx);
|
|
if (!c)
|
|
c = Font_TryLoadGlyph(f, charidx);
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
qboolean Font_LoadHorizontalFont(struct font_s *f, int fheight, const char *fontfilename)
|
|
{ //halflife-style.
|
|
fontface_t *qface;
|
|
void *rawdata;
|
|
qofs_t rawsize;
|
|
qbyte *rgbadata = NULL;
|
|
int width=0,height=0;
|
|
uploadfmt_t format;
|
|
|
|
if (fheight < 1)
|
|
fheight = 1;
|
|
|
|
//ran out of font slots.
|
|
if (f->faces == MAX_FACES)
|
|
return false;
|
|
|
|
for (qface = faces; qface; qface = qface->fnext)
|
|
{
|
|
if (!strcmp(qface->name, fontfilename) && qface->horiz.data)
|
|
{
|
|
qface->refs++;
|
|
f->face[f->faces++] = qface;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
rawdata = FS_MallocFile(fontfilename, FS_GAME, &rawsize);
|
|
if (rawdata)
|
|
rgbadata = ReadRawImageFile(rawdata, rawsize, &width, &height, &format, true, fontfilename);
|
|
FS_FreeFile(rawdata);
|
|
|
|
if (rgbadata)
|
|
{
|
|
/*success!*/
|
|
qface = Z_Malloc(sizeof(*qface));
|
|
qface->flink = &faces;
|
|
qface->fnext = *qface->flink;
|
|
*qface->flink = qface;
|
|
if (qface->fnext)
|
|
qface->fnext->flink = &qface->fnext;
|
|
qface->horiz.data = rgbadata;
|
|
qface->horiz.width = width;
|
|
qface->horiz.height = height;
|
|
qface->refs++;
|
|
Q_strncpyz(qface->name, fontfilename, sizeof(qface->name));
|
|
|
|
f->face[f->faces++] = qface;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef AVAIL_FREETYPE
|
|
qboolean Font_LoadFreeTypeFont(struct font_s *f, int height, const char *fontfilename)
|
|
{
|
|
fontface_t *qface;
|
|
FT_Face face = NULL;
|
|
FT_Error error;
|
|
flocation_t loc;
|
|
void *fbase = NULL;
|
|
if (!*fontfilename)
|
|
return false;
|
|
|
|
if (height < 1)
|
|
height = 1;
|
|
|
|
//ran out of font slots.
|
|
if (f->faces == MAX_FACES)
|
|
return false;
|
|
|
|
for (qface = faces; qface; qface = qface->fnext)
|
|
{
|
|
if (!strcmp(qface->name, fontfilename) && qface->ft.face)
|
|
{
|
|
qface->refs++;
|
|
f->face[f->faces++] = qface;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!fontlib)
|
|
{
|
|
#ifndef FREETYPE_STATIC
|
|
dllfunction_t ft2funcs[] =
|
|
{
|
|
{(void**)&pFT_Init_FreeType, "FT_Init_FreeType"},
|
|
{(void**)&pFT_Load_Char, "FT_Load_Char"},
|
|
{(void**)&pFT_Get_Char_Index, "FT_Get_Char_Index"},
|
|
{(void**)&pFT_Set_Pixel_Sizes, "FT_Set_Pixel_Sizes"},
|
|
{(void**)&pFT_Select_Size, "FT_Select_Size"},
|
|
{(void**)&pFT_New_Face, "FT_New_Face"},
|
|
{(void**)&pFT_New_Memory_Face, "FT_New_Memory_Face"},
|
|
{(void**)&pFT_Init_FreeType, "FT_Init_FreeType"},
|
|
{(void**)&pFT_Done_Face, "FT_Done_Face"},
|
|
{NULL, NULL}
|
|
};
|
|
if (triedtoloadfreetype)
|
|
return false;
|
|
triedtoloadfreetype = true;
|
|
|
|
#ifdef _WIN32
|
|
fontmodule = Sys_LoadLibrary("libfreetype-6", ft2funcs);
|
|
if (!fontmodule)
|
|
fontmodule = Sys_LoadLibrary("freetype6", ft2funcs);
|
|
#else
|
|
fontmodule = Sys_LoadLibrary("libfreetype.so.6", ft2funcs);
|
|
#endif
|
|
if (!fontmodule)
|
|
{
|
|
Con_DPrintf("Couldn't load freetype library.\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
error = pFT_Init_FreeType(&fontlib);
|
|
if (error)
|
|
{
|
|
Con_Printf("FT_Init_FreeType failed.\n");
|
|
#ifndef FREETYPE_STATIC
|
|
Sys_CloseLibrary(fontmodule);
|
|
#endif
|
|
return false;
|
|
}
|
|
/*any other errors leave freetype open*/
|
|
}
|
|
|
|
error = FT_Err_Cannot_Open_Resource;
|
|
if (FS_FLocateFile(fontfilename, FSLF_IFFOUND, &loc) || FS_FLocateFile(va("%s.ttf", fontfilename), FSLF_IFFOUND, &loc))
|
|
{
|
|
if (*loc.rawname && !loc.offset)
|
|
{
|
|
fbase = NULL;
|
|
/*File is directly fopenable with no bias (not in a pk3/pak). Use the system-path form, so we don't have to eat the memory cost*/
|
|
error = pFT_New_Face(fontlib, loc.rawname, 0, &face);
|
|
}
|
|
else
|
|
{
|
|
/*File is inside an archive, we need to read it and pass it as memory (and keep it available)*/
|
|
vfsfile_t *f;
|
|
f = FS_OpenReadLocation(&loc);
|
|
if (f && loc.len > 0)
|
|
{
|
|
fbase = BZ_Malloc(loc.len);
|
|
VFS_READ(f, fbase, loc.len);
|
|
VFS_CLOSE(f);
|
|
|
|
error = pFT_New_Memory_Face(fontlib, fbase, loc.len, 0, &face);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
if (error)
|
|
{
|
|
static qboolean firsttime = true;
|
|
static char fontdir[MAX_OSPATH];
|
|
|
|
if (firsttime)
|
|
{
|
|
HRESULT (WINAPI *dSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
|
|
dllfunction_t shfolderfuncs[] =
|
|
{
|
|
{(void**)&dSHGetFolderPath, "SHGetFolderPathA"},
|
|
{NULL, NULL}
|
|
};
|
|
dllhandle_t *shfolder = Sys_LoadLibrary("shfolder.dll", shfolderfuncs);
|
|
|
|
firsttime = false;
|
|
if (shfolder)
|
|
{
|
|
// 0x14 == CSIDL_FONTS
|
|
if (dSHGetFolderPath(NULL, 0x14, NULL, 0, fontdir) != S_OK)
|
|
*fontdir = 0;
|
|
Sys_CloseLibrary(shfolder);
|
|
}
|
|
}
|
|
if (*fontdir)
|
|
{
|
|
error = pFT_New_Face(fontlib, va("%s/%s", fontdir, fontfilename), 0, &face);
|
|
if (error)
|
|
error = pFT_New_Face(fontlib, va("%s/%s.ttf", fontdir, fontfilename), 0, &face);
|
|
}
|
|
}
|
|
#else
|
|
if (error)
|
|
{ //eg: /usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf
|
|
error = pFT_New_Face(fontlib, va("/usr/share/fonts/%s", fontfilename), 0, &face);
|
|
if (error)
|
|
error = pFT_New_Face(fontlib, va("/usr/share/fonts/truetype/%s.ttf", fontfilename), 0, &face);
|
|
if (error) //just to give a chance of the same names working on more than one os, with the right package installed.
|
|
error = pFT_New_Face(fontlib, va("/usr/share/fonts/truetype/msttcorefonts/%s.ttf", fontfilename), 0, &face);
|
|
}
|
|
#endif
|
|
if (!error)
|
|
{
|
|
if (FT_HAS_FIXED_SIZES(face) && !FT_IS_SCALABLE(face))
|
|
{
|
|
height = 0; //will need to rescale manually I guess
|
|
error = pFT_Select_Size(face, 0);
|
|
}
|
|
else
|
|
error = pFT_Set_Pixel_Sizes(face, 0, height);
|
|
if (!error)
|
|
{
|
|
/*success!*/
|
|
qface = Z_Malloc(sizeof(*qface));
|
|
qface->flink = &faces;
|
|
qface->fnext = *qface->flink;
|
|
*qface->flink = qface;
|
|
if (qface->fnext)
|
|
qface->fnext->flink = &qface->fnext;
|
|
qface->ft.face = face;
|
|
qface->ft.activeheight = qface->ft.actualsize = height;
|
|
qface->ft.membuf = fbase;
|
|
qface->refs++;
|
|
Q_strncpyz(qface->name, fontfilename, sizeof(qface->name));
|
|
|
|
f->face[f->faces++] = qface;
|
|
return true;
|
|
}
|
|
}
|
|
if (error && error != FT_Err_Cannot_Open_Resource)
|
|
Con_Printf("Freetype error: %i\n", error);
|
|
if (fbase)
|
|
BZ_Free(fbase);
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static texid_t Font_LoadReplacementConchars(void)
|
|
{
|
|
texid_t tex;
|
|
//q1 replacement
|
|
tex = R_LoadHiResTexture("gfx/conchars.lmp", NULL, (r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA);
|
|
TEXDOWAIT(tex);
|
|
if (TEXLOADED(tex))
|
|
return tex;
|
|
//q2
|
|
tex = R_LoadHiResTexture("pics/conchars.pcx", NULL, (r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA);
|
|
TEXDOWAIT(tex);
|
|
if (TEXLOADED(tex))
|
|
return tex;
|
|
//q3
|
|
tex = R_LoadHiResTexture("gfx/2d/bigchars.tga", NULL, (r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA);
|
|
TEXDOWAIT(tex);
|
|
if (TEXLOADED(tex))
|
|
return tex;
|
|
return r_nulltex;
|
|
}
|
|
static texid_t Font_LoadQuakeConchars(void)
|
|
{
|
|
/*unsigned int i;
|
|
qbyte *lump;
|
|
lump = W_SafeGetLumpName ("conchars");
|
|
if (lump)
|
|
{
|
|
// add ocrana leds
|
|
if (con_ocranaleds.ival)
|
|
{
|
|
if (con_ocranaleds.ival != 2 || QCRC_Block(lump, 128*128) == 798)
|
|
AddOcranaLEDsIndexed (lump, 128, 128);
|
|
}
|
|
|
|
for (i=0 ; i<128*128 ; i++)
|
|
if (lump[i] == 0)
|
|
lump[i] = 255; // proper transparent color
|
|
|
|
return R_LoadTexture8("charset", 128, 128, (void*)lump, IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA, 1);
|
|
}*/
|
|
return r_nulltex;
|
|
}
|
|
|
|
#ifdef HEXEN2
|
|
static texid_t Font_LoadHexen2Conchars(qboolean iso88591)
|
|
{
|
|
//gulp... so it's come to this has it? rework the hexen2 conchars into the q1 system.
|
|
texid_t tex;
|
|
unsigned int i, x;
|
|
unsigned char *tempchars;
|
|
unsigned char *in, *out, *outbuf;
|
|
FS_LoadFile("gfx/menu/conchars.lmp", (void**)&tempchars);
|
|
|
|
/*hexen2's conchars are arranged 32-wide, 16 high.
|
|
the upper 8 rows are 256 8859-1 chars
|
|
the lower 8 rows are a separate set of recoloured 8859-1 chars.
|
|
|
|
if we're loading for the fallback then we're loading this data for quake compatibility,
|
|
so we grab only the first 4 rows of each set of chars (128 low chars, 128 high chars).
|
|
|
|
if we're loading a proper charset, then we load only the first set of chars, we can recolour the rest anyway (com_parseutf8 will do so anyway).
|
|
as a final note, parsing iso8859-1 french/german/etc as utf8 will generally result in decoding errors which can gracefully revert to 8859-1 safely. If this premise fails too much, we can always change the parser for different charsets - the engine always uses unicode and thus 8859-1 internally.
|
|
*/
|
|
if (tempchars)
|
|
{
|
|
outbuf = BZ_Malloc(8*8*256*8);
|
|
|
|
out = outbuf;
|
|
i = 0;
|
|
/*read the low chars*/
|
|
for (; i < 8*8*1; i+=1)
|
|
{
|
|
if (i&(1<<3))
|
|
in = tempchars + (i>>3)*16*8*8+(i&7)*32*8 - 256*4+128;
|
|
else
|
|
in = tempchars + (i>>3)*16*8*8+(i&7)*32*8;
|
|
for (x = 0; x < 16*8; x++)
|
|
*out++ = *in++;
|
|
}
|
|
if (iso88591)
|
|
{
|
|
/*read the non 8859-1 quake-compat control chars*/
|
|
for (; i < 8*8*1 + 16; i+=1)
|
|
{
|
|
if (i&(1<<3))
|
|
in = tempchars+128*128 + ((i>>3)&7)*16*8*8+(i&7)*32*8 - 256*4+128;
|
|
else
|
|
in = tempchars+128*128 + ((i>>3)&7)*16*8*8+(i&7)*32*8;
|
|
for (x = 0; x < 16*8; x++)
|
|
*out++ = *in++;
|
|
}
|
|
|
|
/*read the final low chars (final if 8859-1 anyway)*/
|
|
for (; i < 8*8*2; i+=1)
|
|
{
|
|
if (i&(1<<3))
|
|
in = tempchars + (i>>3)*16*8*8+(i&7)*32*8 - 256*4+128;
|
|
else
|
|
in = tempchars + (i>>3)*16*8*8+(i&7)*32*8;
|
|
for (x = 0; x < 16*8; x++)
|
|
*out++ = *in++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*read the high chars*/
|
|
for (; i < 8*8*2; i+=1)
|
|
{
|
|
if (i&(1<<3))
|
|
in = tempchars+128*128 + ((i>>3)&15)*16*8*8+(i&7)*32*8 - 256*4+128;
|
|
else
|
|
in = tempchars+128*128 + ((i>>3)&15)*16*8*8+(i&7)*32*8;
|
|
for (x = 0; x < 16*8; x++)
|
|
*out++ = *in++;
|
|
}
|
|
}
|
|
FS_FreeFile(tempchars);
|
|
|
|
// add ocrana leds
|
|
if (!iso88591 && con_ocranaleds.value && con_ocranaleds.value != 2)
|
|
AddOcranaLEDsIndexed (outbuf, 128, 128);
|
|
|
|
for (i=0 ; i<128*128 ; i++)
|
|
if (outbuf[i] == 0)
|
|
outbuf[i] = 255; // proper transparent color
|
|
tex = R_LoadTexture8 (iso88591?"gfx/menu/8859-1.lmp":"charset", 128, 128, outbuf, IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA, 1);
|
|
Z_Free(outbuf);
|
|
return tex;
|
|
}
|
|
return r_nulltex;
|
|
}
|
|
#endif
|
|
|
|
FTE_ALIGN(4) qbyte default_conchar[/*11356*/] =
|
|
{
|
|
#include "lhfont.h"
|
|
};
|
|
|
|
static void Font_CopyGlyph(int src, int dst, void *data)
|
|
{
|
|
int glyphsize = 16;
|
|
int y;
|
|
int x;
|
|
char *srcptr = (char*)data + (src&15)*glyphsize*4 + (src>>4)*glyphsize*256*4;
|
|
char *dstptr = (char*)data + (dst&15)*glyphsize*4 + (dst>>4)*glyphsize*256*4;
|
|
for (y = 0; y < glyphsize; y++)
|
|
{
|
|
for (x = 0; x < glyphsize; x++)
|
|
{
|
|
dstptr[x*4+0] = srcptr[x*4+0];
|
|
dstptr[x*4+1] = srcptr[x*4+1];
|
|
dstptr[x*4+2] = srcptr[x*4+2];
|
|
dstptr[x*4+3] = srcptr[x*4+3];
|
|
}
|
|
dstptr += 256*4;
|
|
srcptr += 256*4;
|
|
}
|
|
}
|
|
static texid_t Font_LoadFallbackConchars(void)
|
|
{
|
|
texid_t tex;
|
|
int width, height;
|
|
unsigned int i;
|
|
qbyte *lump;
|
|
uploadfmt_t format;
|
|
lump = ReadTargaFile(default_conchar, sizeof(default_conchar), &width, &height, &format, false, PTI_INVALID);
|
|
if (!lump || (format != PTI_RGBX8 && format != PTI_RGBA8 && format != PTI_LLLX8))
|
|
Sys_Error("Corrupt internal drawchars (%i)", format);
|
|
/*convert greyscale to alpha*/
|
|
for (i = 0; i < width*height; i++)
|
|
{
|
|
lump[i*4+3] = lump[i*4];
|
|
lump[i*4+0] = 255;
|
|
lump[i*4+1] = 255;
|
|
lump[i*4+2] = 255;
|
|
}
|
|
if (width == 256 && height == 256)
|
|
{ //make up some scroll-bar/download-progress-bar chars, so that webgl doesn't look so buggy with the initial pak file(s).
|
|
Font_CopyGlyph('[', 128, lump);
|
|
Font_CopyGlyph('-', 129, lump);
|
|
Font_CopyGlyph(']', 130, lump);
|
|
Font_CopyGlyph('|', 131, lump);
|
|
Font_CopyGlyph('>', 13, lump);
|
|
}
|
|
tex = R_LoadTexture32("charset", width, height, (void*)lump, IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA);
|
|
BZ_Free(lump);
|
|
return tex;
|
|
}
|
|
|
|
enum fontfmt_e
|
|
{
|
|
FMT_AUTO, //freetype, or quake
|
|
FMT_QUAKE, //first is default
|
|
FMT_ISO88591, //latin-1 (first 256 chars of unicode too, c1 glyphs are usually invisible)
|
|
FMT_WINDOWS1252,//variation of latin-1 with extra glyphs
|
|
FMT_KOI8U, //image is 16*16 koi8-u codepage.
|
|
FMT_HORIZONTAL, //unicode, charcount=width/(height-2). single strip of chars, like halflife.
|
|
};
|
|
|
|
/*loads a fallback image. not allowed to fail (use syserror if needed)*/
|
|
static texid_t Font_LoadDefaultConchars(enum fontfmt_e *fmt)
|
|
{
|
|
texid_t tex;
|
|
tex = Font_LoadReplacementConchars();
|
|
if (TEXLOADED(tex))
|
|
return tex;
|
|
tex = Font_LoadQuakeConchars();
|
|
if (tex && tex->status == TEX_LOADING)
|
|
COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING);
|
|
if (TEXLOADED(tex))
|
|
return tex;
|
|
#ifdef HEXEN2
|
|
tex = Font_LoadHexen2Conchars(true);
|
|
if (tex && tex->status == TEX_LOADING)
|
|
COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING);
|
|
if (TEXLOADED(tex))
|
|
{
|
|
*fmt = FMT_ISO88591;
|
|
return tex;
|
|
}
|
|
#endif
|
|
tex = Font_LoadFallbackConchars();
|
|
if (tex && tex->status == TEX_LOADING)
|
|
COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING);
|
|
if (TEXLOADED(tex))
|
|
{
|
|
*fmt = FMT_QUAKE;
|
|
return tex;
|
|
}
|
|
Sys_Error("Unable to load any conchars\n");
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
short width;
|
|
short height;
|
|
short leftoffset; // pixels to the left of origin
|
|
short topoffset; // pixels below the origin
|
|
int columnofs[1];
|
|
} doompatch_t;
|
|
typedef struct
|
|
{
|
|
unsigned char topdelta; // -1 is the last post in a column
|
|
unsigned char length; // length data bytes follows
|
|
} doomcolumn_t;
|
|
void Doom_ExpandPatch(doompatch_t *p, unsigned char *b, int stride)
|
|
{
|
|
doomcolumn_t *col;
|
|
unsigned char *src, *dst;
|
|
int x, y;
|
|
for (x = 0; x < p->width; x++)
|
|
{
|
|
col = (doomcolumn_t *)((unsigned char *)p + p->columnofs[x]);
|
|
while(col->topdelta != 0xff)
|
|
{
|
|
//exploit protection
|
|
if (col->length + col->topdelta > p->height)
|
|
break;
|
|
|
|
src = (unsigned char *)col + 2; /*why 3? why not, I suppose*/
|
|
dst = b + stride*col->topdelta;
|
|
for (y = 0; y < col->length; y++)
|
|
{
|
|
*dst = *src++;
|
|
dst += stride;
|
|
}
|
|
src++;
|
|
col = (doomcolumn_t *)((unsigned char*)col + col->length + 4);
|
|
}
|
|
b++;
|
|
}
|
|
}
|
|
|
|
//creates a new font object from the given file, with each text row with the given height.
|
|
//width is implicit and scales with height and choice of font.
|
|
struct font_s *Font_LoadFont(const char *fontfilename, float vheight)
|
|
{
|
|
struct font_s *f;
|
|
int i = 0;
|
|
int defaultplane;
|
|
char *aname;
|
|
char *parms;
|
|
int height = ((vheight * vid.rotpixelheight)/vid.height) + 0.5;
|
|
char facename[MAX_QPATH*12];
|
|
struct charcache_s *c;
|
|
float aspect = 1;
|
|
enum fontfmt_e fmt = FMT_AUTO;
|
|
|
|
Q_strncpyz(facename, fontfilename, sizeof(facename));
|
|
|
|
aname = strstr(facename, ":");
|
|
if (aname)
|
|
*aname++ = 0;
|
|
parms = strstr(facename, "?");
|
|
if (parms)
|
|
*parms++ = 0;
|
|
|
|
f = Z_Malloc(sizeof(*f));
|
|
f->charheight = height;
|
|
Q_strncpyz(f->name, fontfilename, sizeof(f->name));
|
|
|
|
switch(M_GameType())
|
|
{
|
|
case MGT_QUAKE2:
|
|
VectorSet(f->alttint, 0.44, 1.0, 0.2);
|
|
break;
|
|
default:
|
|
VectorSet(f->alttint, 1.16, 0.54, 0.41);
|
|
break;
|
|
}
|
|
VectorSet(f->tint, 1, 1, 1);
|
|
fontfilename = facename;
|
|
|
|
if (parms)
|
|
{
|
|
while (*parms)
|
|
{
|
|
if (!strncmp(parms, "col=", 4))
|
|
{
|
|
char *t = parms+4;
|
|
f->tint[0] = strtod(t, &t);
|
|
if (*t == ',') t++;
|
|
if (*t == ' ') t++;
|
|
f->tint[1] = strtod(t, &t);
|
|
if (*t == ',') t++;
|
|
if (*t == ' ') t++;
|
|
f->tint[2] = strtod(t, &t);
|
|
parms = t;
|
|
}
|
|
if (!strncmp(parms, "fmt=", 4))
|
|
{
|
|
char *t = parms+4;
|
|
fmt = 0;
|
|
if (*t == 'q')
|
|
fmt = FMT_QUAKE;
|
|
else if (*t == 'l')
|
|
fmt = FMT_ISO88591;
|
|
else if (*t == 'w')
|
|
fmt = FMT_WINDOWS1252;
|
|
else if (*t == 'k')
|
|
fmt = FMT_KOI8U;
|
|
else if (*t == 'h')
|
|
fmt = FMT_HORIZONTAL;
|
|
}
|
|
if (!strncmp(parms, "aspect=", 7))
|
|
{
|
|
char *t = parms+7;
|
|
aspect = strtod(t, &t);
|
|
parms = t;
|
|
}
|
|
|
|
while(*parms && *parms != '&')
|
|
parms++;
|
|
if (*parms == '&')
|
|
{
|
|
parms++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vid.flags&VID_SRGBAWARE)
|
|
{
|
|
f->tint[0] = M_SRGBToLinear(f->tint[0], 1);
|
|
f->tint[1] = M_SRGBToLinear(f->tint[1], 1);
|
|
f->tint[2] = M_SRGBToLinear(f->tint[2], 1);
|
|
|
|
f->alttint[0] = M_SRGBToLinear(f->alttint[0], 1);
|
|
f->alttint[1] = M_SRGBToLinear(f->alttint[1], 1);
|
|
f->alttint[2] = M_SRGBToLinear(f->alttint[2], 1);
|
|
}
|
|
|
|
#ifdef PACKAGE_DOOMWAD
|
|
if (!*fontfilename)
|
|
{
|
|
unsigned char buf[PLANEWIDTH*PLANEHEIGHT];
|
|
int i;
|
|
int x=0,y=0,h=0;
|
|
doompatch_t *dp;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
for (i = '!'; i <= '_'; i++)
|
|
{
|
|
dp = NULL;
|
|
FS_LoadFile(va("wad/stcfn%.3d", i), (void**)&dp);
|
|
if (!dp)
|
|
break;
|
|
|
|
/*make sure it can fit*/
|
|
if (x + dp->width > PLANEWIDTH)
|
|
{
|
|
x = 0;
|
|
y += h;
|
|
h = 0;
|
|
}
|
|
|
|
f->chars[i].advance = dp->width; /*this is how much line space the char takes*/
|
|
f->chars[i].left = -dp->leftoffset;
|
|
f->chars[i].top = -dp->topoffset;
|
|
f->chars[i].nextchar = 0;
|
|
f->chars[i].pad = 0;
|
|
f->chars[i].texplane = SINGLEPLANE;
|
|
|
|
f->chars[i].bmx = x;
|
|
f->chars[i].bmy = y;
|
|
f->chars[i].bmh = dp->height;
|
|
f->chars[i].bmw = dp->width;
|
|
|
|
Doom_ExpandPatch(dp, &buf[y*PLANEWIDTH + x], PLANEWIDTH);
|
|
|
|
x += dp->width;
|
|
if (dp->height > h)
|
|
{
|
|
h = dp->height;
|
|
if (h > f->charheight)
|
|
f->charheight = h;
|
|
}
|
|
|
|
FS_FreeFile(dp);
|
|
}
|
|
|
|
/*if all loaded okay, replicate the chars to the quake-compat range (both white+red chars)*/
|
|
if (i == '_'+1)
|
|
{
|
|
//doom doesn't have many chars, so make sure the lower case chars exist.
|
|
for (i = 'a'; i <= 'z'; i++)
|
|
f->chars[i] = f->chars[i-'a'+'A'];
|
|
//no space char either
|
|
f->chars[' '].advance = 8;
|
|
|
|
f->singletexture = R_LoadTexture8("doomfont", PLANEWIDTH, PLANEHEIGHT, buf, 0, true);
|
|
for (i = 0xe000; i <= 0xe0ff; i++)
|
|
{
|
|
f->chars[i] = f->chars[toupper(i&0x7f)];
|
|
}
|
|
return f;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef HEXEN2
|
|
if (!strcmp(fontfilename, "gfx/tinyfont"))
|
|
{
|
|
unsigned int *img;
|
|
int x, y;
|
|
size_t lumpsize;
|
|
qbyte lumptype;
|
|
unsigned char *w = W_GetLumpName(fontfilename+4, &lumpsize, &lumptype);
|
|
if (!w || lumpsize != 32*128 || lumptype != 'D')
|
|
{
|
|
Z_Free(f);
|
|
return NULL;
|
|
}
|
|
img = Z_Malloc(PLANEWIDTH*PLANEWIDTH*4);
|
|
for (y = 0; y < 32; y++)
|
|
for (x = 0; x < 128; x++)
|
|
img[x + y*PLANEWIDTH] = w[x + y*128]?d_8to24rgbtable[w[x + y*128]]:0;
|
|
|
|
f->singletexture = R_LoadTexture("tinyfont",PLANEWIDTH,PLANEWIDTH,TF_RGBA32,img,IF_PREMULTIPLYALPHA|IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP);
|
|
if (f->singletexture->status == TEX_LOADING)
|
|
COM_WorkerPartialSync(f->singletexture, &f->singletexture->status, TEX_LOADING);
|
|
Z_Free(img);
|
|
|
|
for (i = 0x00; i <= 0xff; i++)
|
|
{
|
|
c = Font_GetCharStore(f, i);
|
|
c->advance = (height*3)/4;
|
|
c->left = 0;
|
|
c->top = 0;
|
|
c->nextchar = 0; //these chars are not linked in
|
|
c->texplane = BITMAPPLANE; /*if its a 'raster' font, don't use the default chars, always use the raster images*/
|
|
|
|
|
|
if (i >= 'a' && i <= 'z')
|
|
{
|
|
c->bmx = ((i - 64)&15)*8;
|
|
c->bmy = ((i - 64)/16)*8;
|
|
c->bmh = 8;
|
|
c->bmw = 8;
|
|
}
|
|
else if (i >= 32 && i < 96)
|
|
{
|
|
c->bmx = ((i - 32)&15)*8;
|
|
c->bmy = ((i - 32)/16)*8;
|
|
c->bmh = 8;
|
|
c->bmw = 8;
|
|
}
|
|
else
|
|
{
|
|
c->bmh = 0;
|
|
c->bmw = 0;
|
|
c->bmx = 0;
|
|
c->bmy = 0;
|
|
}
|
|
|
|
Font_CopyChar(f, i, i|0xe0ff);
|
|
}
|
|
return f;
|
|
}
|
|
#endif
|
|
|
|
if (aname)
|
|
{
|
|
if (!strncmp(aname, "?col=", 5))
|
|
{
|
|
char *t = aname+5;
|
|
f->alttint[0] = strtod(t, &t);
|
|
if (*t == ',') t++;
|
|
if (*t == ' ') t++;
|
|
f->alttint[1] = strtod(t, &t);
|
|
if (*t == ',') t++;
|
|
if (*t == ' ') t++;
|
|
f->alttint[2] = strtod(t, &t);
|
|
}
|
|
else
|
|
{
|
|
f->alt = Font_LoadFont(aname, vheight);
|
|
if (f->alt)
|
|
{
|
|
VectorCopy(f->alt->tint, f->alttint);
|
|
VectorCopy(f->alt->tint, f->alt->alttint);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
const char *start;
|
|
start = fontfilename;
|
|
for(;;)
|
|
{
|
|
char *end = strchr(start, ',');
|
|
if (end)
|
|
*end = 0;
|
|
|
|
if (fmt == FMT_HORIZONTAL)
|
|
Font_LoadHorizontalFont(f, height, start);
|
|
#ifdef AVAIL_FREETYPE
|
|
else if (fmt == FMT_AUTO)
|
|
Font_LoadFreeTypeFont(f, height, start);
|
|
#endif
|
|
|
|
if (end)
|
|
{
|
|
*end = ',';
|
|
start = end+1;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef HALFLIFEMODELS
|
|
if (!f->faces)
|
|
{
|
|
if (f->faces < MAX_FACES)
|
|
{
|
|
size_t lumpsize;
|
|
qbyte lumptype;
|
|
void *lumpdata;
|
|
lumpdata = W_GetLumpName("conchars", &lumpsize, &lumptype);
|
|
if (lumpdata && lumptype == TYP_HLFONT)
|
|
{
|
|
fontface_t *fa = Z_Malloc(sizeof(*fa));
|
|
fa->halflife = lumpdata;
|
|
fa->flink = &fa->fnext;
|
|
fa->refs = 1;
|
|
f->face[f->faces++] = fa;
|
|
// f->charheight = fa->halflife->fontheight1; //force the font to a specific size.
|
|
return f;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!f->faces)
|
|
{
|
|
//default to only map the ascii-compatible chars from the quake font.
|
|
if (*fontfilename)
|
|
{
|
|
f->singletexture = R_LoadHiResTexture(fontfilename, "fonts:charsets", IF_PREMULTIPLYALPHA|(r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP);
|
|
if (f->singletexture->status == TEX_LOADING)
|
|
COM_WorkerPartialSync(f->singletexture, &f->singletexture->status, TEX_LOADING);
|
|
}
|
|
}
|
|
|
|
defaultplane = INVALIDPLANE;/*assume the bitmap plane - don't use the fallback as people don't think to use com_parseutf8*/
|
|
if (TEXLOADED(f->singletexture))
|
|
defaultplane = BITMAPPLANE;
|
|
else if (TEXLOADED(fontplanes.defaultfont))
|
|
defaultplane = DEFAULTPLANE;
|
|
|
|
if (defaultplane == INVALIDPLANE)
|
|
{
|
|
if (!TEXLOADED(fontplanes.defaultfont))
|
|
{
|
|
fontplanes.defaultfont = Font_LoadDefaultConchars(&fmt);
|
|
}
|
|
|
|
#ifdef HEXEN2
|
|
if (!strcmp(fontfilename, "gfx/hexen2"))
|
|
{
|
|
f->singletexture = Font_LoadHexen2Conchars(false);
|
|
defaultplane = DEFAULTPLANE;
|
|
}
|
|
#endif
|
|
if (!TEXLOADED(f->singletexture))
|
|
f->singletexture = fontplanes.defaultfont;
|
|
|
|
if (TEXLOADED(f->singletexture))
|
|
defaultplane = BITMAPPLANE;
|
|
else if (TEXLOADED(fontplanes.defaultfont))
|
|
defaultplane = DEFAULTPLANE;
|
|
}
|
|
|
|
if (defaultplane != INVALIDPLANE)
|
|
{
|
|
if (fmt==FMT_AUTO)
|
|
fmt=FMT_QUAKE;
|
|
if (!f->faces)
|
|
{
|
|
static const unsigned short iso88591[] = {
|
|
0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87, 0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f,
|
|
0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97, 0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f};
|
|
static const unsigned short win1252[] = {
|
|
0x20ac, 0x81,0x201a,0x0192, 0x201e,0x2026,0x2020,0x2021, 0x02c6,0x2030,0x0160,0x2039, 0x0152, 0x8d,0x017d, 0x8f,
|
|
0x90,0x2018,0x2019,0x101c, 0x201d,0x2022,0x2013,0x2014, 0x02dc,0x2122,0x0161,0x203a, 0x0153, 0x9d,0x017e,0x0178};
|
|
static const unsigned short koi8u[] = {
|
|
0x2500,0x2502,0x250C,0x2510, 0x2514,0x2518,0x251C,0x2524, 0x252C,0x2534,0x253C,0x2580, 0x2584,0x2588,0x258C,0x2590,
|
|
0x2591,0x2592,0x2593,0x2320, 0x25A0,0x2219,0x221A,0x2248, 0x2264,0x2265,0x00A0,0x2321, 0x00B0,0x00B2,0x00B7,0x00F7,
|
|
0x2550,0x2551,0x2552,0x0451, 0x0454,0x2554,0x0456,0x0457, 0x2557,0x2558,0x2559,0x255A, 0x255B,0x0491,0x255D,0x255E,
|
|
0x255F,0x2560,0x2561,0x0401, 0x0404,0x2563,0x0406,0x0407, 0x2566,0x2567,0x2568,0x2569, 0x256A,0x0490,0x256C,0x00A9,
|
|
0x044E,0x0430,0x0431,0x0446, 0x0434,0x0435,0x0444,0x0433, 0x0445,0x0438,0x0439,0x043A, 0x043B,0x043C,0x043D,0x043E,
|
|
0x043F,0x044F,0x0440,0x0441, 0x0442,0x0443,0x0436,0x0432, 0x044C,0x044B,0x0437,0x0448, 0x044D,0x0449,0x0447,0x044A,
|
|
0x042E,0x0410,0x0411,0x0426, 0x0414,0x0415,0x0424,0x0413, 0x0425,0x0418,0x0419,0x041A, 0x041B,0x041C,0x041D,0x041E,
|
|
0x041F,0x042F,0x0420,0x0421, 0x0422,0x0423,0x0416,0x0412, 0x042C,0x042B,0x0417,0x0428, 0x042D,0x0429,0x0427,0x042A};
|
|
const unsigned short *c1;
|
|
unsigned int c1size;
|
|
|
|
if (fmt == FMT_WINDOWS1252)
|
|
{ //some tools use these extra ones (latin-1 has no visible c1 entries)
|
|
c1 = win1252;
|
|
c1size = countof(win1252);
|
|
}
|
|
else if (fmt == FMT_KOI8U)
|
|
{ //lots of russians in the quake scene
|
|
c1 = koi8u;
|
|
c1size = countof(koi8u);
|
|
}
|
|
else
|
|
{
|
|
c1 = iso88591;
|
|
c1size = countof(iso88591);
|
|
}
|
|
c1size += 128;
|
|
|
|
/*force it to load, even if there's nothing there*/
|
|
for (i = ((fmt==FMT_QUAKE)?32:0); i < ((fmt==FMT_QUAKE)?128:256); i++)
|
|
{
|
|
if (i >= 128 && i < c1size)
|
|
c = Font_GetCharStore(f, c1[i-128]);
|
|
else
|
|
c = Font_GetCharStore(f, i);
|
|
|
|
c->advance = f->charheight * aspect;
|
|
c->bmh = PLANEWIDTH/16;
|
|
c->bmw = PLANEWIDTH/16;
|
|
c->bmx = (i&15)*(PLANEWIDTH/16);
|
|
c->bmy = (i/16)*(PLANEWIDTH/16);
|
|
c->left = 0;
|
|
c->top = 0;
|
|
c->nextchar = 0; //these chars are not linked in
|
|
c->texplane = defaultplane;
|
|
}
|
|
}
|
|
|
|
if (fmt == FMT_QUAKE)
|
|
{
|
|
/*pack the default chars into it*/
|
|
for (i = 0xe000; i <= 0xe0ff; i++)
|
|
{
|
|
c = Font_GetCharStore(f, i);
|
|
c->advance = f->charheight * aspect;
|
|
c->bmh = PLANEWIDTH/16;
|
|
c->bmw = PLANEWIDTH/16;
|
|
c->bmx = ((i&15))*(PLANEWIDTH/16);
|
|
c->bmy = ((i&0xf0)/16)*(PLANEWIDTH/16);
|
|
c->left = 0;
|
|
c->top = 0;
|
|
c->nextchar = 0; //these chars are not linked in
|
|
c->texplane = defaultplane;
|
|
}
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
|
|
//removes a font from memory.
|
|
void Font_Free(struct font_s *f)
|
|
{
|
|
size_t i;
|
|
struct charcache_s **link, *c, *valid;
|
|
|
|
if (!f)
|
|
return;
|
|
|
|
//kill the alt font first.
|
|
if (f->alt)
|
|
{
|
|
Font_Free(f->alt);
|
|
f->alt = NULL;
|
|
}
|
|
valid = NULL;
|
|
//walk all chars, unlinking any that appear to be within this font's char cache
|
|
for (link = &fontplanes.oldestchar; *link; )
|
|
{
|
|
c = *link;
|
|
if (f->chars[c->block] && c >= f->chars[c->block] && c <= f->chars[c->block] + FONTBLOCKSIZE)
|
|
{
|
|
c = c->nextchar;
|
|
if (!c)
|
|
fontplanes.newestchar = valid;
|
|
*link = c;
|
|
}
|
|
else
|
|
{
|
|
valid = c;
|
|
link = &c->nextchar;
|
|
}
|
|
}
|
|
|
|
while(f->faces --> 0)
|
|
{
|
|
fontface_t *qface = f->face[f->faces];
|
|
qface->refs--;
|
|
if (!qface->refs)
|
|
{
|
|
#ifdef AVAIL_FREETYPE
|
|
if (qface->ft.face)
|
|
pFT_Done_Face(qface->ft.face);
|
|
if (qface->ft.membuf)
|
|
BZ_Free(qface->ft.membuf);
|
|
#endif
|
|
*qface->flink = qface->fnext;
|
|
if (qface->fnext)
|
|
qface->fnext->flink = qface->flink;
|
|
Z_Free(qface);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < FONTBLOCKS; i++)
|
|
if (f->chars[i])
|
|
Z_Free(f->chars[i]);
|
|
Z_Free(f);
|
|
}
|
|
|
|
//maps a given virtual screen coord to a pixel coord, which matches the font's height/width values
|
|
void Font_BeginString(struct font_s *font, float vx, float vy, int *px, int *py)
|
|
{
|
|
if (R2D_Flush && (R2D_Flush != Font_Flush || curfont != font || font_be_flags != r2d_be_flags))
|
|
R2D_Flush();
|
|
R2D_Flush = Font_Flush;
|
|
font_be_flags = r2d_be_flags;
|
|
curfont = font;
|
|
*px = (vx*(int)vid.rotpixelwidth) / (float)vid.width;
|
|
*py = (vy*(int)vid.rotpixelheight) / (float)vid.height;
|
|
|
|
curfont_scale[0] = curfont->charheight;
|
|
curfont_scale[1] = curfont->charheight;
|
|
curfont_scaled = false;
|
|
}
|
|
void Font_Transform(float vx, float vy, int *px, int *py)
|
|
{
|
|
if (px)
|
|
*px = (vx*(int)vid.rotpixelwidth) / (float)vid.width;
|
|
if (py)
|
|
*py = (vy*(int)vid.rotpixelheight) / (float)vid.height;
|
|
}
|
|
void Font_BeginScaledString(struct font_s *font, float vx, float vy, float szx, float szy, float *px, float *py)
|
|
{
|
|
if (R2D_Flush && (R2D_Flush != Font_Flush || curfont != font || font_be_flags != r2d_be_flags))
|
|
R2D_Flush();
|
|
R2D_Flush = Font_Flush;
|
|
font_be_flags = r2d_be_flags;
|
|
curfont = font;
|
|
*px = (vx*(float)vid.rotpixelwidth) / (float)vid.width;
|
|
*py = (vy*(float)vid.rotpixelheight) / (float)vid.height;
|
|
|
|
//now that its in pixels, clamp it so the text is at least consistant with its position.
|
|
//an individual char may end straddling a pixel boundary, but at least the pixels won't jiggle around as the text moves.
|
|
*px = (int)*px;
|
|
*py = (int)*py;
|
|
|
|
if ((int)(szx * vid.rotpixelheight/vid.height) == curfont->charheight && (int)(szy * vid.rotpixelheight/vid.height) == curfont->charheight)
|
|
curfont_scaled = false;
|
|
else
|
|
curfont_scaled = true;
|
|
|
|
curfont_scale[0] = (szx * (float)vid.rotpixelheight) / (curfont->charheight * (float)vid.height);
|
|
curfont_scale[1] = (szy * (float)vid.rotpixelheight) / (curfont->charheight * (float)vid.height);
|
|
}
|
|
|
|
void Font_EndString(struct font_s *font)
|
|
{
|
|
// Font_Flush();
|
|
// curfont = NULL;
|
|
|
|
R2D_Flush = font_foremesh.numindexes?Font_Flush:NULL;
|
|
}
|
|
|
|
//obtains the font's row height (each row of chars should be drawn using this increment)
|
|
int Font_CharHeight(void)
|
|
{
|
|
return curfont->charheight;
|
|
}
|
|
float Font_CharVHeight(struct font_s *font)
|
|
{
|
|
return ((float)font->charheight * vid.height)/vid.rotpixelheight;
|
|
}
|
|
|
|
//obtains the font's row height (each row of chars should be drawn using this increment)
|
|
float Font_CharScaleHeight(void)
|
|
{
|
|
return curfont->charheight * curfont_scale[1];
|
|
}
|
|
|
|
int Font_TabWidth(int x)
|
|
{
|
|
int tabwidth = Font_CharWidth(CON_WHITEMASK, ' ');
|
|
tabwidth *= 8;
|
|
|
|
x++;
|
|
x = x + ((tabwidth - (x % tabwidth)) % tabwidth);
|
|
return x;
|
|
}
|
|
|
|
/*
|
|
This is where the character ends.
|
|
Note: this function supports tabs - x must always be based off 0, with Font_LineDraw actually used to draw the line.
|
|
*/
|
|
int Font_CharEndCoord(struct font_s *font, int x, unsigned int charflags, unsigned int codepoint)
|
|
{
|
|
struct charcache_s *c;
|
|
|
|
if (charflags&CON_HIDDEN)
|
|
return x;
|
|
if (codepoint == '\t')
|
|
return Font_TabWidth(x);
|
|
|
|
if ((charflags & CON_2NDCHARSETTEXT) && font->alt)
|
|
font = font->alt;
|
|
|
|
c = Font_GetChar(font, codepoint);
|
|
if (!c)
|
|
{
|
|
return x+0;
|
|
}
|
|
|
|
return x+c->advance;
|
|
}
|
|
|
|
//obtains the width of a character from a given font. This is how wide it is. The next char should be drawn at x + result.
|
|
//FIXME: this function cannot cope with tab and should not be used.
|
|
int Font_CharWidth(unsigned int charflags, unsigned int codepoint)
|
|
{
|
|
struct charcache_s *c;
|
|
struct font_s *font = curfont;
|
|
|
|
if (charflags&CON_HIDDEN)
|
|
return 0;
|
|
|
|
if ((charflags & CON_2NDCHARSETTEXT) && font->alt)
|
|
font = font->alt;
|
|
|
|
c = Font_GetChar(curfont, codepoint);
|
|
if (!c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return c->advance;
|
|
}
|
|
|
|
//obtains the width of a character from a given font. This is how wide it is. The next char should be drawn at x + result.
|
|
//FIXME: this function cannot cope with tab and should not be used.
|
|
float Font_CharScaleWidth(unsigned int charflags, unsigned int codepoint)
|
|
{
|
|
struct charcache_s *c;
|
|
struct font_s *font = curfont;
|
|
|
|
if (charflags&CON_HIDDEN)
|
|
return 0;
|
|
if ((charflags & CON_2NDCHARSETTEXT) && font->alt)
|
|
font = font->alt;
|
|
|
|
c = Font_GetChar(curfont, codepoint);
|
|
if (!c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return c->advance * curfont_scale[0];
|
|
}
|
|
|
|
conchar_t *Font_DecodeReverse(conchar_t *start, conchar_t *stop, unsigned int *codeflags, unsigned int *codepoint)
|
|
{
|
|
if (start <= stop)
|
|
{
|
|
*codeflags = 0;
|
|
*codepoint = 0;
|
|
return stop;
|
|
}
|
|
|
|
start--;
|
|
if (start > stop && start[-1] & CON_LONGCHAR)
|
|
if (!(start[-1] & CON_RICHFORECOLOUR))
|
|
{
|
|
start--;
|
|
*codeflags = start[1];
|
|
*codepoint = ((start[0] & CON_CHARMASK)<<16) | (start[1] & CON_CHARMASK);
|
|
return start;
|
|
}
|
|
*codeflags = start[0];
|
|
*codepoint = start[0] & CON_CHARMASK;
|
|
return start;
|
|
}
|
|
|
|
//for a given font, calculate the line breaks and word wrapping for a block of text
|
|
//start+end are the input string
|
|
//starts+ends are an array of line start and end points, which have maxlines elements.
|
|
//(end is the terminator, null or otherwise)
|
|
//maxpixelwidth is the width of the display area in pixels
|
|
int Font_LineBreaks(conchar_t *start, conchar_t *end, int maxpixelwidth, int maxlines, conchar_t **starts, conchar_t **ends)
|
|
{
|
|
conchar_t *l, *bt, *n;
|
|
int px;
|
|
int foundlines = 0;
|
|
struct font_s *font = curfont;
|
|
unsigned int codeflags, codepoint;
|
|
|
|
while (start < end)
|
|
{
|
|
// scan the width of the line
|
|
for (px=0, l=start ; px <= maxpixelwidth; )
|
|
{
|
|
if (l >= end)
|
|
break;
|
|
n = Font_Decode(l, &codeflags, &codepoint);
|
|
if (!(codeflags & CON_HIDDEN) && (codepoint == '\n' || codepoint == '\v'))
|
|
break;
|
|
px = Font_CharEndCoord(font, px, codeflags, codepoint);
|
|
l = n;
|
|
}
|
|
//if we did get to the end
|
|
if (px > maxpixelwidth)
|
|
{
|
|
bt = l;
|
|
//backtrack until we find a space
|
|
for(;;)
|
|
{
|
|
n = Font_DecodeReverse(l, start, &codeflags, &codepoint);
|
|
if (codepoint > ' ')
|
|
l = n;
|
|
else
|
|
break;
|
|
}
|
|
if (l == start && bt>start)
|
|
l = Font_DecodeReverse(bt, start, &codeflags, &codepoint);
|
|
}
|
|
|
|
starts[foundlines] = start;
|
|
ends[foundlines] = l;
|
|
foundlines++;
|
|
if (foundlines == maxlines)
|
|
break;
|
|
|
|
start=l;
|
|
if (start == end)
|
|
break;
|
|
|
|
if ((*start&(CON_CHARMASK|CON_HIDDEN)) == '\n' || (*start&(CON_CHARMASK|CON_HIDDEN)) == '\v')
|
|
start++; // skip the \n
|
|
}
|
|
|
|
return foundlines;
|
|
}
|
|
|
|
int Font_LineWidth(conchar_t *start, conchar_t *end)
|
|
{
|
|
//fixme: does this do the right thing with tabs?
|
|
int x = 0;
|
|
struct font_s *font = curfont;
|
|
unsigned int codeflags, codepoint;
|
|
for (; start < end; )
|
|
{
|
|
start = Font_Decode(start, &codeflags, &codepoint);
|
|
x = Font_CharEndCoord(font, x, codeflags, codepoint);
|
|
}
|
|
return x;
|
|
}
|
|
float Font_LineScaleWidth(conchar_t *start, conchar_t *end)
|
|
{
|
|
int x = 0;
|
|
struct font_s *font = curfont;
|
|
unsigned int codeflags, codepoint;
|
|
while(start < end)
|
|
{
|
|
start = Font_Decode(start, &codeflags, &codepoint);
|
|
x = Font_CharEndCoord(font, x, codeflags, codepoint);
|
|
}
|
|
return x * curfont_scale[0];
|
|
}
|
|
void Font_LineDraw(int x, int y, conchar_t *start, conchar_t *end)
|
|
{
|
|
int lx = 0;
|
|
struct font_s *font = curfont;
|
|
unsigned int codeflags, codepoint;
|
|
for (; start < end; )
|
|
{
|
|
start = Font_Decode(start, &codeflags, &codepoint);
|
|
Font_DrawChar(x+lx, y, codeflags, codepoint);
|
|
lx = Font_CharEndCoord(font, lx, codeflags, codepoint);
|
|
}
|
|
}
|
|
|
|
conchar_t *Font_CharAt(int x, conchar_t *start, conchar_t *end)
|
|
{
|
|
int lx = 0, nx;
|
|
struct font_s *font = curfont;
|
|
unsigned int codeflags, codepoint;
|
|
conchar_t *nc;
|
|
for (; start < end; lx = nx, start = nc)
|
|
{
|
|
nc = Font_Decode(start, &codeflags, &codepoint);
|
|
nx = Font_CharEndCoord(font, lx, codeflags, codepoint);
|
|
if (x >= lx && x < nx)
|
|
return start;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*Note: *all* strings after the current one will inherit the same colour, until one changes it explicitly
|
|
correct usage of this function thus requires calling this with 1111 before Font_EndString*/
|
|
void Font_InvalidateColour(vec4_t newcolour)
|
|
{
|
|
if (font_foretint[0] == newcolour[0] && font_foretint[1] == newcolour[1] && font_foretint[2] == newcolour[2] && font_foretint[3] == newcolour[3])
|
|
return;
|
|
|
|
if (font_colourmask & CON_NONCLEARBG)
|
|
{
|
|
if (R2D_Flush)
|
|
R2D_Flush();
|
|
R2D_Flush = Font_Flush;
|
|
}
|
|
font_colourmask = CON_WHITEMASK;
|
|
|
|
VectorScale(newcolour, newcolour[3], font_foretint);
|
|
font_foretint[3] = newcolour[3];
|
|
Vector4Scale(font_foretint, 255, font_forecolour.rgba);
|
|
|
|
font_backcolour.rgba[3] = 0;
|
|
|
|
/*Any drawchars that are now drawn will get the forced colour*/
|
|
}
|
|
|
|
//draw a character from the current font at a pixel location.
|
|
int Font_DrawChar(int px, int py, unsigned int charflags, unsigned int codepoint)
|
|
{
|
|
struct charcache_s *c;
|
|
float s0, s1;
|
|
float t0, t1;
|
|
float nextx;
|
|
float sx, sy, sw, sh;
|
|
int col;
|
|
int v;
|
|
struct font_s *font = curfont;
|
|
#ifdef D3D11QUAKE
|
|
float dxbias = 0;//(qrenderer == QR_DIRECT3D11)?0.5:0;
|
|
#else
|
|
#define dxbias 0
|
|
#endif
|
|
if (charflags & CON_HIDDEN)
|
|
return px;
|
|
|
|
if (charflags & CON_2NDCHARSETTEXT)
|
|
{
|
|
if (font->alt)
|
|
{
|
|
font = font->alt;
|
|
// charflags &= ~CON_2NDCHARSETTEXT;
|
|
}
|
|
else if ((codepoint) >= 0xe000 && (codepoint) <= 0xe0ff)
|
|
charflags &= ~CON_2NDCHARSETTEXT; //don't double-dip
|
|
}
|
|
|
|
//crash if there is no current font.
|
|
c = Font_GetChar(font, codepoint);
|
|
if (!c)
|
|
return px;
|
|
|
|
nextx = px + c->advance;
|
|
|
|
if (codepoint == '\t')
|
|
return Font_TabWidth(px);
|
|
if (codepoint == ' ' && (charflags & (CON_RICHFORECOLOUR|CON_NONCLEARBG)) != CON_NONCLEARBG)
|
|
return nextx;
|
|
|
|
/* if (charcode & CON_BLINKTEXT)
|
|
{
|
|
if (!cl_noblink.ival)
|
|
if ((int)(realtime*3) & 1)
|
|
return nextx;
|
|
}
|
|
*/
|
|
if (charflags & CON_RICHFORECOLOUR)
|
|
{
|
|
col = charflags & (CON_2NDCHARSETTEXT|CON_BLINKTEXT|CON_RICHFORECOLOUR|(0xfff<<CON_RICHBSHIFT));
|
|
if (col != font_colourmask)
|
|
{
|
|
vec4_t rgba;
|
|
if (font_colourmask & CON_NONCLEARBG)
|
|
{
|
|
Font_Flush();
|
|
R2D_Flush = Font_Flush;
|
|
}
|
|
font_colourmask = col;
|
|
|
|
rgba[0] = ((col>>CON_RICHRSHIFT)&0xf)*0x11;
|
|
rgba[1] = ((col>>CON_RICHGSHIFT)&0xf)*0x11;
|
|
rgba[2] = ((col>>CON_RICHBSHIFT)&0xf)*0x11;
|
|
rgba[3] = 255;
|
|
|
|
font_backcolour.c = 0;
|
|
if (charflags & CON_2NDCHARSETTEXT)
|
|
{
|
|
rgba[0] *= font->alttint[0];
|
|
rgba[1] *= font->alttint[1];
|
|
rgba[2] *= font->alttint[2];
|
|
}
|
|
else
|
|
{
|
|
rgba[0] *= font->tint[0];
|
|
rgba[1] *= font->tint[1];
|
|
rgba[2] *= font->tint[2];
|
|
}
|
|
rgba[0] *= font_foretint[0];
|
|
rgba[1] *= font_foretint[1];
|
|
rgba[2] *= font_foretint[2];
|
|
rgba[3] *= font_foretint[3];
|
|
if (charflags & CON_BLINKTEXT)
|
|
{
|
|
float a = (sin(realtime*3)+1)*0.4 + 0.2;
|
|
Vector4Scale(rgba, a, rgba);
|
|
}
|
|
font_forecolour.rgba[0] = min(rgba[0], 255);
|
|
font_forecolour.rgba[1] = min(rgba[1], 255);
|
|
font_forecolour.rgba[2] = min(rgba[2], 255);
|
|
font_forecolour.rgba[3] = min(rgba[3], 255);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
col = charflags & (CON_2NDCHARSETTEXT|CON_NONCLEARBG|CON_BGMASK|CON_FGMASK|CON_HALFALPHA|CON_BLINKTEXT);
|
|
if (col != font_colourmask)
|
|
{
|
|
vec4_t rgba;
|
|
if ((col ^ font_colourmask) & CON_NONCLEARBG)
|
|
{
|
|
Font_Flush();
|
|
R2D_Flush = Font_Flush;
|
|
}
|
|
font_colourmask = col;
|
|
|
|
col = (charflags&CON_FGMASK)>>CON_FGSHIFT;
|
|
if(charflags & CON_HALFALPHA)
|
|
{
|
|
rgba[0] = consolecolours[col].fr*0x7f;
|
|
rgba[1] = consolecolours[col].fg*0x7f;
|
|
rgba[2] = consolecolours[col].fb*0x7f;
|
|
rgba[3] = 0x7f;
|
|
}
|
|
else
|
|
{
|
|
rgba[0] = consolecolours[col].fr*255;
|
|
rgba[1] = consolecolours[col].fg*255;
|
|
rgba[2] = consolecolours[col].fb*255;
|
|
rgba[3] = 255;
|
|
}
|
|
|
|
if (vid.flags&VID_SRGBAWARE)
|
|
{
|
|
rgba[0] = M_SRGBToLinear(rgba[0], 255);
|
|
rgba[1] = M_SRGBToLinear(rgba[1], 255);
|
|
rgba[2] = M_SRGBToLinear(rgba[2], 255);
|
|
}
|
|
|
|
col = (charflags&CON_BGMASK)>>CON_BGSHIFT;
|
|
if (charflags & CON_NONCLEARBG)
|
|
{
|
|
font_backcolour.rgba[0] = consolecolours[col].fr*255;
|
|
font_backcolour.rgba[1] = consolecolours[col].fg*255;
|
|
font_backcolour.rgba[2] = consolecolours[col].fb*255;
|
|
font_backcolour.rgba[3] = (charflags & CON_NONCLEARBG)?0xc0:0;
|
|
}
|
|
else
|
|
font_backcolour.c = 0;
|
|
|
|
if (charflags & CON_2NDCHARSETTEXT)
|
|
{
|
|
rgba[0] *= font->alttint[0];
|
|
rgba[1] *= font->alttint[1];
|
|
rgba[2] *= font->alttint[2];
|
|
}
|
|
else
|
|
{
|
|
rgba[0] *= font->tint[0];
|
|
rgba[1] *= font->tint[1];
|
|
rgba[2] *= font->tint[2];
|
|
}
|
|
rgba[0] *= font_foretint[0];
|
|
rgba[1] *= font_foretint[1];
|
|
rgba[2] *= font_foretint[2];
|
|
rgba[3] *= font_foretint[3];
|
|
if (charflags & CON_BLINKTEXT)
|
|
{
|
|
float a = (sin(realtime*3)+1)*0.4 + 0.2;
|
|
Vector4Scale(rgba, a, rgba);
|
|
}
|
|
font_forecolour.rgba[0] = min(rgba[0], 255);
|
|
font_forecolour.rgba[1] = min(rgba[1], 255);
|
|
font_forecolour.rgba[2] = min(rgba[2], 255);
|
|
font_forecolour.rgba[3] = min(rgba[3], 255);
|
|
}
|
|
}
|
|
|
|
s0 = (float)c->bmx/PLANEWIDTH;
|
|
t0 = (float)c->bmy/PLANEWIDTH;
|
|
s1 = (float)(c->bmx+c->bmw)/PLANEWIDTH;
|
|
t1 = (float)(c->bmy+c->bmh)/PLANEWIDTH;
|
|
|
|
switch(c->texplane)
|
|
{
|
|
case TRACKERIMAGE:
|
|
s0 = t0 = 0;
|
|
s1 = t1 = 1;
|
|
sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth;
|
|
sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight;
|
|
sw = (c->advance*vid.width) / (float)vid.rotpixelwidth;
|
|
sh = (font->charheight*vid.height) / (float)vid.rotpixelheight;
|
|
v = Font_BeginChar(fontplanes.trackerimage);
|
|
break;
|
|
case DEFAULTPLANE:
|
|
sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth;
|
|
sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight;
|
|
sw = ((c->advance)*vid.width) / (float)vid.rotpixelwidth;
|
|
sh = ((font->charheight)*vid.height) / (float)vid.rotpixelheight;
|
|
v = Font_BeginChar(fontplanes.defaultfont);
|
|
break;
|
|
case BITMAPPLANE:
|
|
sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth;
|
|
sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight;
|
|
sw = ((c->advance)*vid.width) / (float)vid.rotpixelwidth;
|
|
sh = ((font->charheight)*vid.height) / (float)vid.rotpixelheight;
|
|
v = Font_BeginChar(font->singletexture);
|
|
break;
|
|
case SINGLEPLANE:
|
|
sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth;
|
|
sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight;
|
|
sw = ((c->bmw)*vid.width) / (float)vid.rotpixelwidth;
|
|
sh = ((c->bmh)*vid.height) / (float)vid.rotpixelheight;
|
|
v = Font_BeginChar(font->singletexture);
|
|
break;
|
|
default:
|
|
sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth;
|
|
sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight;
|
|
sw = ((c->bmw)*vid.width) / (float)vid.rotpixelwidth;
|
|
sh = ((c->bmh)*vid.height) / (float)vid.rotpixelheight;
|
|
v = Font_BeginChar(fontplanes.texnum[c->texplane]);
|
|
break;
|
|
}
|
|
|
|
font_texcoord[v+0][0] = s0;
|
|
font_texcoord[v+0][1] = t0;
|
|
font_texcoord[v+1][0] = s1;
|
|
font_texcoord[v+1][1] = t0;
|
|
font_texcoord[v+2][0] = s1;
|
|
font_texcoord[v+2][1] = t1;
|
|
font_texcoord[v+3][0] = s0;
|
|
font_texcoord[v+3][1] = t1;
|
|
|
|
font_coord[v+0][0] = sx;
|
|
font_coord[v+0][1] = sy;
|
|
font_coord[v+1][0] = sx+sw;
|
|
font_coord[v+1][1] = sy;
|
|
font_coord[v+2][0] = sx+sw;
|
|
font_coord[v+2][1] = sy+sh;
|
|
font_coord[v+3][0] = sx;
|
|
font_coord[v+3][1] = sy+sh;
|
|
|
|
if (c->flags&CHARF_FORCEWHITE)
|
|
{
|
|
font_forecoloura[v+0].c =
|
|
font_forecoloura[v+1].c =
|
|
font_forecoloura[v+2].c =
|
|
font_forecoloura[v+3].c = 0xffffffff;
|
|
}
|
|
else
|
|
{
|
|
font_forecoloura[v+0] =
|
|
font_forecoloura[v+1] =
|
|
font_forecoloura[v+2] =
|
|
font_forecoloura[v+3] = font_forecolour;
|
|
}
|
|
|
|
if (font_colourmask & CON_NONCLEARBG)
|
|
{
|
|
sx = ((px+dxbias)*(int)vid.width) / (float)vid.rotpixelwidth;
|
|
sy = ((py+dxbias)*(int)vid.height) / (float)vid.rotpixelheight;
|
|
sw = sx + ((c->advance)*vid.width) / (float)vid.rotpixelwidth;
|
|
sh = sy + ((font->charheight)*vid.height) / (float)vid.rotpixelheight;
|
|
|
|
//don't care about texcoords
|
|
font_backcoord[v+0][0] = sx;
|
|
font_backcoord[v+0][1] = sy;
|
|
font_backcoord[v+1][0] = sw;
|
|
font_backcoord[v+1][1] = sy;
|
|
font_backcoord[v+2][0] = sw;
|
|
font_backcoord[v+2][1] = sh;
|
|
font_backcoord[v+3][0] = sx;
|
|
font_backcoord[v+3][1] = sh;
|
|
|
|
font_backcoloura[v+0] = font_backcolour;
|
|
font_backcoloura[v+1] = font_backcolour;
|
|
font_backcoloura[v+2] = font_backcolour;
|
|
font_backcoloura[v+3] = font_backcolour;
|
|
}
|
|
|
|
return nextx;
|
|
}
|
|
|
|
/*there is no sane way to make this pixel-correct*/
|
|
float Font_DrawScaleChar(float px, float py, unsigned int charflags, unsigned int codepoint)
|
|
{
|
|
struct charcache_s *c;
|
|
float s0, s1;
|
|
float t0, t1;
|
|
float nextx;
|
|
float sx, sy, sw, sh;
|
|
int col;
|
|
int v;
|
|
struct font_s *font = curfont;
|
|
float cw, ch;
|
|
#ifdef D3D11QUAKE
|
|
float dxbias = 0;//(qrenderer == QR_DIRECT3D11)?0.5:0;
|
|
#else
|
|
#define dxbias 0
|
|
#endif
|
|
|
|
// if (!curfont_scaled)
|
|
// return Font_DrawChar(px, py, charcode);
|
|
|
|
if (charflags & CON_2NDCHARSETTEXT)
|
|
{
|
|
if (font->alt)
|
|
{
|
|
font = font->alt;
|
|
charflags &= ~CON_2NDCHARSETTEXT;
|
|
}
|
|
else if (codepoint >= 0xe000 && codepoint <= 0xe0ff)
|
|
charflags &= ~CON_2NDCHARSETTEXT; //don't double-dip
|
|
}
|
|
|
|
cw = curfont_scale[0];
|
|
ch = curfont_scale[1];
|
|
|
|
//crash if there is no current font.
|
|
c = Font_GetChar(font, codepoint);
|
|
if (!c)
|
|
return px;
|
|
|
|
nextx = px + c->advance*cw;
|
|
|
|
if (codepoint == ' ' && (charflags & (CON_RICHFORECOLOUR|CON_NONCLEARBG)) != CON_NONCLEARBG)
|
|
return nextx;
|
|
|
|
if (charflags & CON_BLINKTEXT)
|
|
{
|
|
if (!cl_noblink.ival)
|
|
if ((int)(realtime*3) & 1)
|
|
return nextx;
|
|
}
|
|
|
|
if (charflags & CON_RICHFORECOLOUR)
|
|
{
|
|
col = charflags & (CON_2NDCHARSETTEXT|CON_RICHFORECOLOUR|(0xfff<<CON_RICHBSHIFT));
|
|
if (col != font_colourmask)
|
|
{
|
|
vec4_t rgba;
|
|
if (font_backcolour.rgba[3])
|
|
{
|
|
Font_Flush();
|
|
R2D_Flush = Font_Flush;
|
|
}
|
|
font_colourmask = col;
|
|
|
|
rgba[0] = ((col>>CON_RICHRSHIFT)&0xf)*0x11;
|
|
rgba[1] = ((col>>CON_RICHGSHIFT)&0xf)*0x11;
|
|
rgba[2] = ((col>>CON_RICHBSHIFT)&0xf)*0x11;
|
|
rgba[3] = 255;
|
|
|
|
font_backcolour.c = 0;
|
|
|
|
if (charflags & CON_2NDCHARSETTEXT)
|
|
{
|
|
rgba[0] *= font->alttint[0];
|
|
rgba[1] *= font->alttint[1];
|
|
rgba[2] *= font->alttint[2];
|
|
}
|
|
else
|
|
{
|
|
rgba[0] *= font->tint[0];
|
|
rgba[1] *= font->tint[1];
|
|
rgba[2] *= font->tint[2];
|
|
}
|
|
rgba[0] *= font_foretint[0];
|
|
rgba[1] *= font_foretint[1];
|
|
rgba[2] *= font_foretint[2];
|
|
rgba[3] *= font_foretint[3];
|
|
font_forecolour.rgba[0] = min(rgba[0], 255);
|
|
font_forecolour.rgba[1] = min(rgba[1], 255);
|
|
font_forecolour.rgba[2] = min(rgba[2], 255);
|
|
font_forecolour.rgba[3] = min(rgba[3], 255);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
col = charflags & (CON_2NDCHARSETTEXT|CON_NONCLEARBG|CON_BGMASK|CON_FGMASK|CON_HALFALPHA);
|
|
if (col != font_colourmask)
|
|
{
|
|
vec4_t rgba;
|
|
if (font_backcolour.rgba[3] != ((charflags & CON_NONCLEARBG)?127:0))
|
|
{
|
|
Font_Flush();
|
|
R2D_Flush = Font_Flush;
|
|
}
|
|
font_colourmask = col;
|
|
|
|
col = (charflags&CON_FGMASK)>>CON_FGSHIFT;
|
|
if (charflags & CON_HALFALPHA)
|
|
{
|
|
rgba[0] = consolecolours[col].fr*0x7f;
|
|
rgba[1] = consolecolours[col].fg*0x7f;
|
|
rgba[2] = consolecolours[col].fb*0x7f;
|
|
rgba[3] = 0x7f;
|
|
}
|
|
else
|
|
{
|
|
rgba[0] = consolecolours[col].fr*255;
|
|
rgba[1] = consolecolours[col].fg*255;
|
|
rgba[2] = consolecolours[col].fb*255;
|
|
rgba[3] = 255;
|
|
}
|
|
|
|
col = (charflags&CON_BGMASK)>>CON_BGSHIFT;
|
|
if (charflags & CON_NONCLEARBG)
|
|
{
|
|
font_backcolour.rgba[0] = consolecolours[col].fr*0xc0;
|
|
font_backcolour.rgba[1] = consolecolours[col].fg*0xc0;
|
|
font_backcolour.rgba[2] = consolecolours[col].fb*0xc0;
|
|
font_backcolour.rgba[3] = 0xc0;
|
|
}
|
|
else
|
|
font_backcolour.c = 0;
|
|
|
|
if (charflags & CON_2NDCHARSETTEXT)
|
|
{
|
|
rgba[0] *= font->alttint[0];
|
|
rgba[1] *= font->alttint[1];
|
|
rgba[2] *= font->alttint[2];
|
|
}
|
|
else
|
|
{
|
|
rgba[0] *= font->tint[0];
|
|
rgba[1] *= font->tint[1];
|
|
rgba[2] *= font->tint[2];
|
|
}
|
|
rgba[0] *= font_foretint[0];
|
|
rgba[1] *= font_foretint[1];
|
|
rgba[2] *= font_foretint[2];
|
|
rgba[3] *= font_foretint[3];
|
|
font_forecolour.rgba[0] = min(rgba[0], 255);
|
|
font_forecolour.rgba[1] = min(rgba[1], 255);
|
|
font_forecolour.rgba[2] = min(rgba[2], 255);
|
|
font_forecolour.rgba[3] = min(rgba[3], 255);
|
|
}
|
|
}
|
|
|
|
s0 = (float)c->bmx/PLANEWIDTH;
|
|
t0 = (float)c->bmy/PLANEWIDTH;
|
|
s1 = (float)(c->bmx+c->bmw)/PLANEWIDTH;
|
|
t1 = (float)(c->bmy+c->bmh)/PLANEWIDTH;
|
|
|
|
if (c->texplane >= DEFAULTPLANE)
|
|
{
|
|
sx = ((px+c->left*cw));
|
|
sy = ((py+c->top*ch));
|
|
sw = ((font->charheight*cw));
|
|
sh = ((font->charheight*ch));
|
|
|
|
if (c->texplane == DEFAULTPLANE)
|
|
v = Font_BeginChar(fontplanes.defaultfont);
|
|
else
|
|
v = Font_BeginChar(font->singletexture);
|
|
}
|
|
else
|
|
{
|
|
sx = (px+c->left*cw);
|
|
sy = (py+c->top*ch);
|
|
sw = ((c->bmw*cw));
|
|
sh = ((c->bmh*ch));
|
|
v = Font_BeginChar(fontplanes.texnum[c->texplane]);
|
|
}
|
|
|
|
sx += dxbias;
|
|
sy += dxbias;
|
|
|
|
sx *= (int)vid.width / (float)vid.rotpixelwidth;
|
|
sy *= (int)vid.height / (float)vid.rotpixelheight;
|
|
sw *= (int)vid.width / (float)vid.rotpixelwidth;
|
|
sh *= (int)vid.height / (float)vid.rotpixelheight;
|
|
|
|
font_texcoord[v+0][0] = s0;
|
|
font_texcoord[v+0][1] = t0;
|
|
font_texcoord[v+1][0] = s1;
|
|
font_texcoord[v+1][1] = t0;
|
|
font_texcoord[v+2][0] = s1;
|
|
font_texcoord[v+2][1] = t1;
|
|
font_texcoord[v+3][0] = s0;
|
|
font_texcoord[v+3][1] = t1;
|
|
|
|
font_coord[v+0][0] = sx;
|
|
font_coord[v+0][1] = sy;
|
|
font_coord[v+1][0] = sx+sw;
|
|
font_coord[v+1][1] = sy;
|
|
font_coord[v+2][0] = sx+sw;
|
|
font_coord[v+2][1] = sy+sh;
|
|
font_coord[v+3][0] = sx;
|
|
font_coord[v+3][1] = sy+sh;
|
|
|
|
font_forecoloura[v+0] = font_forecolour;
|
|
font_forecoloura[v+1] = font_forecolour;
|
|
font_forecoloura[v+2] = font_forecolour;
|
|
font_forecoloura[v+3] = font_forecolour;
|
|
|
|
if (font_colourmask & CON_NONCLEARBG)
|
|
{
|
|
sx = px + dxbias;
|
|
sy = py + dxbias;
|
|
sw = sx + c->advance;
|
|
sh = sy + font->charheight;
|
|
|
|
sx *= (int)vid.width / (float)vid.rotpixelwidth;
|
|
sy *= (int)vid.height / (float)vid.rotpixelheight;
|
|
sw *= (int)vid.width / (float)vid.rotpixelwidth;
|
|
sh *= (int)vid.height / (float)vid.rotpixelheight;
|
|
|
|
//don't care about texcoords
|
|
font_backcoord[v+0][0] = sx;
|
|
font_backcoord[v+0][1] = sy;
|
|
font_backcoord[v+1][0] = sw;
|
|
font_backcoord[v+1][1] = sy;
|
|
font_backcoord[v+2][0] = sw;
|
|
font_backcoord[v+2][1] = sh;
|
|
font_backcoord[v+3][0] = sx;
|
|
font_backcoord[v+3][1] = sh;
|
|
|
|
font_backcoloura[v+0] = font_backcolour;
|
|
font_backcoloura[v+1] = font_backcolour;
|
|
font_backcoloura[v+2] = font_backcolour;
|
|
font_backcoloura[v+3] = font_backcolour;
|
|
}
|
|
|
|
return nextx;
|
|
}
|
|
|
|
#endif //!SERVERONLY
|