chore: initial commit

This commit is contained in:
fallenoak 2023-01-02 13:17:18 -06:00
commit 70b00c5c38
No known key found for this signature in database
GPG key ID: 7628F8E61AEA070D
965 changed files with 264882 additions and 0 deletions

928
src/gx/font/CGxFont.cpp Normal file
View file

@ -0,0 +1,928 @@
#include "gx/font/CGxFont.hpp"
#include "gx/font/FontFace.hpp"
#include "gx/Texture.hpp"
#include <cmath>
#include <cstring>
#include <storm/Error.hpp>
#include <storm/String.hpp>
#include <storm/Unicode.hpp>
uint16_t TEXTURECACHE::s_textureData[256 * 256];
GLYPHBITMAPDATA::~GLYPHBITMAPDATA() {
if (this->m_data) {
SMemFree(this->m_data, __FILE__, __LINE__, 0x0);
}
this->m_data = nullptr;
}
void GLYPHBITMAPDATA::CopyFrom(GLYPHBITMAPDATA* data) {
if (this->m_data) {
SMemFree(this->m_data, __FILE__, __LINE__, 0);
}
this->m_data = nullptr;
*this = *data;
data->m_data = nullptr;
}
uint32_t CHARCODEDESC::GapToNextTexture() {
CHARCODEDESC* next = this->textureRowLink.Next();
return next
? next->glyphStartPixel - this->glyphEndPixel - 1
: 255 - this->glyphEndPixel;
}
uint32_t CHARCODEDESC::GapToPreviousTexture() {
CHARCODEDESC* previous = this->textureRowLink.Prev();
return previous
? this->glyphStartPixel - previous->glyphEndPixel - 1
: this->glyphStartPixel;
}
void CHARCODEDESC::GenerateTextureCoords(uint32_t rowNumber, uint32_t glyphSide) {
this->bitmapData.m_textureCoords.minY = (glyphSide * rowNumber) / 256.0f;
this->bitmapData.m_textureCoords.minX = this->glyphStartPixel / 256.0f;
this->bitmapData.m_textureCoords.maxY = ((glyphSide * rowNumber) + glyphSide) / 256.0f;
this->bitmapData.m_textureCoords.maxX = (this->glyphStartPixel + this->bitmapData.m_glyphCellWidth) / 256.0f;
}
KERNINGHASHKEY& KERNINGHASHKEY::operator=(const KERNINGHASHKEY& rhs) {
if (this->code != rhs.code) {
this->code = rhs.code;
}
return *this;
}
bool KERNINGHASHKEY::operator==(const KERNINGHASHKEY& rhs) {
return this->code == rhs.code;
}
CHARCODEDESC* TEXTURECACHEROW::CreateNewDesc(GLYPHBITMAPDATA* data, uint32_t rowNumber, uint32_t glyphCellHeight) {
uint32_t glyphWidth = data->m_glyphCellWidth;
if (this->widestFreeSlot < glyphWidth) {
return nullptr;
}
if (!this->glyphList.Head()) {
CHARCODEDESC* newCode = this->glyphList.NewNode(2, 0, 0);
newCode->glyphStartPixel = 0;
newCode->glyphEndPixel = glyphWidth - 1;
newCode->rowNumber = rowNumber;
newCode->bitmapData.CopyFrom(data);
newCode->GenerateTextureCoords(rowNumber, glyphCellHeight);
this->widestFreeSlot -= glyphWidth;
return newCode;
}
uint32_t gapToPrevious = this->glyphList.Head()->GapToPreviousTexture();
this->widestFreeSlot = gapToPrevious;
if (gapToPrevious >= glyphWidth) {
void* m = SMemAlloc(sizeof(CHARCODEDESC), __FILE__, __LINE__, 0);
CHARCODEDESC* newCode = new (m) CHARCODEDESC();
this->glyphList.LinkNode(newCode, 2, this->glyphList.Head());
newCode->glyphEndPixel = this->glyphList.Head()->glyphStartPixel - 1;
newCode->glyphStartPixel = newCode->glyphEndPixel - glyphWidth + 1;
newCode->rowNumber = rowNumber;
newCode->bitmapData.CopyFrom(data);
newCode->GenerateTextureCoords(rowNumber, glyphCellHeight);
this->widestFreeSlot = this->glyphList.Head()
? this->glyphList.Head()->GapToPreviousTexture()
: 0;
for (auto code = this->glyphList.Head(); code; code = this->glyphList.Link(code)->Next()) {
uint32_t gapToNext = code->GapToNextTexture();
if (gapToNext > this->widestFreeSlot) {
this->widestFreeSlot = gapToNext;
}
}
return newCode;
}
int32_t inserted = 0;
CHARCODEDESC* newCode = nullptr;
for (auto code = this->glyphList.Head(); code; code = this->glyphList.Link(code)->Next()) {
uint32_t gapToNext = code->GapToNextTexture();
if (inserted) {
if (gapToNext > this->widestFreeSlot) {
this->widestFreeSlot = gapToNext;
}
continue;
}
if (gapToNext >= glyphWidth) {
void* m = SMemAlloc(sizeof(CHARCODEDESC), __FILE__, __LINE__, 0);
newCode = new (m) CHARCODEDESC();
this->glyphList.LinkNode(newCode, 1, code);
newCode->glyphStartPixel = code->glyphEndPixel + 1;
newCode->glyphEndPixel = code->glyphEndPixel + glyphWidth;
newCode->rowNumber = rowNumber;
newCode->bitmapData.CopyFrom(data);
newCode->GenerateTextureCoords(rowNumber, glyphCellHeight);
inserted = 1;
}
}
return newCode;
}
void TEXTURECACHEROW::EvictGlyph(CHARCODEDESC* desc) {
// TODO
}
void TEXTURECACHE::TextureCallback(EGxTexCommand cmd, uint32_t w, uint32_t h, uint32_t d, uint32_t mipLevel, void* userArg, uint32_t& texelStrideInBytes, const void*& texels) {
TEXTURECACHE* cache = static_cast<TEXTURECACHE*>(userArg);
switch (cmd) {
case GxTex_Latch: {
for (int32_t i = cache->m_textureRows.Count() - 1; i >= 0; i--) {
auto& cacheRow = cache->m_textureRows[i];
for (auto glyph = cacheRow.glyphList.Head(); glyph; glyph = cacheRow.glyphList.Next(glyph)) {
cache->WriteGlyphToTexture(glyph);
}
}
texelStrideInBytes = 512;
texels = TEXTURECACHE::s_textureData;
break;
};
}
}
CHARCODEDESC* TEXTURECACHE::AllocateNewGlyph(GLYPHBITMAPDATA* data) {
for (int32_t i = 0; i < this->m_textureRows.Count(); i++) {
auto& cacheRow = this->m_textureRows[i];
CHARCODEDESC* glyph = cacheRow.CreateNewDesc(data, i, this->m_theFace->m_cellHeight);
if (glyph) {
return glyph;
}
}
return nullptr;
}
void TEXTURECACHE::CreateTexture(int32_t filter) {
CGxTexFlags flags = CGxTexFlags(filter ? GxTex_Linear : GxTex_Nearest, 0, 0, 0, 0, 0, 1);
HTEXTURE texture = TextureCreate(
256,
256,
GxTex_Argb4444,
GxTex_Argb4444,
flags,
this,
TEXTURECACHE::TextureCallback,
"GxuFont",
0
);
this->m_texture = texture;
}
void TEXTURECACHE::Initialize(CGxFont* face, uint32_t pixelSize) {
this->m_theFace = face;
uint32_t rowCount = 256 / pixelSize;
this->m_textureRows.SetCount(rowCount);
for (int32_t i = 0; i < rowCount; i++) {
this->m_textureRows[i].widestFreeSlot = 256;
}
}
void TEXTURECACHE::PasteGlyph(const GLYPHBITMAPDATA& data, uint16_t* dst) {
if (this->m_theFace->m_flags & FONT_OUTLINE) {
if (this->m_theFace->m_flags & FONT_MONOCHROME) {
this->PasteGlyphOutlinedMonochrome(data, dst);
} else {
this->PasteGlyphOutlinedAA(data, dst);
}
} else if (this->m_theFace->m_flags & FONT_MONOCHROME) {
this->PasteGlyphNonOutlinedMonochrome(data, dst);
} else {
this->PasteGlyphNonOutlinedAA(data, dst);
}
}
void TEXTURECACHE::PasteGlyphNonOutlinedAA(const GLYPHBITMAPDATA& glyphData, uint16_t* dst) {
auto src = reinterpret_cast<uint8_t*>(glyphData.m_data);
auto pitch = glyphData.m_glyphPitch;
auto dstCellStride = glyphData.m_glyphCellWidth * 2;
for (int32_t y = 0; y < glyphData.m_yStart; y++) {
memset(dst, 0, dstCellStride);
dst += 256;
}
for (int32_t y = 0; y < glyphData.m_glyphHeight; y++) {
for (int32_t x = 0; x < glyphData.m_glyphWidth; x++) {
dst[x] = ((src[x] & 0xF0) << 8) | 0xFFF;
}
src += pitch;
dst += 256;
}
auto glyphHeight = glyphData.m_glyphHeight;
auto yStart = glyphData.m_yStart;
if (this->m_theFace->m_cellHeight - glyphHeight - yStart > 0 && this->m_theFace->m_cellHeight - glyphHeight != yStart) {
for (int32_t y = 0; y < this->m_theFace->m_cellHeight - glyphHeight - yStart; y++) {
memset(dst, 0, dstCellStride);
dst += 256;
}
}
}
void TEXTURECACHE::PasteGlyphNonOutlinedMonochrome(const GLYPHBITMAPDATA& data, uint16_t* dst) {
// TODO
}
void TEXTURECACHE::PasteGlyphOutlinedAA(const GLYPHBITMAPDATA& glyphData, uint16_t* dst) {
uint32_t v6;
uint32_t v7;
uint16_t* v8;
int32_t v9;
int32_t v15;
int32_t v16;
uint32_t v17;
uint32_t v18;
uint32_t v19;
uint32_t v20;
int32_t v21;
int32_t v22;
int32_t v23;
int32_t v24;
int32_t v25;
uint32_t v26;
uint32_t v27;
uint32_t v28;
bool v29;
bool v30;
int32_t v31;
int32_t v32;
int32_t v33;
int32_t v34;
int32_t v35;
int32_t v36;
bool v37;
int32_t v38;
int32_t v39;
int32_t v40;
uint32_t v41;
uint8_t v42;
bool v43;
uint16_t v44[9216];
uint16_t v45[9216];
uint16_t v46[9216];
uint32_t v49;
uint16_t* v52;
uint32_t v52_2;
uint32_t v52_3;
int32_t v53;
static uint8_t pixelsLitLevels[] = {
0, 1, 1, 3, 5, 7, 9, 0xB, 0xD, 0xF, 0, 0
};
const char* src = reinterpret_cast<char*>(glyphData.m_data);
uint32_t thick = this->m_theFace->m_flags & 0x8;
memset(v45, 0, sizeof(v45));
memset(v46, 0, sizeof(v46));
uint32_t ofs = thick
? 256 * glyphData.m_yStart + 258
: 256 * glyphData.m_yStart + 257;
for (int32_t y = 0; y < glyphData.m_glyphHeight; y++) {
for (int32_t x = 0; x < glyphData.m_glyphWidth; x++) {
uint8_t v10 = src[(y * glyphData.m_glyphPitch) + x];
if (v10) {
v45[ofs + (y * 256) + x] = v10;
v46[ofs + (y * 256) + x] = 1;
}
}
}
v52_2 = 0;
v15 = glyphData.m_yStart - 1;
for (int32_t i = 0; i < (thick != 0) + 1; i++) {
v49 = 2 * (i != 0) + 2;
v16 = 2 * (i != 0) + 1;
v17 = v15 < 0 ? 0 : v15;
v53 = v15 < 0 ? 0 : v15;
if (v17 >= this->m_theFace->m_cellHeight) {
continue;
}
do {
v18 = glyphData.m_glyphCellWidth;
v19 = v17 << 8;
v20 = 0;
if (!v18) {
goto LABEL_68;
}
do {
v21 = v19 + v20;
if (v46[v19 + v20] & v16) {
goto LABEL_66;
}
if (v53) {
if (v53 == this->m_theFace->m_cellHeight - 1) {
if (v20) {
if (v20 == v18 - 1) {
if (
v46[v21 - 1] & v16
|| v46[v19 + v20 - 257] & v16
) {
goto LABEL_65;
}
v22 = v46[v19 + v20 - 256];
} else {
if (
v46[v19 + v20 - 257] & v16
|| v46[v19 + v20 - 256] & v16
|| v46[v19 + v20 - 255] & v16
|| v46[v21 - 1] & v16
) {
goto LABEL_65;
}
v22 = v46[v21 + 1];
}
} else {
if (
v46[v19 - 256] & v16
|| v46[v19 - 255] & v16
) {
goto LABEL_65;
}
v22 = v46[v19 + 1];
}
} else if (v20) {
v24 = v19 + v20;
if (v20 == v18 - 1) {
if (
v46[v24 - 256] & v16
|| v46[v19 + v20 - 257] & v16
|| v46[v21 - 1] & v16
|| v46[v21 + 255] & v16
) {
goto LABEL_65;
}
v22 = v46[v21 + 256];
} else {
if (
v46[v24 - 257] & v16
|| v46[v19 + v20 - 256] & v16
|| v46[v19 + v20 - 255] & v16
|| v46[v21 - 1] & v16
|| v46[v21 + 1] & v16
|| v46[v21 + 255] & v16
|| v46[v21 + 256] & v16
) {
goto LABEL_65;
}
v22 = v46[v21 + 257];
}
} else {
if (
v46[v19 - 256] & v16
|| v46[v19 - 255] & v16
|| v46[v19 + 1] & v16
|| v46[v19 + 257] & v16
) {
goto LABEL_65;
}
v22 = v46[v19 + 256];
}
LABEL_64:
if (!(v22 & v16)) {
goto LABEL_66;
}
goto LABEL_65;
}
if (v20) {
v23 = v46[v20 - 1];
if (v20 == v18 - 1) {
if (!(v23 & v16) && !(v46[v20 + 255] & v16)) {
v22 = v46[v20 + 256];
goto LABEL_64;
}
} else if (!(v23 & v16) && !(v46[v20 + 1] & v16) && !(v46[v20 + 255] & v16) && !(v46[v20 + 256] & v16)) {
v22 = v46[v20 + 257];
goto LABEL_64;
}
} else if (!(v46[1] & v16) && !(v46[257] & v16)) {
v22 = v46[256];
goto LABEL_64;
}
LABEL_65:
v46[v21] = v49;
LABEL_66:
++v20;
} while (v20 < v18);
v17 = v53;
LABEL_68:
v53 = ++v17;
} while (v17 < this->m_theFace->m_cellHeight);
}
memset(v44, 0, sizeof(v44));
v26 = 0;
v53 = 0;
if (!this->m_theFace->m_cellHeight) {
goto LABEL_95;
}
while (2) {
v27 = glyphData.m_glyphCellWidth;
v28 = v26 << 8;
v25 = 0;
if (!v27) {
goto LABEL_94;
}
while (2) {
v37 = v46[v28 + v25] == 0;
v52 = &v46[v28 + v25];
v29 = !v37;
v30 = !v37;
if (v26) {
if (v53 == this->m_theFace->m_cellHeight - 1) {
if (!v25) {
v31 = v30 + (v46[v28 + 1] != 0) + (v46[v28 - 255] != 0) + (v46[v28 - 256] != 0);
goto LABEL_92;
}
v37 = v25 == v27 - 1;
v34 = v28 + v25;
if (v37) {
v31 = (v46[v28 + v25 - 256] != 0) + (v46[v34 - 257] != 0) + v30 + (*(v52 - 1) != 0);
goto LABEL_92;
}
v35 = (v46[v34 - 256] != 0) + (v46[v28 + v25 - 257] != 0);
v36 = 0;
v37 = v46[v28 + v25 - 255] == 0;
v38 = v28 + v25;
} else {
if (!v25) {
v31 = v30
+ (v46[v28 + 256] != 0)
+ (v46[v28 + 1] != 0)
+ (v46[v28 + 257] != 0)
+ (v46[v28 - 255] != 0)
+ (v46[v28 - 256] != 0);
goto LABEL_92;
}
v37 = v25 == v27 - 1;
v39 = v28 + v25;
if (v37) {
v31 = (v46[v28 + v25 + 256] != 0)
+ (v46[v28 + v25 + 255] != 0)
+ (v46[v28 + v25 - 256] != 0)
+ (v46[v39 - 257] != 0)
+ v30
+ (*(v52 - 1) != 0);
goto LABEL_92;
}
v37 = v46[v39 - 256] == 0;
v38 = v28 + v25;
v35 = (v46[v28 + v25 + 256] != 0)
+ (v46[v28 + v25 + 255] != 0)
+ (v46[v28 + v25 - 255] != 0)
+ !v37
+ (v46[v28 + v25 - 257] != 0);
v36 = 0;
v37 = v46[v28 + v25 + 257] == 0;
}
v36 = !v37;
v31 = (v46[v38 + 1] != 0) + v36 + v35 + v30 + (*(v52 - 1) != 0);
} else if (v25) {
if (v25 == v27 - 1) {
v32 = (v46[v25 + 255] != 0) + (v46[v25 - 1] != 0);
v33 = v29 + (v46[v25 + 256] != 0);
} else {
v32 = (v46[v25 + 257] != 0)
+ (v46[v25 + 256] != 0)
+ (v46[v25 + 255] != 0)
+ (v46[v25 - 1] != 0);
v33 = v29 + (v46[v25 + 1] != 0);
}
v31 = v33 + v32;
} else {
v31 = v29 + (v46[1] != 0) + (v46[257] != 0) + (v46[256] != 0);
}
LABEL_92:
v26 = v53;
v44[v28 + v25] = pixelsLitLevels[v31];
v27 = glyphData.m_glyphCellWidth;
if (++v25 < v27) {
continue;
}
break;
}
LABEL_94:
v53 = ++v26;
if (v26 < this->m_theFace->m_cellHeight) {
continue;
}
break;
}
LABEL_95:
v40 = 0;
for (int32_t y = 0; y < this->m_theFace->m_cellHeight; y++) {
for (int32_t x = 0; x < glyphData.m_glyphCellWidth; x++) {
if (v46[v40 + x]) {
if (v45[v40 + x]) {
v42 = (255 * v45[v40 + x]) >> 12;
dst[v40 + x] = v42 | (16 * (v42 | (16 * (v42 | (16 * v44[v40 + x])))));
} else {
dst[v40 + x] = v44[v40 + x] << 12;
}
} else {
dst[v40 + x] = 0;
}
}
v40 += 256;
}
}
void TEXTURECACHE::PasteGlyphOutlinedMonochrome(const GLYPHBITMAPDATA& data, uint16_t* dst) {
// TODO
}
void TEXTURECACHE::UpdateDirty() {
if (this->m_anyDirtyGlyphs && this->m_texture) {
CGxTex* gxTex = TextureGetGxTex(this->m_texture, 1, nullptr);
CiRect updateRect = { 0, 0, 256, 256 };
GxTexUpdate(gxTex, updateRect, 1);
this->m_anyDirtyGlyphs = 0;
}
}
void TEXTURECACHE::WriteGlyphToTexture(CHARCODEDESC* glyph) {
if (!this->m_texture || !this->m_theFace || !this->m_theFace->m_cellHeight) {
return;
}
uint32_t ofs = glyph->glyphStartPixel + (glyph->rowNumber * this->m_theFace->m_cellHeight << 8);
uint16_t* ptr = &TEXTURECACHE::s_textureData[ofs];
this->PasteGlyph(glyph->bitmapData, ptr);
}
CGxFont::~CGxFont() {
this->Clear();
}
int32_t CGxFont::CheckStringGlyphs(const char* string) {
if (!string || !*string) {
return 1;
}
while (*string) {
int32_t advance;
auto code = SUniSGetUTF8(reinterpret_cast<const uint8_t*>(string), &advance);
HASHKEY_NONE key = {};
if (code != '\r' && code != '\n' && !this->m_activeCharacters.Ptr(code, key)) {
return 0;
}
string += advance;
}
return 1;
}
void CGxFont::Clear() {
if (this->m_faceHandle) {
FontFaceCloseHandle(this->m_faceHandle);
}
this->m_faceHandle = nullptr;
this->ClearGlyphs();
}
void CGxFont::ClearGlyphs() {
for (int32_t i = 0; i < 8; i++) {
auto& cache = this->m_textureCache[i];
if (cache.m_texture) {
HandleClose(this->m_textureCache[i].m_texture);
}
cache.m_texture = nullptr;
// TODO
}
this->m_activeCharacters.Clear();
this->m_activeCharacterCache.DeleteAll();
this->m_kernInfo.Clear();
}
float CGxFont::ComputeStep(uint32_t currentCode, uint32_t nextCode) {
KERNINGHASHKEY kernKey = { nextCode | (currentCode << 16) };
KERNNODE* kern = this->m_kernInfo.Ptr(currentCode, kernKey);
if (kern && kern->flags & 0x02) {
return kern->proporportionalSpacing;
}
auto face = FontFaceGetFace(this->m_faceHandle);
auto currentIndex = FT_Get_Char_Index(face, currentCode);
auto nextIndex = FT_Get_Char_Index(face, nextCode);
FT_Vector vector;
vector.x = 0;
if (face->face_flags & FT_FACE_FLAG_KERNING) {
FT_Get_Kerning(face, currentIndex, nextIndex, ft_kerning_unscaled, &vector);
vector.x &= (vector.x >= 0) - 1;
}
HASHKEY_NONE charKey = {};
auto activeChar = this->m_activeCharacters.Ptr(currentCode, charKey);
float advance = 0.0f;
if (activeChar) {
advance = this->m_flags & 0x08
? activeChar->bitmapData.m_glyphAdvance + 1.0f
: activeChar->bitmapData.m_glyphAdvance;
}
float spacing = (this->m_pixelsPerUnit * vector.x) + advance;
if (!kern) {
kern = this->m_kernInfo.New(currentCode, kernKey, 0, 0);
}
kern->flags |= 0x02;
kern->proporportionalSpacing = ceil(spacing);
return kern->proporportionalSpacing;
}
float CGxFont::ComputeStepFixedWidth(uint32_t currentCode, uint32_t nextCode) {
// TODO
return 0.0f;
}
float CGxFont::GetGlyphBearing(const CHARCODEDESC* glyph, bool billboarded, float height) {
if (billboarded) {
float v8 = ScreenToPixelHeight(1, height) / this->GetPixelSize();
return glyph->bitmapData.m_glyphBearing * v8;
}
return ceil(glyph->bitmapData.m_glyphBearing);
}
int32_t CGxFont::GetGlyphData(GLYPHBITMAPDATA* glyphData, uint32_t code) {
FT_Face face = FontFaceGetFace(this->m_faceHandle);
FT_Set_Pixel_Sizes(face, this->m_pixelSize, 0);
uint32_t v6 = 0;
if (this->m_flags & 0x8) {
v6 = 4;
} else if (this->m_flags & FONT_OUTLINE) {
v6 = 2;
}
return IGxuFontGlyphRenderGlyph(
face,
this->m_pixelSize,
code,
this->m_baseline,
glyphData,
this->m_flags & FONT_MONOCHROME,
v6
);
}
const char* CGxFont::GetName(void) const {
STORM_ASSERT(this->m_faceHandle);
return FontFaceGetFontName(this->m_faceHandle);
}
uint32_t CGxFont::GetPixelSize() {
return this->m_pixelSize;
}
int32_t CGxFont::Initialize(const char* name, uint32_t newFlags, float fontHeight) {
SStrPrintf(this->m_fontName, 260, "%s", name);
this->m_requestedFontHeight = fontHeight;
this->Clear();
this->m_flags = newFlags;
this->m_faceHandle = FontFaceGetHandle(name, GetFreeTypeLibrary());
if (this->m_faceHandle) {
return this->UpdateDimensions();
} else {
return 0;
}
}
const CHARCODEDESC* CGxFont::NewCodeDesc(uint32_t code) {
HASHKEY_NONE key = {};
CHARCODEDESC* charDesc = this->m_activeCharacters.Ptr(code, key);
if (charDesc) {
this->m_activeCharacterCache.LinkToHead(charDesc);
return charDesc;
}
GLYPHBITMAPDATA data;
if (!CGxFont::GetGlyphData(&data, code)) {
return nullptr;
}
// Attempt to allocate the character off of texture caches
for (uint32_t textureNumber = 0; textureNumber < 8; textureNumber++) {
TEXTURECACHE* textureCache = &this->m_textureCache[textureNumber];
if (textureCache->m_texture && TextureGetGxTex(reinterpret_cast<CTexture*>(textureCache->m_texture), 1, nullptr)) {
charDesc = textureCache->AllocateNewGlyph(&data);
if (charDesc) {
charDesc->textureNumber = textureNumber;
break;
}
} else {
textureCache->CreateTexture(this->m_flags & 0x4);
textureCache->Initialize(this, this->m_cellHeight);
charDesc = textureCache->AllocateNewGlyph(&data);
if (charDesc) {
charDesc->textureNumber = textureNumber;
}
break;
}
}
// No character was allocated from the texture caches, so evict the oldest character and
// attempt to allocate from that character's texture cache row
if (!charDesc) {
CHARCODEDESC* oldestDesc = this->m_activeCharacterCache.Tail();
if (oldestDesc) {
uint32_t textureNumber = oldestDesc->textureNumber;
uint32_t rowNumber = oldestDesc->rowNumber;
TEXTURECACHE* textureCache = &this->m_textureCache[textureNumber];
TEXTURECACHEROW* cacheRow = &textureCache->m_textureRows[rowNumber];
cacheRow->EvictGlyph(oldestDesc);
this->RegisterEvictNotice(textureNumber);
charDesc = cacheRow->CreateNewDesc(&data, rowNumber, this->m_cellHeight);
if (charDesc) {
charDesc->rowNumber = rowNumber;
charDesc->textureNumber = textureNumber;
}
}
}
if (charDesc) {
this->m_activeCharacters.Insert(charDesc, code, key);
this->m_activeCharacterCache.LinkToHead(charDesc);
this->m_textureCache[charDesc->textureNumber].m_anyDirtyGlyphs = 1;
}
return charDesc;
}
void CGxFont::RegisterEvictNotice(uint32_t a2) {
// TODO
}
int32_t CGxFont::UpdateDimensions() {
FT_Face theFace = FontFaceGetFace(this->m_faceHandle);
float v11 = Sub6C2280(theFace, this->m_requestedFontHeight);
float v3 = 2.0 / GetScreenPixelHeight();
if (v11 > v3) {
v3 = v11;
}
float height = v3;
uint32_t pixelSize = ScreenToPixelHeight(0, height);
if (pixelSize <= 32) {
if (!pixelSize) {
// TODO
// nullsub_3();
}
} else {
pixelSize = 32;
}
this->m_pixelSize = pixelSize;
uint32_t v10 = theFace->ascender + abs(theFace->descender);
if (!v10) {
return 0;
}
this->m_cellHeight = pixelSize;
float baseline = (double)(pixelSize * theFace->ascender) / (double)v10;
this->m_baseline = (int64_t)(baseline + 0.5);
uint32_t flags = this->m_flags;
if (flags & 0x8) {
this->m_cellHeight = pixelSize + 4;
} else if (flags & 0x1) {
this->m_cellHeight = pixelSize + 2;
}
int32_t result = FT_Set_Pixel_Sizes(theFace, pixelSize, 0) == FT_Err_Ok;
this->m_pixelsPerUnit = (double)theFace->size->metrics.x_ppem / (double)theFace->units_per_EM;
return result;
}

