summary refs log tree commit diff
path: root/src/Drawer2D.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Drawer2D.c
initial commit
Diffstat (limited to 'src/Drawer2D.c')
-rw-r--r--src/Drawer2D.c714
1 files changed, 714 insertions, 0 deletions
diff --git a/src/Drawer2D.c b/src/Drawer2D.c
new file mode 100644
index 0000000..7319024
--- /dev/null
+++ b/src/Drawer2D.c
@@ -0,0 +1,714 @@
+#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.SupportsNonPowTwoTextures) {
+		/* Allocate power-of-2 sized bitmap equal to or greater than the given size */
+		width  = Math_NextPowOf2(width);
+		height = Math_NextPowOf2(height);
+	}
+	
+	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, 4, "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;
+	Gfx_RecreateTexture(&tex->ID, &ctx->bmp, flags, false);
+	
+	tex->width  = ctx->width;
+	tex->height = ctx->height;
+	
+	tex->uv.u1  = 0.0f; tex->uv.v1 = 0.0f;
+	tex->uv.u2  = (float)ctx->width  / (float)ctx->bmp.width;
+	tex->uv.v2  = (float)ctx->height / (float)ctx->bmp.height;
+}
+
+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 */
+};