mirror of
https://github.com/thunderbrewhq/thunderbrew
synced 2025-12-12 19:22:30 +00:00
chore: initial commit
This commit is contained in:
commit
70b00c5c38
965 changed files with 264882 additions and 0 deletions
928
src/gx/font/CGxFont.cpp
Normal file
928
src/gx/font/CGxFont.cpp
Normal 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
141
src/gx/font/CGxFont.hpp
Normal 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
638
src/gx/font/CGxString.cpp
Normal 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, "edColor, 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
84
src/gx/font/CGxString.hpp
Normal 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
|
||||
289
src/gx/font/CGxStringBatch.cpp
Normal file
289
src/gx/font/CGxStringBatch.cpp
Normal 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);
|
||||
}
|
||||
45
src/gx/font/CGxStringBatch.hpp
Normal file
45
src/gx/font/CGxStringBatch.hpp
Normal 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
12
src/gx/font/FaceData.cpp
Normal 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
17
src/gx/font/FaceData.hpp
Normal 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
80
src/gx/font/FontFace.cpp
Normal 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
15
src/gx/font/FontFace.hpp
Normal 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
44
src/gx/font/FreeType.cpp
Normal 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
13
src/gx/font/FreeType.hpp
Normal 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
|
||||
16
src/gx/font/FreeTypeInternal.cpp
Normal file
16
src/gx/font/FreeTypeInternal.cpp
Normal 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);
|
||||
};
|
||||
12
src/gx/font/FreeTypeInternal.hpp
Normal file
12
src/gx/font/FreeTypeInternal.hpp
Normal 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
45
src/gx/font/Types.hpp
Normal 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
476
src/gx/font/Wrap.cpp
Normal 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(¤tText[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
16
src/gx/font/Wrap.hpp
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue