mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-13 01:26:50 -04:00
728 lines
22 KiB
C
728 lines
22 KiB
C
#include "Drawer2D.h"
|
|
#include "String.h"
|
|
#include "Graphics.h"
|
|
#include "Funcs.h"
|
|
#include "Platform.h"
|
|
#include "ExtMath.h"
|
|
#include "Logger.h"
|
|
#include "Game.h"
|
|
#include "Event.h"
|
|
#include "Chat.h"
|
|
#include "Stream.h"
|
|
#include "Utils.h"
|
|
#include "Errors.h"
|
|
#include "Window.h"
|
|
#include "Options.h"
|
|
#include "TexturePack.h"
|
|
#include "SystemFonts.h"
|
|
|
|
struct _Drawer2DData Drawer2D;
|
|
#define Font_IsBitmap(font) (!(font)->handle)
|
|
|
|
void DrawTextArgs_Make(struct DrawTextArgs* args, STRING_REF const cc_string* text, struct FontDesc* font, cc_bool useShadow) {
|
|
args->text = *text;
|
|
args->font = font;
|
|
args->useShadow = useShadow;
|
|
}
|
|
|
|
void DrawTextArgs_MakeEmpty(struct DrawTextArgs* args, struct FontDesc* font, cc_bool useShadow) {
|
|
args->text = String_Empty;
|
|
args->font = font;
|
|
args->useShadow = useShadow;
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*-----------------------------------------------------Font functions------------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
int Drawer2D_AdjHeight(int point) { return Math_CeilDiv(point * 3, 2); }
|
|
|
|
void Font_MakeBitmapped(struct FontDesc* desc, int size, int flags) {
|
|
/* TODO: Scale X and Y independently */
|
|
size = Display_ScaleY(size);
|
|
desc->handle = NULL;
|
|
desc->size = size;
|
|
desc->flags = flags;
|
|
desc->height = Drawer2D_AdjHeight(size);
|
|
}
|
|
|
|
void Font_Make(struct FontDesc* desc, int size, int flags) {
|
|
if (Drawer2D.BitmappedText) {
|
|
Font_MakeBitmapped(desc, size, flags);
|
|
} else {
|
|
SysFont_MakeDefault(desc, size, flags);
|
|
}
|
|
}
|
|
|
|
void Font_Free(struct FontDesc* desc) {
|
|
desc->size = 0;
|
|
if (Font_IsBitmap(desc)) return;
|
|
|
|
SysFont_Free(desc);
|
|
desc->handle = NULL;
|
|
}
|
|
|
|
static struct Bitmap fontBitmap;
|
|
static int tileSize = 8; /* avoid divide by 0 if default.png missing */
|
|
/* So really 16 characters per row */
|
|
#define LOG2_CHARS_PER_ROW 4
|
|
static int tileWidths[256];
|
|
|
|
/* Finds the right-most non-transparent pixel in each tile in default.png */
|
|
static void CalculateTextWidths(void) {
|
|
int width = fontBitmap.width, height = fontBitmap.height;
|
|
BitmapCol* row;
|
|
int i, x, y, xx, tileY;
|
|
|
|
for (y = 0; y < height; y++) {
|
|
tileY = y / tileSize;
|
|
row = Bitmap_GetRow(&fontBitmap, y);
|
|
i = 0 | (tileY << LOG2_CHARS_PER_ROW);
|
|
|
|
/* Iterate through each tile on current scanline */
|
|
for (x = 0; x < width; x += tileSize, i++) {
|
|
/* Iterate through each pixel of the given character, on the current scanline */
|
|
for (xx = tileSize - 1; xx >= 0; xx--) {
|
|
if (!BitmapCol_A(row[x + xx])) continue;
|
|
|
|
/* Check if this is the pixel furthest to the right, for the current character */
|
|
tileWidths[i] = max(tileWidths[i], xx + 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
tileWidths[' '] = tileSize / 4;
|
|
}
|
|
|
|
static void FreeFontBitmap(void) {
|
|
int i;
|
|
for (i = 0; i < Array_Elems(tileWidths); i++) tileWidths[i] = 0;
|
|
Mem_Free(fontBitmap.scan0);
|
|
}
|
|
|
|
cc_bool Font_SetBitmapAtlas(struct Bitmap* bmp) {
|
|
/* If not all of these cases are accounted for, end up overwriting memory after tileWidths */
|
|
if (bmp->width != bmp->height) {
|
|
static const cc_string msg = String_FromConst("&cWidth of default.png must equal its height");
|
|
Logger_WarnFunc(&msg);
|
|
return false;
|
|
} else if (bmp->width < 16) {
|
|
static const cc_string msg = String_FromConst("&cdefault.png must be at least 16 pixels wide");
|
|
Logger_WarnFunc(&msg);
|
|
return false;
|
|
} else if (!Math_IsPowOf2(bmp->width)) {
|
|
static const cc_string msg = String_FromConst("&cWidth of default.png must be a power of two");
|
|
Logger_WarnFunc(&msg);
|
|
return false;
|
|
}
|
|
|
|
/* TODO: Use shift instead of mul/div */
|
|
FreeFontBitmap();
|
|
fontBitmap = *bmp;
|
|
tileSize = bmp->width >> LOG2_CHARS_PER_ROW;
|
|
|
|
CalculateTextWidths();
|
|
return true;
|
|
}
|
|
|
|
void Font_SetPadding(struct FontDesc* desc, int amount) {
|
|
if (!Font_IsBitmap(desc)) return;
|
|
desc->height = desc->size + Display_ScaleY(amount) * 2;
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*---------------------------------------------------Drawing functions-----------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
cc_bool Drawer2D_Clamp(struct Context2D* ctx, int* x, int* y, int* width, int* height) {
|
|
if (*x >= ctx->width || *y >= ctx->height) return false;
|
|
|
|
/* origin is negative, move inside */
|
|
if (*x < 0) { *width += *x; *x = 0; }
|
|
if (*y < 0) { *height += *y; *y = 0; }
|
|
|
|
*width = min(*x + *width, ctx->width) - *x;
|
|
*height = min(*y + *height, ctx->height) - *y;
|
|
return *width > 0 && *height > 0;
|
|
}
|
|
#define Drawer2D_ClampPixel(p) p = (p < 0 ? 0 : (p > 255 ? 255 : p))
|
|
|
|
void Context2D_Alloc(struct Context2D* ctx, int width, int height) {
|
|
ctx->width = width;
|
|
ctx->height = height;
|
|
ctx->meta = NULL;
|
|
|
|
if (Gfx.NonPowTwoTexturesSupport == GFX_NONPOW2_NONE) {
|
|
/* Allocate power-of-2 sized bitmap equal to or greater than the given size */
|
|
width = Math_NextPowOf2(width);
|
|
height = Math_NextPowOf2(height);
|
|
} else if (Gfx.NonPowTwoTexturesSupport == GFX_NONPOW2_UPLOAD) {
|
|
/* Can upload texture without needing to pad up to power of two */
|
|
}
|
|
|
|
if (Gfx.MinTexWidth) { width = max(width, Gfx.MinTexWidth); }
|
|
if (Gfx.MinTexHeight) { height = max(height, Gfx.MinTexHeight); }
|
|
|
|
ctx->bmp.width = width;
|
|
ctx->bmp.height = height;
|
|
ctx->bmp.scan0 = (BitmapCol*)Mem_AllocCleared(width * height, BITMAPCOLOR_SIZE, "bitmap data");
|
|
}
|
|
|
|
void Context2D_Wrap(struct Context2D* ctx, struct Bitmap* bmp) {
|
|
ctx->bmp = *bmp;
|
|
ctx->width = bmp->width;
|
|
ctx->height = bmp->height;
|
|
ctx->meta = NULL;
|
|
}
|
|
|
|
void Context2D_Free(struct Context2D* ctx) {
|
|
Mem_Free(ctx->bmp.scan0);
|
|
}
|
|
|
|
#define BitmapColor_Raw(r, g, b) (BitmapColor_R_Bits(r) | BitmapColor_G_Bits(g) | BitmapColor_B_Bits(b))
|
|
void Gradient_Noise(struct Context2D* ctx, BitmapCol color, int variation,
|
|
int x, int y, int width, int height) {
|
|
struct Bitmap* bmp = (struct Bitmap*)ctx;
|
|
BitmapCol* dst;
|
|
int R, G, B, xx, yy, n;
|
|
int noise, delta;
|
|
cc_uint32 alpha;
|
|
|
|
if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;
|
|
alpha = color & BITMAPCOLOR_A_MASK;
|
|
|
|
for (yy = 0; yy < height; yy++) {
|
|
dst = Bitmap_GetRow(bmp, y + yy) + x;
|
|
|
|
for (xx = 0; xx < width; xx++, dst++) {
|
|
n = (x + xx) + (y + yy) * 57;
|
|
n = (n << 13) ^ n;
|
|
|
|
/*
|
|
float noise = 1.0f - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0f;
|
|
int delta = (int)(noise * variation);
|
|
*/
|
|
/* Fixed point equivalent to the above expression */
|
|
noise = ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff);
|
|
delta = (((1024 - noise / 0x100000)) * variation) >> 10;
|
|
|
|
R = BitmapCol_R(color) + delta; Drawer2D_ClampPixel(R);
|
|
G = BitmapCol_G(color) + delta; Drawer2D_ClampPixel(G);
|
|
B = BitmapCol_B(color) + delta; Drawer2D_ClampPixel(B);
|
|
|
|
*dst = BitmapColor_Raw(R, G, B) | alpha;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Gradient_Vertical(struct Context2D* ctx, BitmapCol a, BitmapCol b,
|
|
int x, int y, int width, int height) {
|
|
struct Bitmap* bmp = (struct Bitmap*)ctx;
|
|
BitmapCol* row, color;
|
|
int xx, yy;
|
|
float t;
|
|
if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;
|
|
|
|
for (yy = 0; yy < height; yy++) {
|
|
row = Bitmap_GetRow(bmp, y + yy) + x;
|
|
t = (float)yy / (height - 1); /* so last row has color of b */
|
|
|
|
color = BitmapCol_Make(
|
|
Math_Lerp(BitmapCol_R(a), BitmapCol_R(b), t),
|
|
Math_Lerp(BitmapCol_G(a), BitmapCol_G(b), t),
|
|
Math_Lerp(BitmapCol_B(a), BitmapCol_B(b), t),
|
|
255);
|
|
|
|
for (xx = 0; xx < width; xx++) { row[xx] = color; }
|
|
}
|
|
}
|
|
|
|
void Gradient_Blend(struct Context2D* ctx, BitmapCol color, int blend,
|
|
int x, int y, int width, int height) {
|
|
struct Bitmap* bmp = (struct Bitmap*)ctx;
|
|
BitmapCol* dst;
|
|
int R, G, B, xx, yy;
|
|
if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;
|
|
|
|
/* Pre compute the alpha blended source color */
|
|
/* TODO: Avoid shift when multiplying */
|
|
color = BitmapCol_Make(
|
|
BitmapCol_R(color) * blend / 255,
|
|
BitmapCol_G(color) * blend / 255,
|
|
BitmapCol_B(color) * blend / 255,
|
|
0);
|
|
blend = 255 - blend; /* inverse for existing pixels */
|
|
|
|
for (yy = 0; yy < height; yy++) {
|
|
dst = Bitmap_GetRow(bmp, y + yy) + x;
|
|
|
|
for (xx = 0; xx < width; xx++, dst++) {
|
|
/* TODO: Not shift when multiplying */
|
|
R = BitmapCol_R(color) + (BitmapCol_R(*dst) * blend) / 255;
|
|
G = BitmapCol_G(color) + (BitmapCol_G(*dst) * blend) / 255;
|
|
B = BitmapCol_B(color) + (BitmapCol_B(*dst) * blend) / 255;
|
|
|
|
*dst = BitmapColor_RGB(R, G, B);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Context2D_DrawPixels(struct Context2D* ctx, int x, int y, struct Bitmap* src) {
|
|
struct Bitmap* dst = (struct Bitmap*)ctx;
|
|
int width = src->width, height = src->height;
|
|
BitmapCol* dstRow;
|
|
BitmapCol* srcRow;
|
|
int xx, yy;
|
|
if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;
|
|
|
|
for (yy = 0; yy < height; yy++) {
|
|
srcRow = Bitmap_GetRow(src, yy);
|
|
dstRow = Bitmap_GetRow(dst, y + yy) + x;
|
|
|
|
for (xx = 0; xx < width; xx++) { dstRow[xx] = srcRow[xx]; }
|
|
}
|
|
}
|
|
|
|
void Context2D_Clear(struct Context2D* ctx, BitmapCol color,
|
|
int x, int y, int width, int height) {
|
|
struct Bitmap* bmp = (struct Bitmap*)ctx;
|
|
BitmapCol* row;
|
|
int xx, yy;
|
|
if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;
|
|
|
|
for (yy = 0; yy < height; yy++) {
|
|
row = Bitmap_GetRow(bmp, y + yy) + x;
|
|
for (xx = 0; xx < width; xx++) { row[xx] = color; }
|
|
}
|
|
}
|
|
|
|
|
|
void Drawer2D_MakeTextTexture(struct Texture* tex, struct DrawTextArgs* args) {
|
|
static struct Texture empty = { 0, Tex_Rect(0,0, 0,0), Tex_UV(0,0, 1,1) };
|
|
struct Context2D ctx;
|
|
int width, height;
|
|
/* pointless to draw anything when context is lost */
|
|
if (Gfx.LostContext) { *tex = empty; return; }
|
|
|
|
width = Drawer2D_TextWidth(args);
|
|
if (!width) { *tex = empty; return; }
|
|
height = Drawer2D_TextHeight(args);
|
|
|
|
Context2D_Alloc(&ctx, width, height);
|
|
{
|
|
Context2D_DrawText(&ctx, args, 0, 0);
|
|
Context2D_MakeTexture(tex, &ctx);
|
|
}
|
|
Context2D_Free(&ctx);
|
|
}
|
|
|
|
void Context2D_MakeTexture(struct Texture* tex, struct Context2D* ctx) {
|
|
int flags = TEXTURE_FLAG_NONPOW2 | TEXTURE_FLAG_LOWRES;
|
|
int nouv = Gfx.Limitations & GFX_LIMIT_NO_UV_SUPPORT;
|
|
Gfx_RecreateTexture(&tex->ID, &ctx->bmp, flags, false);
|
|
|
|
/* TODO need to find a better solution in NoUVSupport case */
|
|
tex->width = nouv ? ctx->bmp.width : ctx->width;
|
|
tex->height = nouv ? ctx->bmp.height : ctx->height;
|
|
|
|
tex->uv.u1 = 0.0f;
|
|
tex->uv.v1 = 0.0f;
|
|
tex->uv.u2 = Context2D_CalcUV(ctx->width, ctx->bmp.width);
|
|
tex->uv.v2 = Context2D_CalcUV(ctx->height, ctx->bmp.height);
|
|
}
|
|
|
|
float Context2D_CalcUV(int pixels, int axisLen) {
|
|
if (Gfx.NonPowTwoTexturesSupport == GFX_NONPOW2_UPLOAD) {
|
|
return (float)pixels / (float)Math_NextPowOf2(axisLen);
|
|
} else {
|
|
return (float)pixels / (float)axisLen;
|
|
}
|
|
}
|
|
|
|
cc_bool Drawer2D_ValidColorCodeAt(const cc_string* text, int i) {
|
|
if (i >= text->length) return false;
|
|
return BitmapCol_A(Drawer2D_GetColor(text->buffer[i])) != 0;
|
|
}
|
|
|
|
cc_bool Drawer2D_UNSAFE_NextPart(cc_string* left, cc_string* part, char* colorCode) {
|
|
BitmapCol color;
|
|
char cur;
|
|
int i;
|
|
|
|
/* check if current part starts with a colour code */
|
|
if (left->length >= 2 && left->buffer[0] == '&') {
|
|
cur = left->buffer[1];
|
|
color = Drawer2D_GetColor(cur);
|
|
|
|
if (BitmapCol_A(color)) {
|
|
*colorCode = cur;
|
|
left->buffer += 2;
|
|
left->length -= 2;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < left->length; i++)
|
|
{
|
|
if (left->buffer[i] == '&' && Drawer2D_ValidColorCodeAt(left, i + 1)) break;
|
|
}
|
|
|
|
/* advance string starts and lengths */
|
|
part->buffer = left->buffer;
|
|
part->length = i;
|
|
left->buffer += i;
|
|
left->length -= i;
|
|
|
|
return part->length > 0 || left->length > 0;
|
|
}
|
|
|
|
cc_bool Drawer2D_IsEmptyText(const cc_string* text) {
|
|
cc_string left = *text, part;
|
|
char colorCode;
|
|
|
|
while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
|
|
{
|
|
if (part.length) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Drawer2D_WithoutColors(cc_string* str, const cc_string* src) {
|
|
cc_string left = *src, part;
|
|
char colorCode;
|
|
|
|
while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
|
|
{
|
|
String_AppendString(str, &part);
|
|
}
|
|
}
|
|
|
|
char Drawer2D_LastColor(const cc_string* text, int start) {
|
|
int i;
|
|
if (start >= text->length) start = text->length - 1;
|
|
|
|
for (i = start; i >= 0; i--) {
|
|
if (text->buffer[i] != '&') continue;
|
|
if (Drawer2D_ValidColorCodeAt(text, i + 1)) {
|
|
return text->buffer[i + 1];
|
|
}
|
|
}
|
|
return '\0';
|
|
}
|
|
cc_bool Drawer2D_IsWhiteColor(char c) { return c == '\0' || c == 'f' || c == 'F'; }
|
|
|
|
/* TODO: Needs to account for DPI */
|
|
#define Drawer2D_ShadowOffset(point) (point / 8)
|
|
#define Drawer2D_XPadding(point) (Math_CeilDiv(point, 8))
|
|
static int Drawer2D_Width(int point, char c) {
|
|
return Math_CeilDiv(tileWidths[(cc_uint8)c] * point, tileSize);
|
|
}
|
|
|
|
void Drawer2D_ReducePadding_Tex(struct Texture* tex, int point, int scale) {
|
|
int padding;
|
|
float vAdj;
|
|
if (!Drawer2D.BitmappedText) return;
|
|
|
|
padding = (tex->height - point) / scale;
|
|
vAdj = (float)padding / Math_NextPowOf2(tex->height);
|
|
tex->uv.v1 += vAdj; tex->uv.v2 -= vAdj;
|
|
tex->height -= (cc_uint16)(padding * 2);
|
|
}
|
|
|
|
void Drawer2D_ReducePadding_Height(int* height, int point, int scale) {
|
|
int padding;
|
|
if (!Drawer2D.BitmappedText) return;
|
|
|
|
padding = (*height - point) / scale;
|
|
*height -= padding * 2;
|
|
}
|
|
|
|
void Drawer2D_Fill(struct Bitmap* bmp, int x, int y, int width, int height, BitmapCol color) {
|
|
BitmapCol* row;
|
|
int xx, yy;
|
|
|
|
for (yy = y; yy < y + height; yy++) {
|
|
if (yy >= bmp->height) return;
|
|
row = Bitmap_GetRow(bmp, yy);
|
|
|
|
for (xx = x; xx < x + width; xx++) {
|
|
if (xx >= bmp->width) break;
|
|
row[xx] = color;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DrawBitmappedTextCore(struct Bitmap* bmp, struct DrawTextArgs* args, int x, int y, cc_bool shadow) {
|
|
BitmapCol color;
|
|
cc_string text = args->text;
|
|
int i, point = args->font->size, count = 0;
|
|
|
|
int xPadding;
|
|
int srcX, srcY, dstX, dstY;
|
|
int fontX, fontY;
|
|
int srcWidth, dstWidth;
|
|
int dstHeight, begX, xx, yy;
|
|
int cellY, underlineY, underlineHeight;
|
|
|
|
BitmapCol* srcRow, src;
|
|
BitmapCol* dstRow;
|
|
|
|
cc_uint8 coords[DRAWER2D_MAX_TEXT_LENGTH];
|
|
BitmapCol colors[DRAWER2D_MAX_TEXT_LENGTH];
|
|
cc_uint16 dstWidths[DRAWER2D_MAX_TEXT_LENGTH];
|
|
|
|
color = Drawer2D.Colors['f'];
|
|
if (shadow) color = GetShadowColor(color);
|
|
|
|
for (i = 0; i < text.length; i++) {
|
|
char c = text.buffer[i];
|
|
if (c == '&' && Drawer2D_ValidColorCodeAt(&text, i + 1)) {
|
|
color = Drawer2D_GetColor(text.buffer[i + 1]);
|
|
|
|
if (shadow) color = GetShadowColor(color);
|
|
i++; continue; /* skip over the color code */
|
|
}
|
|
|
|
coords[count] = c;
|
|
colors[count] = color;
|
|
dstWidths[count] = Drawer2D_Width(point, c);
|
|
count++;
|
|
}
|
|
|
|
dstHeight = point; begX = x;
|
|
/* adjust coords to make drawn text match GDI fonts */
|
|
y += (args->font->height - dstHeight) / 2;
|
|
xPadding = Drawer2D_XPadding(point);
|
|
|
|
for (yy = 0; yy < dstHeight; yy++) {
|
|
dstY = y + yy;
|
|
if ((unsigned)dstY >= (unsigned)bmp->height) continue;
|
|
|
|
fontY = 0 + yy * tileSize / dstHeight;
|
|
dstRow = Bitmap_GetRow(bmp, dstY);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
srcX = (coords[i] & 0x0F) * tileSize;
|
|
srcY = (coords[i] >> 4) * tileSize;
|
|
srcRow = Bitmap_GetRow(&fontBitmap, fontY + srcY);
|
|
|
|
srcWidth = tileWidths[coords[i]];
|
|
dstWidth = dstWidths[i];
|
|
color = colors[i];
|
|
|
|
for (xx = 0; xx < dstWidth; xx++) {
|
|
fontX = srcX + xx * srcWidth / dstWidth;
|
|
src = srcRow[fontX];
|
|
if (!BitmapCol_A(src)) continue;
|
|
|
|
dstX = x + xx;
|
|
if ((unsigned)dstX >= (unsigned)bmp->width) continue;
|
|
|
|
/* TODO: Transparent text by multiplying by col.A */
|
|
/* TODO: Not shift when multiplying */
|
|
/* TODO: avoid BitmapCol_A shift */
|
|
dstRow[dstX] = BitmapCol_Make(
|
|
BitmapCol_R(src) * BitmapCol_R(color) / 255,
|
|
BitmapCol_G(src) * BitmapCol_G(color) / 255,
|
|
BitmapCol_B(src) * BitmapCol_B(color) / 255,
|
|
BitmapCol_A(src));
|
|
}
|
|
x += dstWidth + xPadding;
|
|
}
|
|
x = begX;
|
|
}
|
|
|
|
if (!(args->font->flags & FONT_FLAGS_UNDERLINE)) return;
|
|
/* scale up bottom row of a cell to drawn text font */
|
|
cellY = (8 - 1) * dstHeight / 8;
|
|
underlineY = y + cellY;
|
|
underlineHeight = dstHeight - cellY;
|
|
|
|
for (i = 0; i < count; ) {
|
|
dstWidth = 0;
|
|
color = colors[i];
|
|
|
|
for (; i < count && color == colors[i]; i++) {
|
|
dstWidth += dstWidths[i] + xPadding;
|
|
}
|
|
Drawer2D_Fill(bmp, x, underlineY, dstWidth, underlineHeight, color);
|
|
x += dstWidth;
|
|
}
|
|
}
|
|
|
|
static void DrawBitmappedText(struct Bitmap* bmp, struct DrawTextArgs* args, int x, int y) {
|
|
int offset = Drawer2D_ShadowOffset(args->font->size);
|
|
|
|
if (!fontBitmap.scan0) {
|
|
if (args->useShadow) FallbackFont_DrawText(args, bmp, x, y, true);
|
|
FallbackFont_DrawText(args, bmp, x, y, false);
|
|
return;
|
|
}
|
|
|
|
if (args->useShadow) {
|
|
DrawBitmappedTextCore(bmp, args, x + offset, y + offset, true);
|
|
}
|
|
DrawBitmappedTextCore(bmp, args, x, y, false);
|
|
}
|
|
|
|
static int MeasureBitmappedWidth(const struct DrawTextArgs* args) {
|
|
int i, point = args->font->size;
|
|
int xPadding, width;
|
|
cc_string text;
|
|
|
|
if (!fontBitmap.scan0) return FallbackFont_TextWidth(args);
|
|
|
|
/* adjust coords to make drawn text match GDI fonts */
|
|
xPadding = Drawer2D_XPadding(point);
|
|
width = 0;
|
|
|
|
text = args->text;
|
|
for (i = 0; i < text.length; i++) {
|
|
char c = text.buffer[i];
|
|
if (c == '&' && Drawer2D_ValidColorCodeAt(&text, i + 1)) {
|
|
i++; continue; /* skip over the color code */
|
|
}
|
|
width += Drawer2D_Width(point, c) + xPadding;
|
|
}
|
|
if (!width) return 0;
|
|
|
|
/* Remove padding at end */
|
|
if (!(args->font->flags & FONT_FLAGS_PADDING)) width -= xPadding;
|
|
|
|
if (args->useShadow) { width += Drawer2D_ShadowOffset(point); }
|
|
return width;
|
|
}
|
|
|
|
void Context2D_DrawText(struct Context2D* ctx, struct DrawTextArgs* args, int x, int y) {
|
|
struct Bitmap* bmp = (struct Bitmap*)ctx;
|
|
if (Drawer2D_IsEmptyText(&args->text)) return;
|
|
if (Font_IsBitmap(args->font)) { DrawBitmappedText(bmp, args, x, y); return; }
|
|
|
|
if (args->useShadow) { SysFont_DrawText(args, bmp, x, y, true); }
|
|
SysFont_DrawText(args, bmp, x, y, false);
|
|
}
|
|
|
|
int Drawer2D_TextWidth(struct DrawTextArgs* args) {
|
|
if (Font_IsBitmap(args->font)) return MeasureBitmappedWidth(args);
|
|
return SysFont_TextWidth(args);
|
|
}
|
|
|
|
int Drawer2D_TextHeight(struct DrawTextArgs* args) {
|
|
return Font_CalcHeight(args->font, args->useShadow);
|
|
}
|
|
|
|
int Font_CalcHeight(const struct FontDesc* font, cc_bool useShadow) {
|
|
int height = font->height;
|
|
if (Font_IsBitmap(font)) {
|
|
if (useShadow) { height += Drawer2D_ShadowOffset(font->size); }
|
|
} else {
|
|
if (useShadow) height += 2;
|
|
}
|
|
return height;
|
|
}
|
|
|
|
void Drawer2D_DrawClippedText(struct Context2D* ctx, struct DrawTextArgs* args,
|
|
int x, int y, int maxWidth) {
|
|
char strBuffer[512];
|
|
struct DrawTextArgs part;
|
|
int i, width;
|
|
|
|
width = Drawer2D_TextWidth(args);
|
|
/* No clipping needed */
|
|
if (width <= maxWidth) { Context2D_DrawText(ctx, args, x, y); return; }
|
|
part = *args;
|
|
|
|
String_InitArray(part.text, strBuffer);
|
|
String_Copy(&part.text, &args->text);
|
|
String_Append(&part.text, '.');
|
|
|
|
for (i = part.text.length - 2; i > 0; i--) {
|
|
part.text.buffer[i] = '.';
|
|
/* skip over trailing spaces */
|
|
if (part.text.buffer[i - 1] == ' ') continue;
|
|
|
|
part.text.length = i + 2;
|
|
width = Drawer2D_TextWidth(&part);
|
|
if (width <= maxWidth) { Context2D_DrawText(ctx, &part, x, y); return; }
|
|
|
|
/* If down to <= 2 chars, try omitting the .. */
|
|
if (i > 2) continue;
|
|
part.text.length = i;
|
|
width = Drawer2D_TextWidth(&part);
|
|
if (width <= maxWidth) { Context2D_DrawText(ctx, &part, x, y); return; }
|
|
}
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*---------------------------------------------------Drawer2D component----------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
static void DefaultPngProcess(struct Stream* stream, const cc_string* name) {
|
|
struct Bitmap bmp;
|
|
cc_result res;
|
|
|
|
if ((res = Png_Decode(&bmp, stream))) {
|
|
Logger_SysWarn2(res, "decoding", name);
|
|
Mem_Free(bmp.scan0);
|
|
} else if (Font_SetBitmapAtlas(&bmp)) {
|
|
Event_RaiseVoid(&ChatEvents.FontChanged);
|
|
} else {
|
|
Mem_Free(bmp.scan0);
|
|
}
|
|
}
|
|
static struct TextureEntry default_entry = { "default.png", DefaultPngProcess };
|
|
|
|
|
|
/* The default 16 colours are the CGA 16 color palette (without special brown colour) */
|
|
/* See https://en.wikipedia.org/wiki/Color_Graphics_Adapter#With_an_RGBI_monitor for reference */
|
|
/* The 16 hex colours below were produced from the following formula: */
|
|
/* R = 191 * ((hex >> 2) & 1) + 64 * (hex >> 3) */
|
|
/* G = 191 * ((hex >> 1) & 1) + 64 * (hex >> 3) */
|
|
/* B = 191 * ((hex >> 0) & 1) + 64 * (hex >> 3) */
|
|
static const BitmapCol defaults_0_9[] = {
|
|
BitmapColor_RGB( 0, 0, 0), /* 0 */
|
|
BitmapColor_RGB( 0, 0, 191), /* 1 */
|
|
BitmapColor_RGB( 0, 191, 0), /* 2 */
|
|
BitmapColor_RGB( 0, 191, 191), /* 3 */
|
|
BitmapColor_RGB(191, 0, 0), /* 4 */
|
|
BitmapColor_RGB(191, 0, 191), /* 5 */
|
|
BitmapColor_RGB(191, 191, 0), /* 6 */
|
|
BitmapColor_RGB(191, 191, 191), /* 7 */
|
|
BitmapColor_RGB( 64, 64, 64), /* 8 */
|
|
BitmapColor_RGB( 64, 64, 255) /* 9 */
|
|
};
|
|
static const BitmapCol defaults_a_f[] = {
|
|
BitmapColor_RGB( 64, 255, 64), /* A */
|
|
BitmapColor_RGB( 64, 255, 255), /* B */
|
|
BitmapColor_RGB(255, 64, 64), /* C */
|
|
BitmapColor_RGB(255, 64, 255), /* D */
|
|
BitmapColor_RGB(255, 255, 64), /* E */
|
|
BitmapColor_RGB(255, 255, 255), /* F */
|
|
};
|
|
|
|
static void OnReset(void) {
|
|
Mem_Set(Drawer2D.Colors, 0, sizeof(Drawer2D.Colors));
|
|
|
|
Mem_Copy(&Drawer2D.Colors['0'], defaults_0_9, sizeof(defaults_0_9));
|
|
Mem_Copy(&Drawer2D.Colors['a'], defaults_a_f, sizeof(defaults_a_f));
|
|
Mem_Copy(&Drawer2D.Colors['A'], defaults_a_f, sizeof(defaults_a_f));
|
|
}
|
|
|
|
static void OnInit(void) {
|
|
OnReset();
|
|
TextureEntry_Register(&default_entry);
|
|
|
|
Drawer2D.BitmappedText = Game_ClassicMode || !Options_GetBool(OPT_USE_CHAT_FONT, false);
|
|
Drawer2D.BlackTextShadows = Options_GetBool(OPT_BLACK_TEXT, false);
|
|
}
|
|
|
|
static void OnFree(void) {
|
|
FreeFontBitmap();
|
|
fontBitmap.scan0 = NULL;
|
|
}
|
|
|
|
struct IGameComponent Drawer2D_Component = {
|
|
OnInit, /* Init */
|
|
OnFree, /* Free */
|
|
OnReset, /* Reset */
|
|
};
|