141
src/gx/font/CGxFont.hpp Normal file
View file

@ -0,0 +1,141 @@
#ifndef GX_C_GX_FONT_HPP
#define GX_C_GX_FONT_HPP
#include "gx/font/CGxString.hpp"
#include "gx/Font.hpp"
#include "gx/Texture.hpp"
#include <cstdint>
#include <storm/Hash.hpp>
#include <storm/List.hpp>
class CTexture;
class GLYPHBITMAPDATA {
public:
// Member variables
void* m_data = nullptr;
uint32_t m_dataSize = 0;
uint32_t m_glyphWidth;
uint32_t m_glyphHeight;
uint32_t m_glyphCellWidth;
float m_glyphAdvance;
float m_glyphBearing;
uint32_t m_glyphPitch;
int32_t m_yOffset;
int32_t m_yStart;
CRect m_textureCoords;
// Member functions
~GLYPHBITMAPDATA();
void CopyFrom(GLYPHBITMAPDATA*);
};
class CHARCODEDESC : public TSHashObject<CHARCODEDESC, HASHKEY_NONE> {
public:
// Member variables
TSLink<CHARCODEDESC> textureRowLink;
TSLink<CHARCODEDESC> fontGlyphLink;
uint32_t textureNumber = -1;
uint32_t rowNumber = -1;
uint32_t glyphStartPixel = -1;
uint32_t glyphEndPixel = 0;
GLYPHBITMAPDATA bitmapData;
// Member functions
uint32_t GapToNextTexture(void);
uint32_t GapToPreviousTexture(void);
void GenerateTextureCoords(uint32_t, uint32_t);
};
class KERNINGHASHKEY {
public:
// Member variables
uint32_t code;
// Member functions
KERNINGHASHKEY& operator=(const KERNINGHASHKEY&);
bool operator==(const KERNINGHASHKEY&);
};
class KERNNODE : public TSHashObject<KERNNODE, KERNINGHASHKEY> {
public:
// Member variables
uint32_t flags = 0x0;
float proporportionalSpacing = 0.0f;
float fixedWidthSpacing = 0.0f;
};
class TEXTURECACHEROW {
public:
// Member variables
uint32_t widestFreeSlot = 0;
STORM_EXPLICIT_LIST(CHARCODEDESC, textureRowLink) glyphList;
// Member functions
CHARCODEDESC* CreateNewDesc(GLYPHBITMAPDATA*, uint32_t, uint32_t);
void EvictGlyph(CHARCODEDESC*);
};
class TEXTURECACHE {
public:
// Static variables
static uint16_t s_textureData[256 * 256];
// Static functions
static void TextureCallback(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&);
// Member variables
HTEXTURE m_texture = nullptr;
CGxFont* m_theFace = nullptr;
int8_t m_anyDirtyGlyphs = 0;
int8_t pad[3];
TSFixedArray<TEXTURECACHEROW> m_textureRows;
// Member functions
CHARCODEDESC* AllocateNewGlyph(GLYPHBITMAPDATA*);
void CreateTexture(int32_t);
void Initialize(CGxFont*, uint32_t);
void PasteGlyph(const GLYPHBITMAPDATA&, uint16_t*);
void PasteGlyphNonOutlinedAA(const GLYPHBITMAPDATA&, uint16_t*);
void PasteGlyphNonOutlinedMonochrome(const GLYPHBITMAPDATA&, uint16_t*);
void PasteGlyphOutlinedAA(const GLYPHBITMAPDATA&, uint16_t*);
void PasteGlyphOutlinedMonochrome(const GLYPHBITMAPDATA&, uint16_t*);
void UpdateDirty(void);
void WriteGlyphToTexture(CHARCODEDESC*);
};
class CGxFont : public TSLinkedNode<CGxFont> {
public:
// Member variables
STORM_EXPLICIT_LIST(CGxString, m_fontStringLink) m_strings;
TSHashTable<CHARCODEDESC, HASHKEY_NONE> m_activeCharacters;
TSHashTable<KERNNODE, KERNINGHASHKEY> m_kernInfo;
STORM_EXPLICIT_LIST(CHARCODEDESC, fontGlyphLink) m_activeCharacterCache;
HFACE m_faceHandle;
char m_fontName[260];
uint32_t m_cellHeight = 0;
uint32_t m_baseline;
uint32_t m_flags;
float m_requestedFontHeight;
float m_pixelsPerUnit = 0.0f;
TEXTURECACHE m_textureCache[8];
uint32_t m_pixelSize;
// Member functions
~CGxFont();
int32_t CheckStringGlyphs(const char*);
void Clear(void);
void ClearGlyphs(void);
float ComputeStep(uint32_t, uint32_t);
float ComputeStepFixedWidth(uint32_t, uint32_t);
float GetGlyphBearing(const CHARCODEDESC*, bool, float);
int32_t GetGlyphData(GLYPHBITMAPDATA*, uint32_t);
const char* GetName(void) const;
uint32_t GetPixelSize(void);
int32_t Initialize(const char*, uint32_t, float);
const CHARCODEDESC* NewCodeDesc(uint32_t);
void RegisterEvictNotice(uint32_t);
int32_t UpdateDimensions(void);
};
#endif

