2022-02-27 07:53:44 -05:00
|
|
|
#include <STDInclude.hpp>
|
2017-02-26 06:57:02 -05:00
|
|
|
|
2021-06-28 14:25:23 -04:00
|
|
|
#define STB_TRUETYPE_IMPLEMENTATION
|
|
|
|
#include <stb_truetype.h>
|
|
|
|
|
2017-02-26 06:57:02 -05:00
|
|
|
namespace Assets
|
|
|
|
{
|
2021-06-28 14:25:23 -04:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
int PackFonts(const uint8_t* data, std::vector<uint16_t>& charset, Game::Glyph* glyphs, float pixel_height, unsigned char* pixels, int pw, int ph, int yOffset)
|
|
|
|
{
|
|
|
|
stbtt_fontinfo f;
|
|
|
|
f.userdata = NULL;
|
|
|
|
|
|
|
|
if (!stbtt_InitFont(&f, data, 0))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
std::memset(pixels, 0, pw * ph);
|
|
|
|
|
|
|
|
int x = 1, y = 1, bottom_y = 1;
|
|
|
|
|
|
|
|
float scale = stbtt_ScaleForPixelHeight(&f, pixel_height);
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
for (auto& ch : charset)
|
|
|
|
{
|
|
|
|
int advance, lsb, x0, y0, x1, y1, gw, gh;
|
|
|
|
|
|
|
|
int g = stbtt_FindGlyphIndex(&f, ch);
|
|
|
|
|
|
|
|
stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb);
|
|
|
|
stbtt_GetGlyphBitmapBox(&f, g, scale, scale, &x0, &y0, &x1, &y1);
|
|
|
|
|
|
|
|
gw = x1 - x0;
|
|
|
|
gh = y1 - y0;
|
|
|
|
|
|
|
|
if (x + gw + 1 >= pw)
|
|
|
|
{
|
|
|
|
// Advance to next row
|
|
|
|
y = bottom_y;
|
|
|
|
x = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (y + gh + 1 >= ph)
|
|
|
|
{
|
|
|
|
// Check if we have ran out of the room
|
|
|
|
return -i;
|
|
|
|
}
|
|
|
|
|
|
|
|
stbtt_MakeGlyphBitmap(&f, pixels + x + y * pw, gw, gh, pw, scale, scale, g);
|
|
|
|
|
|
|
|
auto& glyph = glyphs[i++];
|
|
|
|
|
|
|
|
glyph.letter = ch;
|
|
|
|
glyph.s0 = x / static_cast<float>(pw);
|
|
|
|
glyph.s1 = (x + gw) / static_cast<float>(pw);
|
|
|
|
glyph.t0 = y / static_cast<float>(ph);
|
|
|
|
glyph.t1 = (y + gh) / static_cast<float>(ph);
|
|
|
|
glyph.pixelWidth = static_cast<char>(gw);
|
|
|
|
glyph.pixelHeight = static_cast<char>(gh);
|
|
|
|
glyph.x0 = static_cast<char>(x0);
|
|
|
|
glyph.y0 = static_cast<char>(y0 + yOffset);
|
2022-01-27 12:11:21 -05:00
|
|
|
glyph.dx = static_cast<char>(std::roundf(scale * advance));
|
2021-06-28 14:25:23 -04:00
|
|
|
|
|
|
|
// Advance to next col
|
|
|
|
x = x + gw + 1;
|
|
|
|
|
|
|
|
// Expand bottom of current row if current glyph is bigger
|
|
|
|
if (y + gh + 1 > bottom_y)
|
|
|
|
{
|
|
|
|
bottom_y = y + gh + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bottom_y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-26 06:57:02 -05:00
|
|
|
void IFont_s::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
|
|
|
{
|
|
|
|
Game::Font_s *asset = header.font;
|
|
|
|
|
2018-05-09 08:33:52 -04:00
|
|
|
if (asset->material)
|
2017-02-26 06:57:02 -05:00
|
|
|
{
|
2018-05-09 08:33:52 -04:00
|
|
|
builder->loadAsset(Game::ASSET_TYPE_MATERIAL, asset->material);
|
2017-02-26 06:57:02 -05:00
|
|
|
}
|
|
|
|
|
2018-05-09 08:33:52 -04:00
|
|
|
if (asset->glowMaterial)
|
2017-02-26 06:57:02 -05:00
|
|
|
{
|
2018-05-09 08:33:52 -04:00
|
|
|
builder->loadAsset(Game::ASSET_TYPE_MATERIAL, asset->glowMaterial);
|
2017-02-26 06:57:02 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-27 12:11:21 -05:00
|
|
|
void IFont_s::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
2021-06-28 14:25:23 -04:00
|
|
|
{
|
|
|
|
Components::FileSystem::File fontDefFile(Utils::String::VA("%s.json", name.data()));
|
|
|
|
Components::FileSystem::File fontFile(Utils::String::VA("%s.ttf", name.data()));
|
|
|
|
|
|
|
|
if (fontDefFile.exists() && fontFile.exists())
|
|
|
|
{
|
|
|
|
std::string errors;
|
|
|
|
auto fontDef = json11::Json::parse(fontDefFile.getBuffer(), errors);
|
|
|
|
|
|
|
|
if (!errors.empty())
|
|
|
|
{
|
|
|
|
Components::Logger::Error("Font define %s is broken: %s.", name.data(), errors.data());
|
2022-01-27 12:11:21 -05:00
|
|
|
return;
|
2021-06-28 14:25:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!fontDef.is_object())
|
|
|
|
{
|
|
|
|
Components::Logger::Error("Font define %s is invaild.", name.data(), errors.data());
|
2022-01-27 12:11:21 -05:00
|
|
|
return;
|
2021-06-28 14:25:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
int w = fontDef["textureWidth"].int_value();
|
|
|
|
int h = fontDef["textureHeight"].int_value();
|
|
|
|
|
|
|
|
int size = fontDef["size"].int_value();
|
|
|
|
int yOffset = fontDef["yOffset"].int_value();
|
|
|
|
|
2022-01-27 12:11:21 -05:00
|
|
|
auto* pixels = builder->getAllocator()->allocateArray<uint8_t>(w * h);
|
2021-06-28 14:25:23 -04:00
|
|
|
|
|
|
|
// Setup assets
|
2022-01-27 12:11:21 -05:00
|
|
|
const auto* texName = builder->getAllocator()->duplicateString(Utils::String::VA("if_%s", name.data() + 6 /* skip "fonts/" */));
|
|
|
|
const auto* fontName = builder->getAllocator()->duplicateString(name.data());
|
|
|
|
const auto* glowMaterialName = builder->getAllocator()->duplicateString(Utils::String::VA("%s_glow", name.data()));
|
|
|
|
|
|
|
|
auto* image = builder->getAllocator()->allocate<Game::GfxImage>();
|
|
|
|
std::memcpy(image, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_IMAGE, "gamefonts_pc").image, sizeof(Game::GfxImage));
|
2021-06-28 14:25:23 -04:00
|
|
|
|
|
|
|
image->name = texName;
|
2022-01-27 12:11:21 -05:00
|
|
|
|
|
|
|
auto* material = builder->getAllocator()->allocate<Game::Material>();
|
|
|
|
std::memcpy(material, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc").material, sizeof(Game::Material));
|
|
|
|
|
2022-05-15 01:49:59 -04:00
|
|
|
auto textureTable = builder->getAllocator()->allocate<Game::MaterialTextureDef>();
|
|
|
|
std::memcpy(textureTable, material->textureTable, sizeof(Game::MaterialTextureDef));
|
|
|
|
|
|
|
|
material->textureTable = textureTable;
|
2021-06-28 14:25:23 -04:00
|
|
|
material->textureTable->u.image = image;
|
|
|
|
material->info.name = fontName;
|
|
|
|
|
2022-01-27 12:11:21 -05:00
|
|
|
auto* glowMaterial = builder->getAllocator()->allocate<Game::Material>();
|
|
|
|
std::memcpy(glowMaterial, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc_glow").material, sizeof(Game::Material));
|
|
|
|
|
2021-06-28 14:25:23 -04:00
|
|
|
glowMaterial->textureTable = material->textureTable;
|
|
|
|
glowMaterial->info.name = glowMaterialName;
|
|
|
|
|
|
|
|
std::vector<uint16_t> charset;
|
|
|
|
|
|
|
|
if (fontDef["charset"].is_array())
|
|
|
|
{
|
|
|
|
for (auto& ch : fontDef["charset"].array_items())
|
|
|
|
charset.push_back(static_cast<uint16_t>(ch.int_value()));
|
|
|
|
|
|
|
|
// order matters
|
|
|
|
std::sort(charset.begin(), charset.end());
|
|
|
|
|
|
|
|
for (uint16_t i = 32; i < 128; i++)
|
|
|
|
{
|
|
|
|
if (std::find(charset.begin(), charset.end(), i) == charset.end())
|
|
|
|
{
|
|
|
|
Components::Logger::Error("Font %s missing codepoint %d.", name.data(), i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (uint16_t i = 32; i < 128; i++)
|
|
|
|
charset.push_back(i);
|
|
|
|
}
|
|
|
|
|
2022-01-27 12:11:21 -05:00
|
|
|
auto* font = builder->getAllocator()->allocate<Game::Font_s>();
|
2021-06-28 14:25:23 -04:00
|
|
|
|
|
|
|
font->fontName = fontName;
|
|
|
|
font->pixelHeight = size;
|
|
|
|
font->material = material;
|
|
|
|
font->glowMaterial = glowMaterial;
|
|
|
|
font->glyphCount = charset.size();
|
2022-01-27 12:11:21 -05:00
|
|
|
font->glyphs = builder->getAllocator()->allocateArray<Game::Glyph>(charset.size());
|
2021-06-28 14:25:23 -04:00
|
|
|
|
|
|
|
// Generate glyph data
|
|
|
|
int result = PackFonts(reinterpret_cast<const uint8_t*>(fontFile.getBuffer().data()), charset, font->glyphs, static_cast<float>(size), pixels, w, h, yOffset);
|
|
|
|
|
|
|
|
if (result == -1)
|
|
|
|
{
|
|
|
|
Components::Logger::Error("Truetype font %s is broken.", name.data());
|
|
|
|
}
|
|
|
|
else if (result < 0)
|
|
|
|
{
|
|
|
|
Components::Logger::Error("Texture size of font %s is not enough.", name.data());
|
|
|
|
}
|
|
|
|
else if(h - result > size)
|
|
|
|
{
|
|
|
|
Components::Logger::Print("Warn: Texture of font %s have too much left over space: %d\n", name.data(), h - result);
|
|
|
|
}
|
|
|
|
|
|
|
|
header->font = font;
|
|
|
|
|
|
|
|
// Save generated materials
|
|
|
|
Game::XAssetHeader tmpHeader;
|
|
|
|
|
|
|
|
tmpHeader.image = image;
|
|
|
|
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_IMAGE, tmpHeader);
|
|
|
|
|
|
|
|
tmpHeader.material = material;
|
|
|
|
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_MATERIAL, tmpHeader);
|
|
|
|
|
|
|
|
tmpHeader.material = glowMaterial;
|
|
|
|
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_MATERIAL, tmpHeader);
|
|
|
|
|
|
|
|
// Save generated image
|
|
|
|
Utils::IO::CreateDir("userraw\\images");
|
|
|
|
|
2021-06-29 09:06:50 -04:00
|
|
|
int fileSize = w * h * 4;
|
|
|
|
int iwiHeaderSize = static_cast<int>(sizeof(Game::GfxImageFileHeader));
|
2021-06-28 14:25:23 -04:00
|
|
|
|
2021-06-29 09:06:50 -04:00
|
|
|
Game::GfxImageFileHeader iwiHeader =
|
|
|
|
{
|
|
|
|
{ 'I', 'W', 'i' },
|
|
|
|
/* version */
|
|
|
|
8,
|
|
|
|
/* flags */
|
|
|
|
2,
|
|
|
|
/* format */
|
|
|
|
Game::IMG_FORMAT_BITMAP_RGBA,
|
|
|
|
0,
|
|
|
|
/* dimensions(x, y, z) */
|
|
|
|
{ static_cast<short>(w), static_cast<short>(h), 1 },
|
|
|
|
/* fileSizeForPicmip (mipSize in bytes + sizeof(GfxImageFileHeader)) */
|
|
|
|
{ fileSize + iwiHeaderSize, fileSize, fileSize, fileSize }
|
|
|
|
};
|
|
|
|
|
|
|
|
std::string outIwi;
|
|
|
|
outIwi.resize(fileSize + sizeof(Game::GfxImageFileHeader));
|
|
|
|
|
|
|
|
std::memcpy(outIwi.data(), &iwiHeader, sizeof(Game::GfxImageFileHeader));
|
|
|
|
|
|
|
|
// Generate RGBA data
|
|
|
|
auto* rgbaPixels = outIwi.data() + sizeof(Game::GfxImageFileHeader);
|
|
|
|
|
|
|
|
for (int i = 0; i < w * h * 4; i += 4)
|
|
|
|
{
|
|
|
|
rgbaPixels[i + 0] = static_cast<char>(255);
|
|
|
|
rgbaPixels[i + 1] = static_cast<char>(255);
|
|
|
|
rgbaPixels[i + 2] = static_cast<char>(255);
|
|
|
|
rgbaPixels[i + 3] = static_cast<char>(pixels[i / 4]);
|
|
|
|
}
|
2021-06-28 14:25:23 -04:00
|
|
|
|
2021-06-29 09:06:50 -04:00
|
|
|
Utils::IO::WriteFile(Utils::String::VA("userraw\\images\\%s.iwi", texName), outIwi);
|
2021-06-28 14:25:23 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-26 06:57:02 -05:00
|
|
|
void IFont_s::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
|
|
|
{
|
|
|
|
AssertSize(Game::Font_s, 24);
|
2018-05-09 08:33:52 -04:00
|
|
|
AssertSize(Game::Glyph, 24);
|
2017-02-26 06:57:02 -05:00
|
|
|
|
|
|
|
Utils::Stream* buffer = builder->getBuffer();
|
|
|
|
Game::Font_s* asset = header.font;
|
|
|
|
Game::Font_s* dest = buffer->dest<Game::Font_s>();
|
|
|
|
|
|
|
|
buffer->save(asset);
|
|
|
|
|
|
|
|
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
|
|
|
|
2018-05-09 08:33:52 -04:00
|
|
|
if (asset->fontName)
|
2017-02-26 06:57:02 -05:00
|
|
|
{
|
2018-05-09 08:33:52 -04:00
|
|
|
buffer->saveString(asset->fontName);
|
|
|
|
Utils::Stream::ClearPointer(&dest->fontName);
|
2017-02-26 06:57:02 -05:00
|
|
|
}
|
|
|
|
|
2018-05-09 08:33:52 -04:00
|
|
|
dest->material = builder->saveSubAsset(Game::ASSET_TYPE_MATERIAL, asset->material).material;
|
|
|
|
dest->glowMaterial = builder->saveSubAsset(Game::ASSET_TYPE_MATERIAL, asset->glowMaterial).material;
|
2017-02-26 06:57:02 -05:00
|
|
|
|
2018-05-09 08:33:52 -04:00
|
|
|
if (asset->glyphs)
|
2017-02-26 06:57:02 -05:00
|
|
|
{
|
|
|
|
buffer->align(Utils::Stream::ALIGN_4);
|
2018-05-09 08:33:52 -04:00
|
|
|
buffer->saveArray(asset->glyphs, asset->glyphCount);
|
|
|
|
Utils::Stream::ClearPointer(&dest->glyphs);
|
2017-02-26 06:57:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
buffer->popBlock();
|
|
|
|
}
|
|
|
|
}
|