diff --git a/neo/engine/renderer/Font.cpp b/neo/engine/renderer/Font.cpp
new file mode 100644
index 00000000..b218c062
--- /dev/null
+++ b/neo/engine/renderer/Font.cpp
@@ -0,0 +1,508 @@
+/*
+===========================================================================
+
+Doom 3 BFG Edition GPL Source Code
+Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
+
+This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
+
+Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Doom 3 BFG Edition Source Code. If not, see .
+
+In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
+
+If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
+
+===========================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "tr_local.h"
+
+const char* DEFAULT_FONT = "Gothic821_Cn_BT";
+
+static const float old_scale2 = 0.6f;
+static const float old_scale1 = 0.3f;
+
+/*
+==============================
+Old_SelectValueForScale
+==============================
+*/
+ID_INLINE float Old_SelectValueForScale(float scale, float v0, float v1, float v2) {
+ return (scale >= old_scale2) ? v2 : (scale >= old_scale1) ? v1 : v0;
+}
+
+static void Font_ReadBigInt(idFile *fd, int &value) {
+ fd->Read(&value, sizeof(value));
+ value = BigLong(value);
+}
+
+static void Font_ReadBigShort(idFile *fd, short &value) {
+ fd->Read(&value, sizeof(value));
+ value = BigShort(value);
+}
+
+static bool Font_ReadTGAHeader(const char* filename, int &width, int &height) {
+ width = 0;
+ height = 0;
+
+ idFile* fd = fileSystem->OpenFileRead(filename);
+ if (fd == NULL) {
+ return false;
+ }
+
+ byte header[18];
+ const int bytesRead = fd->Read(header, sizeof(header));
+ fileSystem->CloseFile(fd);
+
+ if (bytesRead != sizeof(header)) {
+ return false;
+ }
+
+ width = header[12] | (header[13] << 8);
+ height = header[14] | (header[15] << 8);
+ return width > 0 && height > 0;
+}
+
+/*
+==============================
+R_NormalizeFontName
+==============================
+*/
+static idStr R_NormalizeFontName(const char* fontName) {
+ idStr cleanName = fontName ? fontName : "";
+ cleanName.StripQuotes();
+ cleanName.BackSlashesToSlashes();
+ cleanName.StripFileExtension();
+
+ if (cleanName.IsEmpty() || cleanName.Icmp("fonts") == 0 || cleanName.Icmp("default") == 0) {
+ return DEFAULT_FONT;
+ }
+
+ const char* newFontPrefix = "newfonts/";
+ if (idStr::Icmpn(cleanName.c_str(), newFontPrefix, strlen(newFontPrefix)) == 0) {
+ idStr rest = cleanName.c_str() + strlen(newFontPrefix);
+ int slash = rest.Find('/');
+ if (slash >= 0) {
+ rest = rest.Left(slash);
+ }
+ return rest.IsEmpty() ? DEFAULT_FONT : rest;
+ }
+
+ cleanName.StripPath();
+ return cleanName.IsEmpty() ? DEFAULT_FONT : cleanName;
+}
+
+/*
+==============================
+idRenderSystemLocal::RegisterFont
+==============================
+*/
+idFont* idRenderSystemLocal::RegisterFont(const char* fontName) {
+ idStr cleanName = R_NormalizeFontName(fontName);
+
+ for (int i = 0; i < fonts.Num(); i++) {
+ if (idStr::Icmp(fonts[i]->GetName(), cleanName.c_str()) == 0) {
+ fonts[i]->Touch();
+ return fonts[i];
+ }
+ }
+
+ idFont* font = new idFont(cleanName.c_str());
+ fonts.Append(font);
+ return font;
+}
+
+/*
+==============================
+idRenderSystemLocal::FreeFonts
+==============================
+*/
+void idRenderSystemLocal::FreeFonts() {
+ fonts.DeleteContents(true);
+}
+
+/*
+==============================
+idFont::RemapFont
+==============================
+*/
+idFont* idFont::RemapFont(const char* baseName) {
+ idStr cleanName = R_NormalizeFontName(baseName);
+
+ if (cleanName.Icmp(DEFAULT_FONT) == 0) {
+ return NULL;
+ }
+
+ return NULL;
+}
+
+/*
+==============================
+idFont::~idFont
+==============================
+*/
+idFont::~idFont() {
+ if (fontInfo != NULL) {
+ Mem_Free(fontInfo->glyphData);
+ Mem_Free(fontInfo->charIndex);
+ delete fontInfo;
+ }
+}
+
+/*
+==============================
+idFont::idFont
+==============================
+*/
+idFont::idFont(const char* n) : name(R_NormalizeFontName(n)) {
+ fontInfo = NULL;
+ alias = RemapFont(name.c_str());
+
+ if (alias != NULL) {
+ for (idFont* f = alias; f != NULL; f = f->alias) {
+ if (f == this) {
+ common->FatalError("Font alias \"%s\" is a circular reference!", n);
+ }
+ }
+ return;
+ }
+
+ if (!LoadFont()) {
+ if (name.Icmp(DEFAULT_FONT) == 0) {
+ common->FatalError("Could not load default font \"%s\"", DEFAULT_FONT);
+ }
+ else {
+ common->Warning("Could not load font %s", n);
+ alias = renderSystem->RegisterFont(DEFAULT_FONT);
+ }
+ }
+}
+
+struct oldGlyphInfo_t {
+ int height;
+ int top;
+ int bottom;
+ int pitch;
+ int xSkip;
+ int imageWidth;
+ int imageHeight;
+ float s;
+ float t;
+ float s2;
+ float t2;
+ int junk;
+ char materialName[32];
+};
+static const int GLYPHS_PER_FONT = 256;
+
+/*
+==============================
+LoadOldGlyphData
+==============================
+*/
+bool LoadOldGlyphData(const char* filename, oldGlyphInfo_t glyphInfo[GLYPHS_PER_FONT]) {
+ idFile* fd = fileSystem->OpenFileRead(filename);
+ if (fd == NULL) {
+ return false;
+ }
+ fd->Read(glyphInfo, GLYPHS_PER_FONT * sizeof(oldGlyphInfo_t));
+ for (int i = 0; i < GLYPHS_PER_FONT; i++) {
+ glyphInfo[i].height = LittleLong(glyphInfo[i].height);
+ glyphInfo[i].top = LittleLong(glyphInfo[i].top);
+ glyphInfo[i].bottom = LittleLong(glyphInfo[i].bottom);
+ glyphInfo[i].pitch = LittleLong(glyphInfo[i].pitch);
+ glyphInfo[i].xSkip = LittleLong(glyphInfo[i].xSkip);
+ glyphInfo[i].imageWidth = LittleLong(glyphInfo[i].imageWidth);
+ glyphInfo[i].imageHeight = LittleLong(glyphInfo[i].imageHeight);
+ glyphInfo[i].s = LittleFloat(glyphInfo[i].s);
+ glyphInfo[i].t = LittleFloat(glyphInfo[i].t);
+ glyphInfo[i].s2 = LittleFloat(glyphInfo[i].s2);
+ glyphInfo[i].t2 = LittleFloat(glyphInfo[i].t2);
+ assert(glyphInfo[i].imageWidth == glyphInfo[i].pitch);
+ assert(glyphInfo[i].imageHeight == glyphInfo[i].height);
+ assert(glyphInfo[i].imageWidth == (glyphInfo[i].s2 - glyphInfo[i].s) * 256);
+ assert(glyphInfo[i].imageHeight == (glyphInfo[i].t2 - glyphInfo[i].t) * 256);
+ assert(glyphInfo[i].junk == 0);
+ }
+ fileSystem->CloseFile(fd);
+ return true;
+}
+
+/*
+==============================
+idFont::LoadFont
+==============================
+*/
+bool idFont::LoadFont() {
+ idStr fontName = va("newfonts/%s/48.dat", GetName());
+ idFile* fd = fileSystem->OpenFileRead(fontName);
+ if (fd == NULL) {
+ return false;
+ }
+
+ const int FONT_INFO_VERSION = 42;
+ const int FONT_INFO_MAGIC = (FONT_INFO_VERSION | ('i' << 24) | ('d' << 16) | ('f' << 8));
+
+ int version = 0;
+ Font_ReadBigInt(fd, version);
+ if (version != FONT_INFO_MAGIC) {
+ idLib::Warning("Wrong version in %s", GetName());
+ fileSystem->CloseFile(fd);
+ return false;
+ }
+
+ fontInfo = new fontInfo_t;
+ memset(fontInfo, 0, sizeof(*fontInfo));
+
+ short pointSize = 0;
+
+ Font_ReadBigShort(fd, pointSize);
+ assert(pointSize == 48);
+
+ Font_ReadBigShort(fd, fontInfo->ascender);
+ Font_ReadBigShort(fd, fontInfo->descender);
+
+ Font_ReadBigShort(fd, fontInfo->numGlyphs);
+
+ fontInfo->glyphData = (glyphInfo_t*)Mem_Alloc(sizeof(glyphInfo_t) * fontInfo->numGlyphs);
+ fontInfo->charIndex = (uint32_t*)Mem_Alloc(sizeof(uint32_t) * fontInfo->numGlyphs);
+
+ const int GLYPH_RECORD_SIZE = 10;
+ for (int i = 0; i < fontInfo->numGlyphs; i++) {
+ byte glyphBytes[GLYPH_RECORD_SIZE];
+ if (fd->Read(glyphBytes, sizeof(glyphBytes)) != sizeof(glyphBytes)) {
+ common->Warning("Short glyph data in %s", fontName.c_str());
+ fileSystem->CloseFile(fd);
+ return false;
+ }
+
+ fontInfo->glyphData[i].width = glyphBytes[0];
+ fontInfo->glyphData[i].height = glyphBytes[1];
+ fontInfo->glyphData[i].top = (char)glyphBytes[2];
+ fontInfo->glyphData[i].left = (char)glyphBytes[3];
+ fontInfo->glyphData[i].xSkip = glyphBytes[4];
+ fontInfo->glyphData[i].s = glyphBytes[6] | (glyphBytes[7] << 8);
+ fontInfo->glyphData[i].t = glyphBytes[8] | (glyphBytes[9] << 8);
+ }
+
+ fd->Read(fontInfo->charIndex, fontInfo->numGlyphs * sizeof(uint32_t));
+ for (int i = 0; i < fontInfo->numGlyphs; i++) {
+ fontInfo->charIndex[i] = LittleLong(fontInfo->charIndex[i]);
+ }
+
+ memset(fontInfo->ascii, -1, sizeof(fontInfo->ascii));
+ for (int i = 0; i < fontInfo->numGlyphs; i++) {
+ if (fontInfo->charIndex[i] < 128) {
+ fontInfo->ascii[fontInfo->charIndex[i]] = i;
+ }
+ else {
+ break;
+ }
+ }
+
+ idStr fontTextureName = fontName;
+ fontTextureName.SetFileExtension("tga");
+
+ fontInfo->material = declManager->FindMaterial(fontTextureName);
+ fontInfo->material->SetSort(SS_GUI);
+ if (!Font_ReadTGAHeader(fontTextureName.c_str(), fontInfo->materialWidth, fontInfo->materialHeight)) {
+ fontInfo->materialWidth = fontInfo->material->GetImageWidth();
+ fontInfo->materialHeight = fontInfo->material->GetImageHeight();
+ }
+
+ int pointSizes[3] = { 12, 24, 48 };
+ float scales[3] = { 4.0f, 2.0f, 1.0f };
+ for (int i = 0; i < 3; i++) {
+ oldGlyphInfo_t oldGlyphInfo[GLYPHS_PER_FONT];
+ const char* oldFileName = va("newfonts/%s/old_%d.dat", GetName(), pointSizes[i]);
+ if (LoadOldGlyphData(oldFileName, oldGlyphInfo)) {
+ int mh = 0;
+ int mw = 0;
+ for (int g = 0; g < GLYPHS_PER_FONT; g++) {
+ if (mh < oldGlyphInfo[g].height) {
+ mh = oldGlyphInfo[g].height;
+ }
+ if (mw < oldGlyphInfo[g].xSkip) {
+ mw = oldGlyphInfo[g].xSkip;
+ }
+ }
+ fontInfo->oldInfo[i].maxWidth = scales[i] * mw;
+ fontInfo->oldInfo[i].maxHeight = scales[i] * mh;
+ }
+ else {
+ int mh = 0;
+ int mw = 0;
+ for (int g = 0; g < fontInfo->numGlyphs; g++) {
+ if (mh < fontInfo->glyphData[g].height) {
+ mh = fontInfo->glyphData[g].height;
+ }
+ if (mw < fontInfo->glyphData[g].xSkip) {
+ mw = fontInfo->glyphData[g].xSkip;
+ }
+ }
+ fontInfo->oldInfo[i].maxWidth = mw;
+ fontInfo->oldInfo[i].maxHeight = mh;
+ }
+ }
+ fileSystem->CloseFile(fd);
+ return true;
+}
+
+/*
+==============================
+idFont::GetGlyphIndex
+==============================
+*/
+int idFont::GetGlyphIndex(uint32_t idx) const {
+ if (idx < 128) {
+ return fontInfo->ascii[idx];
+ }
+ if (fontInfo->numGlyphs == 0) {
+ return -1;
+ }
+ if (fontInfo->charIndex == NULL) {
+ return idx;
+ }
+ int len = fontInfo->numGlyphs;
+ int mid = fontInfo->numGlyphs;
+ int offset = 0;
+ while (mid > 0) {
+ mid = len >> 1;
+ if (fontInfo->charIndex[offset + mid] <= idx) {
+ offset += mid;
+ }
+ len -= mid;
+ }
+ return (fontInfo->charIndex[offset] == idx) ? offset : -1;
+}
+
+/*
+==============================
+idFont::GetLineHeight
+==============================
+*/
+float idFont::GetLineHeight(float scale) const {
+ if (alias != NULL) {
+ return alias->GetLineHeight(scale);
+ }
+ if (fontInfo != NULL) {
+ return scale * Old_SelectValueForScale(scale, fontInfo->oldInfo[0].maxHeight, fontInfo->oldInfo[1].maxHeight, fontInfo->oldInfo[2].maxHeight);
+ }
+ return 0.0f;
+}
+
+/*
+==============================
+idFont::GetAscender
+==============================
+*/
+float idFont::GetAscender(float scale) const {
+ if (alias != NULL) {
+ return alias->GetAscender(scale);
+ }
+ if (fontInfo != NULL) {
+ return scale * fontInfo->ascender;
+ }
+ return 0.0f;
+}
+
+/*
+==============================
+idFont::GetMaxCharWidth
+==============================
+*/
+float idFont::GetMaxCharWidth(float scale) const {
+ if (alias != NULL) {
+ return alias->GetMaxCharWidth(scale);
+ }
+ if (fontInfo != NULL) {
+ return scale * Old_SelectValueForScale(scale, fontInfo->oldInfo[0].maxWidth, fontInfo->oldInfo[1].maxWidth, fontInfo->oldInfo[2].maxWidth);
+ }
+ return 0.0f;
+}
+
+/*
+==============================
+idFont::GetGlyphWidth
+==============================
+*/
+float idFont::GetGlyphWidth(float scale, uint32_t idx) const {
+ if (alias != NULL) {
+ return alias->GetGlyphWidth(scale, idx);
+ }
+ if (fontInfo != NULL) {
+ int i = GetGlyphIndex(idx);
+ const int asterisk = 42;
+ if (i == -1 && idx != asterisk) {
+ i = GetGlyphIndex(asterisk);
+ }
+ if (i >= 0) {
+ return scale * fontInfo->glyphData[i].xSkip;
+ }
+ }
+ return 0.0f;
+}
+
+/*
+==============================
+idFont::GetScaledGlyph
+==============================
+*/
+void idFont::GetScaledGlyph(float scale, uint32_t idx, scaledGlyphInfo_t& glyphInfo) const {
+ if (alias != NULL) {
+ return alias->GetScaledGlyph(scale, idx, glyphInfo);
+ }
+ if (fontInfo != NULL) {
+ int i = GetGlyphIndex(idx);
+ const int asterisk = 42;
+ if (i == -1 && idx != asterisk) {
+ i = GetGlyphIndex(asterisk);
+ }
+ if (i >= 0) {
+ float invMaterialWidth = 1.0f / fontInfo->materialWidth;
+ float invMaterialHeight = 1.0f / fontInfo->materialHeight;
+ glyphInfo_t& gi = fontInfo->glyphData[i];
+ glyphInfo.xSkip = scale * gi.xSkip;
+ glyphInfo.top = scale * gi.top;
+ glyphInfo.left = scale * gi.left;
+ glyphInfo.width = scale * gi.width;
+ glyphInfo.height = scale * gi.height;
+ glyphInfo.s1 = (gi.s - 0.5f) * invMaterialWidth;
+ glyphInfo.t1 = (gi.t - 0.5f) * invMaterialHeight;
+ glyphInfo.s2 = (gi.s + gi.width + 0.5f) * invMaterialWidth;
+ glyphInfo.t2 = (gi.t + gi.height + 0.5f) * invMaterialHeight;
+ glyphInfo.material = fontInfo->material;
+ return;
+ }
+ }
+ memset(&glyphInfo, 0, sizeof(glyphInfo));
+}
+
+/*
+==============================
+idFont::Touch
+==============================
+*/
+void idFont::Touch() {
+ if (alias != NULL) {
+ alias->Touch();
+ }
+ if (fontInfo != NULL) {
+ const_cast(fontInfo->material)->EnsureNotPurged();
+ fontInfo->material->SetSort(SS_GUI);
+ }
+}