638
src/gx/font/CGxString.cpp Normal file
View file

@ -0,0 +1,638 @@
#include "gx/font/CGxString.hpp"
#include "gx/Buffer.hpp"
#include "gx/font/CGxFont.hpp"
#include "gx/font/Wrap.hpp"
#include "gx/Font.hpp"
#include "gx/Gx.hpp"
#include <algorithm>
#include <cmath>
#include <new>
#include <storm/Memory.hpp>
#include <storm/String.hpp>
#include <tempest/Math.hpp>
TEXTLINETEXTURE* TEXTLINETEXTURE::NewTextLineTexture() {
// TODO
// Allocate off of TEXTLINETEXTURE::s_freeTextLineTextures
void* m = SMemAlloc(sizeof(TEXTLINETEXTURE), __FILE__, __LINE__, 0x0);
return new (m) TEXTLINETEXTURE();
}
void TEXTLINETEXTURE::Recycle(TEXTLINETEXTURE* ptr) {
// TODO if (TEXTLINETEXTURE::s_recycledBytes <= 0x80000)
if (ptr) {
delete ptr;
}
}
void TEXTLINETEXTURE::WriteGeometry(CGxVertexPCT* buf, const CImVector& fontColor, const C2Vector& shadowOffset, const CImVector& shadowColor, const C3Vector& viewTranslation, bool a7, bool a8, int32_t ofs, int32_t size) {
if (!size || !this->m_vert.Count()) {
return;
}
uint32_t colorCount = this->m_vert.Count() == this->m_colors.Count()
? this->m_colors.Count()
: 0;
if (ofs >= this->m_vert.Count()) {
return;
}
uint32_t v24 = this->m_vert.Count() - ofs;
if (size >= v24) {
size = v24;
}
if (a7) {
C3Vector shadowTranslation = {
viewTranslation.x + floor(ScreenToPixelWidth(0, shadowOffset.x)),
viewTranslation.y + floor(ScreenToPixelHeight(0, shadowOffset.y)),
viewTranslation.z
};
auto color = colorCount ? this->m_colors[ofs] : fontColor;
for (int32_t i = 0; i < size; i++) {
auto& vert = this->m_vert[i + ofs];
C3Vector p = {
vert.vc.x + shadowTranslation.x,
vert.vc.y + shadowTranslation.y,
vert.vc.z + shadowTranslation.z
};
buf->p = p;
buf->tc[0] = vert.tc;
auto formattedShadowColor = shadowColor;
if (a8 && colorCount) {
formattedShadowColor.a = static_cast<uint8_t>(
(static_cast<float>(formattedShadowColor.a) * static_cast<float>(color.a)) / 65536.0f
);
}
GxFormatColor(formattedShadowColor);
buf->c = formattedShadowColor;
buf++;
}
}
// if (BATCHEDRENDERFONTDESC::s_billboarded) {
// // TODO
// }
for (int32_t i = 0; i < size; i++) {
auto& vert = this->m_vert[i + ofs];
auto color = colorCount ? this->m_colors[i + ofs] : fontColor;
GxFormatColor(color);
// if (BATCHEDRENDERFONTDESC::s_billboarded) {
// // TODO
// continue;
// }
C3Vector p = {
vert.vc.x + viewTranslation.x,
vert.vc.y + viewTranslation.y,
vert.vc.z + viewTranslation.z
};
buf->p = p;
buf->tc[0] = vert.tc;
buf->c = color;
buf++;
}
}
CGxString* CGxString::GetNewString(int32_t linkOnList) {
CGxString* string = g_freeStrings.Head();
if (string) {
g_strings.LinkToTail(string);
return string;
}
void* m = SMemAlloc(sizeof(CGxString), __FILE__, __LINE__, 0x8);
string = new (m) CGxString();
if (linkOnList) {
g_strings.LinkToTail(string);
}
return string;
}
CGxString::~CGxString() {
// TODO
}
void CGxString::AddShadow(const C2Vector& offset, const CImVector& color) {
this->m_shadowColor = color;
this->m_shadowColor.a = std::min(this->m_shadowColor.a, this->m_fontColor.a);
this->m_shadowOffset = offset;
this->m_flags |= 0x1;
if (this->m_flags & 0x20) {
this->ClearInstanceData();
}
}
uint32_t CGxString::CalculateVertsNeeded(int32_t line) {
if (this->m_flags & 0x01) {
return 2 * this->m_textLines[line]->m_vert.Count();
}
return this->m_textLines[line]->m_vert.Count();
}
bool CGxString::CheckGeometry() {
if (this->m_textureEvicted) {
if (!this->m_currentFace->CheckStringGlyphs(this->m_text)) {
this->ClearInstanceData();
}
this->m_textureEvicted = 0;
}
this->CreateGeometry();
this->m_intD4 = 0;
return this->m_intB0 != 0;
}
void CGxString::ClearInstanceData() {
// TODO this->m_hyperlinkInfo->SetCount(0);
// TODO this->m_textureInfo->SetCount(0);
// TODO this->dwordA4 = 0;
this->m_lastGradientStart = -1;
this->m_lastGradientLength = -1;
this->m_intB0 = 0;
for (int32_t i = 0; i < 8; i++) {
if (this->m_textLines[i]) {
TEXTLINETEXTURE::Recycle(this->m_textLines[i]);
this->m_textLines[i] = nullptr;
}
}
}
void CGxString::CreateGeometry() {
if (this->m_intB0 || !this->m_text || !*this->m_text) {
return;
}
EMBEDDEDPARSEINFO info;
uint32_t flags = this->m_flags;
uint32_t numBytes = 0;
const char* currentText = this->m_text;
const char* nextText = nullptr;
int32_t advance = 0;
uint32_t wide = 0;
int32_t gStart = this->m_lastGradientStart;
int32_t gLength = this->m_lastGradientLength;
CImVector lineColor = this->m_fontColor;
float height = -this->m_currentFontHeight;
C3Vector linePos = {
0.0f,
this->m_flags & 0x80 ? 0.0f : ScreenToPixelHeight(0, -this->m_currentFontHeight),
0.0f
};
bool a10 = true;
float blockWidth = this->m_blockWidth;
auto v611 = static_cast<float>(CMath::fuint_pi(GetScreenPixelHeight() * this->m_spacing));
auto v51 = ScreenToPixelHeight(0, this->m_currentFontHeight);
auto spacing = this->m_flags & 0x80 ? this->m_spacing : v611 / static_cast<float>(GetScreenPixelHeight());
auto v59 = this->m_flags & 0x80 ? this->m_currentFontHeight + this->m_spacing : v611 + v51;
float v49 = 0.0f;
while (*currentText) {
if (this->m_blockHeight <= v49) {
break;
}
if (this->m_flags & 0x2000 && this->m_intB0 == 1) {
blockWidth -= GetIndentNormWidth();
}
v49 += this->m_currentFontHeight + spacing;
QUOTEDCODE code = GxuDetermineQuotedCode(currentText, advance, 0, flags, wide);
if (code == CODE_NEWLINE) {
currentText += advance;
this->m_intB0++;
linePos.y -= v59;
// TODO
// info.dword0C -= v59;
// info.dword04 -= v59;
// info.dword34 -= v59;
// info.dword2C -= v59;
continue;
}
float extent = 0.0f;
float a8 = 0.0f;
float a11 = 0.0f;
if (this->m_flags & 0x1) {
a8 = this->m_shadowOffset.x;
}
CalcWrapPoint(
this->m_currentFace,
currentText,
this->m_currentFontHeight,
blockWidth,
&numBytes,
&extent,
&nextText,
a8,
this->m_flags,
&a10,
&a11,
this->m_scale
);
if (nextText == currentText || !nextText || (!numBytes && !*nextText)) {
break;
}
if (this->m_horzJust == GxHJ_Right) {
float indent = this->m_flags & 0x2000 && this->m_intB0 ? GetIndentPixelWidth() : 0.0f;
linePos.x = ScreenToPixelWidth(this->m_flags & 0x80, -extent) - indent;
} else if (this->m_horzJust == GxHJ_Center) {
linePos.x = ScreenToPixelWidth(this->m_flags & 0x80, -(extent * 0.5f));
} else if (this->m_flags & 0x2000 && this->m_intB0) {
linePos.x = GetIndentPixelWidth();
} else {
linePos.x = 0.0f;
}
float offsetY = 0.0f;
if (v51 < a11) {
if (this->m_vertJust == GxVJ_Bottom) {
offsetY = a11 - v51;
} else if (this->m_vertJust == GxVJ_Middle) {
offsetY = floor((a11 - v51) * 0.5);
}
}
linePos.y -= offsetY;
// TODO
// info.dword0C -= v62;
// info.dword04 -= v62;
// info.dword34 = linePos.y;
// info.dword2C = linePos.y + v59;
// if (info.dword00) {
// info.dword08 = linePos.x;
// }
uint32_t texturePagesUsedFlag = 0;
this->InitializeTextLine(currentText, numBytes, lineColor, linePos, &texturePagesUsedFlag, info);
this->m_intB0++;
this->m_texturePagesUsed |= texturePagesUsedFlag;
currentText = nextText;
linePos.y -= offsetY + v59;
if (this->m_flags & 0x2) {
break;
}
// TODO
// info.dword0C -= offsetY + v59;
// info.dword04 -= offsetY + v59;
// info.dword34 = linePos.y;
};
this->InitializeViewTranslation();
if (this->m_flags & 0x20 && (gStart != -1 || gLength != -1)) {
this->SetGradient(gStart, gLength);
}
}
int32_t CGxString::Initialize(float fontHeight, const C3Vector& position, float blockWidth, float blockHeight, CGxFont* face, const char* text, EGxFontVJusts vertJust, EGxFontHJusts horzJust, float spacing, uint32_t flags, const CImVector& color, float scale) {
uint32_t textLen = SStrLen(text) + 1;
if (textLen > this->m_textLen) {
if (this->m_text) {
SMemFree(this->m_text, __FILE__, __LINE__, 0x0);
}
this->m_textLen = textLen;
this->m_text = static_cast<char*>(SMemAlloc(textLen, __FILE__, __LINE__, 0x0));
}
SStrCopy(this->m_text, text, this->m_textLen);
this->m_blockWidth = blockWidth;
this->m_blockHeight = blockHeight;
this->m_spacing = spacing;
this->m_position = position;
this->m_horzJust = horzJust;
this->m_vertJust = vertJust;
this->m_flags = flags;
this->m_fontColor = color;
this->m_scale = scale;
this->m_currentFace = face;
face->m_strings.LinkToTail(this);
float requestedFontHeight = this->m_flags & 0x4 && !(this->m_flags & 0x80)
? GxuFontGetOneToOneHeight(face)
: fontHeight;
this->m_requestedFontHeight = requestedFontHeight;
this->m_currentFontHeight = std::max(this->m_requestedFontHeight, 2.0f / GetScreenPixelHeight());
return 1;
}
void CGxString::InitializeTextLine(const char* currentText, uint32_t numBytes, CImVector& workingColor, const C3Vector& position, uint32_t* texturePagesUsedFlag, EMBEDDEDPARSEINFO& info) {
if (this->m_flags & 0x08) {
// TODO
}
C3Vector curPos = position;
float screenPixelHeight = ScreenToPixelHeight(this->m_flags & 0x80, this->m_currentFontHeight);
float glyphToScreenPixels = screenPixelHeight / this->m_currentFace->GetPixelSize();
float glyphPixelHeight = ScreenToPixelHeight(this->m_flags & 0x80, this->m_currentFontHeight);
if (this->m_currentFace->m_flags & 0x08) {
glyphPixelHeight += 4.0f;
} else if (this->m_currentFace->m_flags & 0x01) {
glyphPixelHeight += 2.0f;
}
// TODO
// - billboard adjustment table init
// - EMBEDDEDPARSEINFO update
float stepGlyph = 0.0f;
float stepScreen = 0.0f;
uint32_t prevCode = 0;
CImVector color;
while (numBytes && *currentText) {
int32_t advance;
uint32_t code;
CImVector quotedColor;
QUOTEDCODE quotedCode = GxuDetermineQuotedCode(currentText, advance, &quotedColor, this->m_flags, code);
currentText += advance;
numBytes -= advance;
if (prevCode) {
if (this->m_flags & 0x10) {
// TODO
// stepGlyph = this->m_currentFace->ComputeStepFixedWidth(prevCode, code);
} else {
stepGlyph = this->m_currentFace->ComputeStep(prevCode, code);
}
}
stepScreen = stepGlyph * glyphToScreenPixels;
switch (quotedCode) {
case CODE_COLORON:
if (!(this->m_flags & 0x08)) {
color = quotedColor;
color.a = this->m_fontColor.a;
}
// TODO
// EMBEDDEDPARSEINFO update
continue;
case CODE_COLORRESTORE:
color = this->m_fontColor;
continue;
case CODE_NEWLINE:
continue;
case CODE_HYPERLINKSTART:
// TODO
continue;
case CODE_HYPERLINKSTOP:
// TODO
continue;
case CODE_TEXTURESTART:
// TODO
continue;
case CODE_TEXTURESTOP:
continue;
default: {
auto glyph = this->m_currentFace->NewCodeDesc(code);
if (!glyph) {
glyph = this->m_currentFace->NewCodeDesc('?');
}
if (!glyph) {
continue;
}
*texturePagesUsedFlag |= 1 << glyph->textureNumber;
if (!this->m_textLines[glyph->textureNumber]) {
this->m_textLines[glyph->textureNumber] = TEXTLINETEXTURE::NewTextLineTexture();
}
auto line = this->m_textLines[glyph->textureNumber];
if (!(this->m_flags & 0x08)) {
uint32_t index = line->m_colors.Add(4, 0, &color);
if (this->m_flags & 0x20) {
// TODO
}
}
if (!(this->m_flags & 0x80)) {
stepScreen = floor(stepScreen + 0.5f);
}
curPos.x += stepScreen;
VERT vert;
vert.vc = { curPos.x, curPos.y, curPos.z };
vert.tc = { 0.0f, 0.0f };
vert.vc.x += this->m_currentFace->GetGlyphBearing(
glyph,
this->m_flags & 0x80,
this->m_currentFontHeight
);
if (this->m_currentFace->m_flags & 0x08) {
vert.vc.y -= 2.0f;
} else if (this->m_currentFace->m_flags & 0x01) {
vert.vc.y -= 1.0f;
}
vert.vc.y += (glyph->bitmapData.m_yOffset * glyphToScreenPixels);
uint32_t index = line->m_vert.Add(4, 0, &vert);
VERT* verts = &line->m_vert[index];
float width = this->m_flags & 0x80
? glyph->bitmapData.m_glyphCellWidth * glyphToScreenPixels
: floor(glyph->bitmapData.m_glyphCellWidth * glyphToScreenPixels);
float height = this->m_flags & 0x80
? screenPixelHeight
: glyphPixelHeight;
verts[0].vc.x = verts[1].vc.x;
verts[3].vc.x = verts[3].vc.x + width;
verts[2].vc.x = verts[3].vc.x;
verts[3].vc.y = verts[3].vc.y + height;
verts[1].vc.y = verts[3].vc.y;
verts[0].vc.y = verts[2].vc.y;
verts[2].tc.y = glyph->bitmapData.m_textureCoords.maxY;
verts[0].tc.y = verts[2].tc.y;
verts[3].tc.y = glyph->bitmapData.m_textureCoords.minY;
verts[1].tc.y = verts[3].tc.y;
verts[1].tc.x = glyph->bitmapData.m_textureCoords.minX;
verts[0].tc.x = verts[1].tc.x;
verts[3].tc.x = glyph->bitmapData.m_textureCoords.maxX;
verts[2].tc.x = verts[3].tc.x;
if (this->m_flags & 0x80) {
// TODO
// verts[3].tc.y += flt_C7D2E8;
// verts[1].tc.y += flt_C7D2E8;
// verts[1].tc.x += flt_C7D2EC;
// verts[0].tc.x += flt_C7D2EC;
// verts[2].tc.y += flt_C7D2F0;
// verts[0].tc.y += flt_C7D2F0;
// verts[3].tc.x += flt_C7D2F4;
// verts[2].tc.x += flt_C7D2F4;
}
prevCode = code;
}
}
}
// TODO
// info.dword10 = curPos.x + stepScreen;
}
void CGxString::InitializeViewTranslation() {
this->m_viewTranslation = this->m_position;
if (!this->m_intB0 || this->m_flags & 0x80) {
return;
}
if (this->m_horzJust == GxHJ_Right) {
this->m_viewTranslation.x += this->m_blockWidth;
} else if (this->m_horzJust == GxHJ_Center) {
this->m_viewTranslation.x += (this->m_blockWidth * 0.5f);
}
float v13 = ((this->m_spacing * (float)GetScreenPixelHeight()) + 0.9999499917030334f) / (float)GetScreenPixelHeight();
float v24 = v13 * (float)(this->m_intB0 - 1) + (float)this->m_intB0 * this->m_currentFontHeight;
if (this->m_intD4 >= 4) {
// TODO
}
// TODO
// - current font height logic
if (this->m_vertJust == GxVJ_Bottom) {
this->m_viewTranslation.y += v24;
} else if (this->m_vertJust == GxVJ_Middle) {
this->m_viewTranslation.y += 0.5f * (this->m_blockHeight - v24) + v24;
} else {
this->m_viewTranslation.y += this->m_blockHeight;
}
this->m_viewTranslation.x = floor((float)GetScreenPixelWidth() * this->m_viewTranslation.x);
this->m_viewTranslation.y = floor((float)GetScreenPixelHeight() * this->m_viewTranslation.y);
}
void CGxString::Recycle() {
this->m_batchedStringLink.Unlink();
g_freeStrings.LinkToTail(this);
this->ClearInstanceData();
}
void CGxString::SetColor(const CImVector& color) {
if (this->m_fontColor == color) {
return;
}
this->m_fontColor = color;
this->m_shadowColor.a = std::min(color.a, this->m_shadowColor.a);
if (!(this->m_flags & 0x8) || this->m_flags & 0x20 || this->m_intD4) {
this->ClearInstanceData();
}
}
int32_t CGxString::SetGradient(int32_t startCharacter, int32_t length) {
// TODO
}
void CGxString::SetStringPosition(const C3Vector& position) {
this->m_position = position;
this->InitializeViewTranslation();
}
void CGxString::Tick() {
if (this->m_intB0) {
this->m_intD4++;
if (this->m_intD4 - 1 > 30) {
this->ClearInstanceData();
}
}
}
void CGxString::WriteGeometry(CGxVertexPCT* buf, int32_t line, int32_t ofs, int32_t size) {
auto textLine = this->m_textLines[line];
if (textLine) {
textLine->WriteGeometry(
buf,
this->m_fontColor,
this->m_shadowOffset,
this->m_shadowColor,
this->m_viewTranslation,
this->m_flags & 0x01,
this->m_flags & 0x20,
ofs,
size
);
}
}

84
src/gx/font/CGxString.hpp Normal file
View file

@ -0,0 +1,84 @@
#ifndef GX_C_GX_STRING_HPP
#define GX_C_GX_STRING_HPP
#include "gx/Types.hpp"
#include <cstdint>
#include <storm/Array.hpp>
#include <storm/List.hpp>
#include <tempest/Vector.hpp>
struct EMBEDDEDPARSEINFO;
class CGxFont;
class CGxVertexPCT;
struct VERT {
C3Vector vc;
C2Vector tc;
};
class TEXTLINETEXTURE {
public:
// Static functions
static TEXTLINETEXTURE* NewTextLineTexture(void);
static void Recycle(TEXTLINETEXTURE* ptr);
// Member variables
TSGrowableArray<VERT> m_vert;
TSGrowableArray<CImVector> m_colors;
// Member functions
void WriteGeometry(CGxVertexPCT*, const CImVector&, const C2Vector&, const CImVector&, const C3Vector&, bool, bool, int32_t, int32_t);
};
class CGxString : public TSLinkedNode<CGxString> {
public:
// Static functions
static CGxString* GetNewString(int32_t linkOnList);
// Member variables
TSLink<CGxString> m_fontStringLink;
TSLink<CGxString> m_batchedStringLink;
float m_requestedFontHeight = 0.02f;
float m_currentFontHeight = 0.02f;
C3Vector m_position;
CImVector m_fontColor = { 0xFF, 0xFF, 0xFF, 0xFF };
CImVector m_shadowColor = { 0x00, 0x00, 0x00, 0xFF };
C2Vector m_shadowOffset = { 0.00125f, -0.00125f };
float m_blockWidth = 1.0f;
float m_blockHeight = 1.0f;
CGxFont* m_currentFace = nullptr;
char* m_text = nullptr;
int32_t m_textLen = 0;
EGxFontVJusts m_vertJust = GxVJ_Top;
EGxFontHJusts m_horzJust = GxHJ_Left;
float m_spacing = 0.0f;
uint32_t m_flags = 0;
uint32_t m_texturePagesUsed = 0;
int32_t m_textureEvicted = 0;
int32_t m_lastGradientStart = -1;
int32_t m_lastGradientLength = -1;
C3Vector m_viewTranslation;
float m_scale = 1.0f;
int32_t m_intB0 = 0;
TEXTLINETEXTURE* m_textLines[8] = {};
int32_t m_intD4 = 0;
// Member functions
~CGxString();
void AddShadow(const C2Vector& offset, const CImVector& color);
uint32_t CalculateVertsNeeded(int32_t);
bool CheckGeometry(void);
void ClearInstanceData(void);
void CreateGeometry(void);
int32_t Initialize(float, const C3Vector&, float, float, CGxFont*, const char*, EGxFontVJusts, EGxFontHJusts, float, uint32_t, const CImVector&, float);
void InitializeTextLine(const char*, uint32_t, CImVector&, const C3Vector&, uint32_t*, EMBEDDEDPARSEINFO&);
void InitializeViewTranslation(void);
void Recycle();
void SetColor(const CImVector&);
int32_t SetGradient(int32_t, int32_t);
void SetStringPosition(const C3Vector& position);
void Tick();
void WriteGeometry(CGxVertexPCT*, int32_t, int32_t, int32_t);
};
#endif

View file

@ -0,0 +1,289 @@
#include "gx/font/CGxStringBatch.hpp"
#include "gx/font/CGxFont.hpp"
#include "gx/Buffer.hpp"
#include "gx/CGxBatch.hpp"
#include "gx/Device.hpp"
#include "gx/Draw.hpp"
#include "gx/Font.hpp"
#include "gx/Gx.hpp"
#include "gx/RenderState.hpp"
#include "gx/Shader.hpp"
#include "gx/Texture.hpp"
#include "gx/Transform.hpp"
#include <cmath>
#include <storm/Error.hpp>
bool BATCHEDRENDERFONTDESC::s_billboarded;
CGxBuf* BATCHEDRENDERFONTDESC::s_indexBuf;
CGxPool* BATCHEDRENDERFONTDESC::s_indexPool;
int32_t SetProjection() {
float minX, maxX, minY, maxY, minZ, maxZ;
GxXformViewport(minX, maxX, minY, maxY, minZ, maxZ);
float v14 = 0.0f;
float v15 = 0.0f;
if (!GxCaps()->m_pixelCenterOnEdge) {
v14 = -0.5f;
v15 = 0.5f;
}
C44Matrix proj;
uint32_t pixelWidth = GetScreenPixelWidth();
float pixelMinX = floor(minX * pixelWidth);
float pixelMaxX = floor(maxX * pixelWidth);
if (pixelMinX >= pixelMaxX) {
return 0;
}
uint32_t pixelHeight = GetScreenPixelHeight();
float pixelMinY = floor(minY * pixelHeight);
float pixelMaxY = floor(maxY * pixelHeight);
if (pixelMinY >= pixelMaxY) {
return 0;
}
if (pixelHeight > pixelMaxY) {
// TODO
}
pixelMinX += v14;
pixelMaxX += v14;
pixelMinY += v15;
pixelMaxY += v15;
GxuXformCreateOrthoDepth(pixelMinX, pixelMaxX, pixelMinY, pixelMaxY, -5000.0f, 5000.0f, proj);
GxXformSetProjection(proj);
return 1;
}
void BATCHEDRENDERFONTDESC::Initialize() {
CGxPool* indexPool = GxPoolCreate(
GxPoolTarget_Index,
GxPoolUsage_Static,
6144,
GxPoolHintBit_Unk0,
"BATCHEDRENDERFONTDESC_idx"
);
CGxBuf* indexBuf = GxBufCreate(
indexPool,
2,
3072,
0
);
BATCHEDRENDERFONTDESC::s_indexPool = indexPool;
BATCHEDRENDERFONTDESC::s_indexBuf = indexBuf;
}
void BATCHEDRENDERFONTDESC::InitializeIndexBuff() {
char* indexData = g_theGxDevicePtr->BufLock(BATCHEDRENDERFONTDESC::s_indexBuf);
uint16_t* indexBuf = reinterpret_cast<uint16_t*>(indexData);
uint16_t index = 0;
for (int32_t i = 0; i < 512; i++) {
indexBuf[i * 6 + 0] = index + 0;
indexBuf[i * 6 + 1] = index + 2;
indexBuf[i * 6 + 2] = index + 1;
indexBuf[i * 6 + 3] = index + 2;
indexBuf[i * 6 + 4] = index + 3;
indexBuf[i * 6 + 5] = index + 1;
index += 4;
}
GxBufUnlock(BATCHEDRENDERFONTDESC::s_indexBuf, 0);
};
CGxVertexPCT* BATCHEDRENDERFONTDESC::UnlockVertexPtrAndRender(CGxBuf*& buf, int32_t count) {
GxBufUnlock(buf, sizeof(CGxVertexPCT) * count);
if (!BATCHEDRENDERFONTDESC::s_indexBuf->unk1C || !BATCHEDRENDERFONTDESC::s_indexBuf->unk1D) {
BATCHEDRENDERFONTDESC::InitializeIndexBuff();
}
if (BATCHEDRENDERFONTDESC::s_indexBuf->unk1C && BATCHEDRENDERFONTDESC::s_indexBuf->unk1D) {
GxPrimVertexPtr(buf, GxVBF_PCT);
GxPrimIndexPtr(BATCHEDRENDERFONTDESC::s_indexBuf);
CGxBatch batch;
batch.m_primType = GxPrim_Triangles;
batch.m_start = 0;
batch.m_count = 6 * (count / 4);
batch.m_minIndex = 0;
batch.m_maxIndex = count - 1;
GxDraw(&batch, 1);
}
return reinterpret_cast<CGxVertexPCT*>(g_theGxDevicePtr->BufLock(buf));
}
void BATCHEDRENDERFONTDESC::RenderBatch() {
if (!BATCHEDRENDERFONTDESC::s_indexPool) {
return;
}
for (auto string = this->m_strings.Head(); string; string = this->m_strings.Next(string)) {
string->CheckGeometry();
}
for (int32_t i = 0; i < 8; i++) {
this->m_face->m_textureCache[i].UpdateDirty();
}
int32_t maxBatchCapacity = 2048;
CGxBuf* vertexStream = g_theGxDevicePtr->BufStream(GxPoolTarget_Vertex, 0x18, maxBatchCapacity);
char* vertexData = g_theGxDevicePtr->BufLock(vertexStream);
CGxVertexPCT* vertexBuf = reinterpret_cast<CGxVertexPCT*>(vertexData);
for (int32_t i = 0; i < 8; i++) {
auto& textureCache = this->m_face->m_textureCache[i];
auto texture = textureCache.m_texture;
if (texture) {
auto gxTex = TextureGetGxTex(reinterpret_cast<CTexture*>(texture), 1, nullptr);
if (gxTex) {
GxRsSet(GxRs_Texture0, gxTex);
for (auto string = this->m_strings.Head(); string; string = this->m_strings.Next(string)) {
auto line = string->m_textLines[i];
if (line) {
int32_t vertsNeeded = string->CalculateVertsNeeded(i);
int32_t batchOffset = 0;
int32_t batchCapacity = maxBatchCapacity;
while (vertsNeeded) {
int32_t batchCount = std::min(vertsNeeded, batchCapacity);
string->WriteGeometry(vertexBuf, i, batchOffset, batchCount);
vertsNeeded -= batchCount;
batchOffset += batchCount;
batchCapacity -= batchCount;
vertexBuf += batchCount;
if (!batchCapacity) {
vertexBuf = this->UnlockVertexPtrAndRender(vertexStream, maxBatchCapacity);
batchCapacity = maxBatchCapacity;
}
}
if (batchCapacity != maxBatchCapacity) {
vertexBuf = this->UnlockVertexPtrAndRender(vertexStream, maxBatchCapacity - batchCapacity);
batchCapacity = maxBatchCapacity;
}
}
}
}
}
}
g_theGxDevicePtr->BufUnlock(vertexStream, 0);
}
CGxStringBatch::~CGxStringBatch() {
this->m_fontBatch.Clear();
}
void CGxStringBatch::AddString(CGxString* string) {
STORM_ASSERT(string);
STORM_ASSERT(string->m_currentFace);
auto face = string->m_currentFace;
uint32_t hashval = reinterpret_cast<uintptr_t>(face);
HASHKEY_PTR key = { face };
auto batch = this->m_fontBatch.Ptr(hashval, key);
if (!batch) {
batch = this->m_fontBatch.New(hashval, key, 0, 0);
batch->m_face = face;
}
batch->m_strings.LinkToTail(string);
}
void CGxStringBatch::RenderBatch() {
// TODO
// if (!g_perf) {
// return;
// }
C44Matrix oldProjection;
C44Matrix oldView;
GxXformProjection(oldProjection);
GxXformView(oldView);
GxRsPush();
GxRsSet(GxRs_Fog, 0);
GxRsSet(GxRs_Culling, 0);
GxRsSet(GxRs_BlendingMode, GxBlend_Alpha);
GxRsSetAlphaRef();
int32_t setProjection;
int32_t stereoEnabled;
if (this->m_flags & 0x1) {
BATCHEDRENDERFONTDESC::s_billboarded = true;
GxRsSet(GxRs_DepthTest, 1);
GxRsSet(GxRs_DepthWrite, 1);
GxRsSet(GxRs_AlphaRef, 1);
setProjection = 1;
stereoEnabled = 0;
} else {
C44Matrix view;
GxXformSetView(view);
GxRsSet(GxRs_DepthTest, 0);
GxRsSet(GxRs_DepthWrite, 0);
setProjection = SetProjection();
stereoEnabled = g_theGxDevicePtr->StereoEnabled();
}
CGxShader* vs = g_fontVertexShader[stereoEnabled ? 1 : 0];
CGxShader* ps = g_fontPixelShader[0];
if (setProjection && vs->Valid() && ps->Valid()) {
GxRsSet(GxRs_VertexShader, vs);
GxRsSet(GxRs_PixelShader, ps);
C44Matrix viewProjMat;
GxXformViewProjNativeTranspose(viewProjMat);
GxShaderConstantsSet(GxSh_Vertex, 0, reinterpret_cast<float*>(&viewProjMat), 4);
for (auto fontBatch = this->m_fontBatch.Head(); fontBatch; fontBatch = this->m_fontBatch.Next(fontBatch)) {
if (fontBatch->m_strings.Head()) {
fontBatch->RenderBatch();
if (this->m_flags & 0x2) {
// TODO
}
} else {
this->m_fontBatch.Unlink(fontBatch);
}
}
}
BATCHEDRENDERFONTDESC::s_billboarded = false;
GxRsPop();
GxXformSetView(oldView);
GxXformSetProjection(oldProjection);
}

View file

@ -0,0 +1,45 @@
#ifndef GX_C_GX_STRING_BATCH_HPP
#define GX_C_GX_STRING_BATCH_HPP
#include "gx/font/CGxString.hpp"
#include <cstdint>
#include <storm/Hash.hpp>
#include <storm/List.hpp>
class CGxBuf;
class CGxFont;
class CGxPool;
class BATCHEDRENDERFONTDESC : public TSHashObject<BATCHEDRENDERFONTDESC, HASHKEY_PTR> {
public:
// Static variables
static bool s_billboarded;
static CGxBuf* s_indexBuf;
static CGxPool* s_indexPool;
// Static functions
static void Initialize(void);
static void InitializeIndexBuff(void);
CGxVertexPCT* UnlockVertexPtrAndRender(CGxBuf*&, int32_t);
// Member variables
CGxFont* m_face = nullptr;
STORM_EXPLICIT_LIST(CGxString, m_batchedStringLink) m_strings;
// Member functions
void RenderBatch(void);
};
class CGxStringBatch : public TSLinkedNode<CGxStringBatch> {
public:
// Member variables
TSHashTable<BATCHEDRENDERFONTDESC, HASHKEY_PTR> m_fontBatch;
uint32_t m_flags = 0x0;
// Member functions
~CGxStringBatch();
void AddString(CGxString*);
void RenderBatch(void);
};
#endif

12
src/gx/font/FaceData.cpp Normal file
View file

@ -0,0 +1,12 @@
#include "gx/font/FaceData.hpp"
#include "util/SFile.hpp"
FACEDATA::~FACEDATA() {
if (this->face) {
FT_Done_Face(this->face);
}
if (this->data) {
SFile::Unload(this->data);
}
}

17
src/gx/font/FaceData.hpp Normal file
View file

@ -0,0 +1,17 @@
#ifndef GX_FONT_FACE_DATA_HPP
#define GX_FONT_FACE_DATA_HPP
#include "gx/font/FreeType.hpp"
#include "gx/font/Types.hpp"
#include <common/Handle.hpp>
#include <storm/Hash.hpp>
class FACEDATA : public CHandleObject, public TSHashObject<FACEDATA, HASHKEY_STRI> {
public:
void* data = nullptr;
FT_Face face = nullptr;
HFACE selfReference;
virtual ~FACEDATA();
};
#endif

80
src/gx/font/FontFace.cpp Normal file
View file

@ -0,0 +1,80 @@
#include "gx/font/FontFace.hpp"
#include "gx/font/FaceData.hpp"
#include "util/SFile.hpp"
#include <storm/Error.hpp>
#include <storm/Hash.hpp>
TSHashTable<FACEDATA, HASHKEY_STRI> s_faceHash;
void FontFaceCloseHandle(HFACE handle) {
STORM_ASSERT(handle);
HandleClose(handle);
FACEDATA* dataPtr = reinterpret_cast<FACEDATA*>(handle);
if (dataPtr->m_refcount <= 1) {
HandleClose(dataPtr->selfReference);
dataPtr->selfReference = nullptr;
}
}
FT_Face FontFaceGetFace(HFACE handle) {
STORM_ASSERT(handle);
return reinterpret_cast<FACEDATA*>(handle)->face;
}
const char* FontFaceGetFontName(HFACE handle) {
STORM_ASSERT(handle);
return reinterpret_cast<FACEDATA*>(handle)->m_key.m_str;
}
HFACE FontFaceGetHandle(const char* fileName, FT_Library library) {
if (!library || !fileName || !*fileName) {
return nullptr;
}
if (auto existing = s_faceHash.Ptr(fileName)) {
return HandleDuplicate(existing->selfReference);
}
void* data = nullptr;
size_t size;
if (!SFile::Load(nullptr, fileName, &data, &size, 0, 0x3, nullptr) || !data) {
if (data) {
SFile::Unload(data);
}
return nullptr;
}
FT_Face theFace;
if (FT_New_Memory_Face(library, (FT_Byte*)data, size, 0, &theFace) != FT_Err_Ok || !theFace) {
if (data) {
SFile::Unload(data);
}
return nullptr;
}
if (FT_Select_Charmap(theFace, ft_encoding_unicode) != FT_Err_Ok) {
if (data) {
SFile::Unload(data);
}
return nullptr;
}
auto faceData = s_faceHash.New(fileName, 0, 0);
auto handle = HandleCreate(faceData);
faceData->data = data;
faceData->face = theFace;
faceData->selfReference = handle;
return HandleDuplicate(handle);
}

15
src/gx/font/FontFace.hpp Normal file
View file

@ -0,0 +1,15 @@
#ifndef GX_FONT_FONT_FACE_HPP
#define GX_FONT_FONT_FACE_HPP
#include "gx/font/FreeType.hpp"
#include "gx/font/Types.hpp"
void FontFaceCloseHandle(HFACE);
FT_Face FontFaceGetFace(HFACE);
const char* FontFaceGetFontName(HFACE);
HFACE FontFaceGetHandle(const char*, FT_Library);
#endif

44
src/gx/font/FreeType.cpp Normal file
View file

@ -0,0 +1,44 @@
#include "gx/font/FreeType.hpp"
#include "gx/font/FreeTypeInternal.hpp"
#include "freetype/ftmodule.h"
#include <storm/Memory.hpp>
FT_Library g_FTLibrary;
FT_MemoryRec_ s_GxuMemoryRecord = {
nullptr,
&FreeTypeAllocFunction,
&FreeTypeFreeFunction,
&FreeTypeReallocFunction
};
void FreeTypeInitialize() {
FT_New_Library(&s_GxuMemoryRecord, &g_FTLibrary);
FT_Add_Default_Modules(g_FTLibrary);
}
int32_t FREETYPE_RenderGlyph(uint32_t code, bool monochrome, FT_Face face) {
FT_UInt index = FT_Get_Char_Index(face, code);
if (!index) {
return 0;
}
FT_Int flags = monochrome
? FT_LOAD_NO_HINTING | FT_LOAD_CROP_BITMAP | FT_LOAD_PEDANTIC | FT_LOAD_LINEAR_DESIGN
: FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_PEDANTIC | FT_LOAD_LINEAR_DESIGN;
if (FT_Load_Glyph(face, index, flags)) {
return 0;
}
if (FT_Render_Glyph(face->glyph, monochrome ? ft_render_mode_mono : ft_render_mode_normal)) {
return 0;
}
return 1;
}
FT_Library GetFreeTypeLibrary() {
return g_FTLibrary;
};

13
src/gx/font/FreeType.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef GX_FONT_FREE_TYPE_HPP
#define GX_FONT_FREE_TYPE_HPP
#include "freetype/freetype.h"
#include <cstdint>
void FreeTypeInitialize();
int32_t FREETYPE_RenderGlyph(uint32_t, bool, FT_Face);
FT_Library GetFreeTypeLibrary();
#endif

View file

@ -0,0 +1,16 @@
#include "gx/font/FreeTypeInternal.hpp"
#include <storm/Memory.hpp>
void* FreeTypeAllocFunction(FT_Memory memory, long size) {
return SMemAlloc(size, __FILE__, __LINE__, 0);
};
void FreeTypeFreeFunction(FT_Memory memory, void* block) {
if (block) {
SMemFree(block, __FILE__, __LINE__, 0);
}
};
void* FreeTypeReallocFunction(FT_Memory memory, long currentSize, long newSize, void* block) {
return SMemReAlloc(block, newSize, __FILE__, __LINE__, 0);
};

View file

@ -0,0 +1,12 @@
#ifndef GX_FONT_FREE_TYPE_INTERNAL_HPP
#define GX_FONT_FREE_TYPE_INTERNAL_HPP
#include "freetype/freetype.h"
void* FreeTypeAllocFunction(FT_Memory memory, long size);
void FreeTypeFreeFunction(FT_Memory memory, void* block);
void* FreeTypeReallocFunction(FT_Memory memory, long currentSize, long newSize, void* block);
#endif

45
src/gx/font/Types.hpp Normal file
View file

@ -0,0 +1,45 @@
#ifndef GX_FONT_TYPES_HPP
#define GX_FONT_TYPES_HPP
#include <cstdint>
#include <common/Handle.hpp>
typedef HOBJECT HTEXTBLOCK;
typedef HOBJECT HTEXTFONT;
typedef HOBJECT HFACE;
enum QUOTEDCODE {
CODE_INVALIDCODE = 0x0,
CODE_COLORON = 0x1,
CODE_COLORRESTORE = 0x2,
CODE_NEWLINE = 0x3,
CODE_PIPE = 0x4,
CODE_HYPERLINKSTART = 0x5,
CODE_HYPERLINKSTOP = 0x6,
CODE_TEXTURESTART = 0x7,
CODE_TEXTURESTOP = 0x8,
NUM_QUOTEDCODES = 0x9
};
struct EMBEDDEDPARSEINFO {
// TODO
};
struct GXUEMBEDDEDTEXTUREINFO {
float float0 = 0.0f;
float float4 = 0.0f;
float float8 = 0.0f;
float floatC = 0.0f;
uint32_t dword10 = 0;
uint32_t dword14 = 0;
float float18 = 0.0f;
float float1C = 0.0f;
float float20 = 0.0f;
float float24 = 0.0f;
float float28 = 0.0f;
float float2C = 0.0f;
float float30 = 0.0f;
float float34 = 0.0f;
};
#endif

476
src/gx/font/Wrap.cpp Normal file
View file

@ -0,0 +1,476 @@
#include "gx/font/Wrap.hpp"
#include "gx/font/CGxFont.hpp"
#include "gx/Font.hpp"
#include <cmath>
#include <cwctype>
#include <storm/Unicode.hpp>
void CalcWrapPoint(CGxFont* face, const char* currentText, float fontHeight, float blockWidth, uint32_t* numBytes, float* extent, const char** nextText, float a8, uint32_t flags, bool* a10, float* a11, float scale) {
if (fontHeight < 0.0f || blockWidth <= 0.0f || !currentText || !*currentText) {
*numBytes = 0;
*extent = 0.0f;
*nextText = nullptr;
return;
}
if (flags & 0x80) {
CalcWrapPointBillboarded(
currentText,
flags,
face,
fontHeight,
numBytes,
extent,
nextText,
a11,
scale
);
} else {
CalcWrapPointNonBillboarded(
currentText,
face,
fontHeight,
blockWidth,
numBytes,
extent,
nextText,
a8,
flags,
a10,
a11,
scale
);
}
}
void CalcWrapPointBillboarded(const char* currentText, uint32_t flags, CGxFont* face, float fontHeight, uint32_t* numBytes, float* extent, const char** nextText, float* a8, float scale) {
// TODO
}
void CalcWrapPointNonBillboarded(const char* currentText, CGxFont* face, float fontHeight, float blockWidth, uint32_t* numBytes, float* extent, const char** nextText, float a8, uint32_t flags, bool* a10, float* a11, float scale) {
if (fontHeight == 0.0f || flags & 0x4) {
fontHeight = GxuFontGetOneToOneHeight(face);
}
float v46 = 0.0f;
if (a8 > 0.0f) {
v46 += ceil(ScreenToPixelWidth(0, a8));
}
if (face->m_flags & 0x8) {
v46 += 4.0f;
} else if (face->m_flags & 0x1) {
v46 += 2.0f;
}
float pixelFontHeight = ScreenToPixelHeight(0, fontHeight);
float v41 = static_cast<float>(face->GetPixelSize()) / pixelFontHeight;
float pixelBlockWidth = ceil(v41 * blockWidth * static_cast<float>(GetScreenPixelWidth()));
if (a11) {
*a11 = 0.0f;
}
uint32_t v18 = 0;
uint32_t prevCode = 0;
float v42 = 0.0f;
float v56 = 0.0f;
auto startText = currentText;
int32_t advance = 0;
QUOTEDCODE quotedCode = CODE_NEWLINE;
while (*currentText) {
uint32_t code;
quotedCode = GxuDetermineQuotedCode(currentText, advance, 0, flags, code);
if (quotedCode == CODE_NEWLINE) {
currentText += advance;
v18 = currentText - startText;
v56 = v42 + v46;
break;
}
if (
quotedCode == CODE_COLORON
|| quotedCode == CODE_COLORRESTORE
|| quotedCode == CODE_HYPERLINKSTART
|| quotedCode == CODE_HYPERLINKSTOP
|| quotedCode == CODE_TEXTURESTOP
) {
currentText += advance;
continue;
}
if (quotedCode == CODE_TEXTURESTART) {
GXUEMBEDDEDTEXTUREINFO textureInfo;
// TODO
// if (!ParseEmbeddedTexture(currentText, textureInfo, pixelFontHeight, scale, fontHeight)) {
// currentText += advance;
//
// continue;
// }
float v21 = textureInfo.float1C + v46;
if (pixelBlockWidth >= v21) {
v46 = v21;
if (a11 && *a11 < textureInfo.float18) {
*a11 = textureInfo.float18;
}
currentText += advance;
continue;
}
prevCode = code;
advance = 0;
}
if (face->NewCodeDesc(code)) {
float step = 0.0f;
if (prevCode) {
if (flags & 0x10) {
step = face->ComputeStepFixedWidth(prevCode, code);
} else {
step = face->ComputeStep(prevCode, code);
}
}
if (CanWrapBetween(code, prevCode)) {
if (*a10 || v18) {
v18 = currentText - startText;
}
*a10 = true;
v56 = v42 + v46;
}
float v38 = GetCharacterWidth(&currentText[advance], flags, code, face, fontHeight);
float v24 = step + v46;
if (pixelBlockWidth < v38 + v24) {
break;
}
prevCode = code;
v46 = v24;
v42 = v38;
}
currentText += advance;
}
v46 = (v42 + v46) / static_cast<float>(GetScreenPixelWidth());
float v57 = v56 / static_cast<float>(GetScreenPixelWidth());
v41 = pixelFontHeight / static_cast<float>(face->GetPixelSize());
if (quotedCode == CODE_NEWLINE || !*currentText) {
*extent = v46 * v41;
*nextText = currentText;
*numBytes = currentText - startText;
} else {
if (!v18 || flags & 0x2) {
*a10 = flags & 0x40;
*numBytes = currentText - startText;
*extent = v46 * v41;
} else {
*numBytes = v18;
*extent = v57 * v41;
currentText = &startText[v18];
}
while (*currentText) {
auto code = SUniSGetUTF8(reinterpret_cast<const uint8_t*>(currentText), &advance);
if (!iswspace(code)) {
break;
}
currentText += advance;
}
*nextText = currentText;
}
}
int32_t CanWrapBetween(uint32_t codeA, uint32_t codeB) {
if (!codeB) {
return 0;
}
if (codeB != '-' && codeB != ';' && codeB != '/' && codeA != ('|') && codeA != '\xFF\xFF\xFF\xFF') {
if (iswspace(codeB)) {
return 0;
}
if (!iswspace(codeA)) {
bool v3, v5;
if (codeB > 0x3008) {
if (codeB <= 0x3014) {
if (codeB != 0x3014) {
switch (codeB) {
case 0x300A:
case 0x300C:
case 0x300E:
case 0x3010:
return 0;
default:
goto LABEL_18;
}
}
return 0;
}
if (codeB > 0xFF08) {
switch (codeB) {
case 0xFF3B:
case 0xFF5B:
case 0xFFE1:
case 0xFFE5:
case 0xFFE6:
return 0;
default:
goto LABEL_18;
}
}
if (codeB == 0xFF08) {
return 0;
}
if (codeB > 0xFE5B) {
if (codeB == 0xFE5D) {
return 0;
}
v3 = codeB == 0xFF04;
} else {
if (codeB == 0xFE5B || codeB == 0x301D) {
return 0;
}
v3 = codeB == 0xFE59;
}
} else {
if (codeB == 0x3008) {
return 0;
}
if (codeB <= '{') {
if (codeB != '{') {
switch (codeB) {
case '$':
case '(':
case '[':
case '\\':
return 0;
default:
goto LABEL_18;
}
}
return 0;
}
if (codeB == 0x2018 || codeB == 0x201C) {
return 0;
}
v3 = codeB == 0x2035;
}
if (v3) {
return 0;
}
LABEL_18:
if (codeA <= 0x2014) {
if (codeA < 0x2013) {
switch (codeA) {
case '!':
case '%':
case ')':
case ',':
case '.':
case ':':
case ';':
case '?':
case ']':
case '}':
case 0xB0:
case 0xB7:
return 0;
default:
return codeB == 0x3002
|| codeB == 0xFF0C
|| (codeA >= 0x1100 && codeA <= 0x11FF)
|| (codeA >= 0x3000 && codeA <= 0xD7AF)
|| (codeA >= 0xF900 && codeA <= 0xFAFF)
|| (codeA >= 0xFF00 && codeA <= 0xFF9F)
|| codeA - 0xFFA0 <= 0x3C;
}
}
return 0;
}
if (codeA > 0xFE30) {
if (codeA > 0xFF5D) {
switch (codeA) {
case 0xFF70:
case 0xFF9E:
case 0xFF9F:
case 0xFFE0:
return 0;
default:
return codeB == 0x3002
|| codeB == 0xFF0C
|| (codeA >= 0x1100 && codeA <= 0x11FF)
|| (codeA >= 0x3000 && codeA <= 0xD7AF)
|| (codeA >= 0xF900 && codeA <= 0xFAFF)
|| (codeA >= 0xFF00 && codeA <= 0xFF9F)
|| codeA - 0xFFA0 <= 0x3C;
}
}
if (codeA != 0xFF5D) {
switch (codeA) {
case 0xFE50:
case 0xFE51:
case 0xFE52:
case 0xFE54:
case 0xFE55:
case 0xFE56:
case 0xFE57:
case 0xFE5A:
case 0xFE5C:
case 0xFE5E:
case 0xFF01:
case 0xFF05:
case 0xFF09:
case 0xFF0C:
case 0xFF0E:
case 0xFF1A:
case 0xFF1B:
case 0xFF1F:
case 0xFF3D:
return 0;
default:
return codeB == 0x3002
|| codeB == 0xFF0C
|| (codeA >= 0x1100 && codeA <= 0x11FF)
|| (codeA >= 0x3000 && codeA <= 0xD7AF)
|| (codeA >= 0xF900 && codeA <= 0xFAFF)
|| (codeA >= 0xFF00 && codeA <= 0xFF9F)
|| codeA - 0xFFA0 <= 0x3C;
}
}
return 0;
}
if (codeA == 0xFE30) {
return 0;
}
if (codeA > 0x3009) {
switch (codeA) {
case 0x300B:
case 0x300D:
case 0x300F:
case 0x3011:
case 0x3015:
case 0x301E:
case 0x30FC:
return 0;
default:
return codeB == 0x3002
|| codeB == 0xFF0C
|| (codeA >= 0x1100 && codeA <= 0x11FF)
|| (codeA >= 0x3000 && codeA <= 0xD7AF)
|| (codeA >= 0xF900 && codeA <= 0xFAFF)
|| (codeA >= 0xFF00 && codeA <= 0xFF9F)
|| codeA - 0xFFA0 <= 0x3C;
}
}
if (codeA == 0x3009) {
return 0;
}
if (codeA <= 0x2027) {
if (codeA >= 0x2026 || codeA == 0x2019 || codeA == 0x201D || codeA == 0x2022) {
return 0;
}
return codeB == 0x3002
|| codeB == 0xFF0C
|| (codeA >= 0x1100 && codeA <= 0x11FF)
|| (codeA >= 0x3000 && codeA <= 0xD7AF)
|| (codeA >= 0xF900 && codeA <= 0xFAFF)
|| (codeA >= 0xFF00 && codeA <= 0xFF9F)
|| codeA - 0xFFA0 <= 0x3C;
}
if (codeA > 0x2103) {
if (codeA < 0x3001) {
return codeB == 0x3002
|| codeB == 0xFF0C
|| (codeA >= 0x1100 && codeA <= 0x11FF)
|| (codeA >= 0x3000 && codeA <= 0xD7AF)
|| (codeA >= 0xF900 && codeA <= 0xFAFF)
|| (codeA >= 0xFF00 && codeA <= 0xFF9F)
|| codeA - 0xFFA0 <= 0x3C;
}
v5 = codeA <= 0x3002;
} else {
if (codeA == 0x2103) {
return 0;
}
if (codeA < 0x2032) {
return codeB == 0x3002
|| codeB == 0xFF0C
|| (codeA >= 0x1100 && codeA <= 0x11FF)
|| (codeA >= 0x3000 && codeA <= 0xD7AF)
|| (codeA >= 0xF900 && codeA <= 0xFAFF)
|| (codeA >= 0xFF00 && codeA <= 0xFF9F)
|| codeA - 0xFFA0 <= 0x3C;
}
v5 = codeA <= 0x2033;
}
if (v5) {
return 0;
}
return codeB == 0x3002
|| codeB == 0xFF0C
|| (codeA >= 0x1100 && codeA <= 0x11FF)
|| (codeA >= 0x3000 && codeA <= 0xD7AF)
|| (codeA >= 0xF900 && codeA <= 0xFAFF)
|| (codeA >= 0xFF00 && codeA <= 0xFF9F)
|| codeA - 0xFFA0 <= 0x3C;
}
}
return 1;
}

16
src/gx/font/Wrap.hpp Normal file
View file

@ -0,0 +1,16 @@
#ifndef GX_FONT_WRAP_HPP
#define GX_FONT_WRAP_HPP
#include <cstdint>
class CGxFont;
void CalcWrapPoint(CGxFont*, const char*, float, float, uint32_t*, float*, const char**, float, uint32_t, bool*, float*, float);
void CalcWrapPointBillboarded(const char*, uint32_t, CGxFont*, float, uint32_t*, float*, const char**, float*, float);
void CalcWrapPointNonBillboarded(const char*, CGxFont*, float, float, uint32_t*, float*, const char**, float, uint32_t, bool*, float*, float);
int32_t CanWrapBetween(uint32_t codeA, uint32_t codeB);
#endif