mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-07-18 22:10:38 +00:00
feat: font generation from ttf (#882)
* chore: add file variant of json fonts * chore: add stb dependency * feat: generate fonts from ttf files * chore: add option to add yOffset to file font * chore: dynamically adjust generated font bitmap size * fix: printable characters should respect ISO-8859-1 control characters * chore: do not write missing glyphs into the bitmap * chore: remove test texture conversion * chore: adjust font compiler for iw3,iw4,iw5,t4,t5 * chore: add possibility to fill color channels when converting * fix: games other than t6 use rgba for fonts * fix: make sure no mipmaps are set on loaddef * fix: t4 and iw3 use dimensions in image loaddef * chore: also include optional glyphs in fonts
This commit is contained in:
@@ -22,3 +22,6 @@
|
||||
[submodule "thirdparty/webwindowed"]
|
||||
path = thirdparty/webwindowed
|
||||
url = https://github.com/Laupetin/webwindowed.git
|
||||
[submodule "thirdparty/stb"]
|
||||
path = thirdparty/stb
|
||||
url = https://github.com/nothings/stb.git
|
||||
|
||||
@@ -100,6 +100,7 @@ include "thirdparty/json.lua"
|
||||
include "thirdparty/minilzo.lua"
|
||||
include "thirdparty/minizip.lua"
|
||||
include "thirdparty/salsa20.lua"
|
||||
include "thirdparty/stb.lua"
|
||||
include "thirdparty/webwindowed.lua"
|
||||
include "thirdparty/zlib.lua"
|
||||
|
||||
@@ -115,6 +116,7 @@ group "ThirdParty"
|
||||
minilzo:project()
|
||||
minizip:project()
|
||||
salsa20:project()
|
||||
stb:project()
|
||||
zlib:project()
|
||||
|
||||
if _OPTIONS["modman"] then
|
||||
|
||||
@@ -838,7 +838,7 @@ namespace T6
|
||||
uint32_t valid : 1;
|
||||
};
|
||||
|
||||
enum MapType
|
||||
enum MapType : unsigned char
|
||||
{
|
||||
MAPTYPE_NONE = 0x0,
|
||||
MAPTYPE_INVALID1 = 0x1,
|
||||
@@ -900,7 +900,7 @@ namespace T6
|
||||
struct GfxImage
|
||||
{
|
||||
GfxTexture texture;
|
||||
char mapType;
|
||||
MapType mapType;
|
||||
char semantic;
|
||||
char category;
|
||||
bool delayLoadPixels;
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace font
|
||||
return path.string();
|
||||
}
|
||||
|
||||
// ISO-8859-1
|
||||
bool IsPrintableLetter(const unsigned letter)
|
||||
{
|
||||
// Control characters
|
||||
@@ -23,25 +24,17 @@ namespace font
|
||||
if (letter < 0x7F)
|
||||
return true;
|
||||
|
||||
// 0xA0 is not a control character but NBSP that just looks like a space
|
||||
if (letter < 0xA1)
|
||||
return false;
|
||||
|
||||
// There are characters after this point that are printable as well
|
||||
// But they don't seem to play a role in cod fonts
|
||||
if (letter > 0xFF)
|
||||
return false;
|
||||
|
||||
switch (letter)
|
||||
{
|
||||
case 0x7F:
|
||||
case 0x81:
|
||||
case 0x8D:
|
||||
case 0x8F:
|
||||
case 0x90:
|
||||
case 0x9D:
|
||||
case 0xA0:
|
||||
case 0xAD:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
// 0xAD is soft hyphen
|
||||
return letter != 0xAD;
|
||||
}
|
||||
|
||||
std::string LetterToString(const unsigned letter)
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#include "Json/JsonExtension.h"
|
||||
#include "Font/FontCommon.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace GAME
|
||||
@@ -100,7 +102,7 @@ namespace GAME
|
||||
}
|
||||
#endif
|
||||
|
||||
class JsonFont
|
||||
class JsonCompiledFont
|
||||
{
|
||||
public:
|
||||
unsigned pixelHeight;
|
||||
@@ -116,7 +118,7 @@ namespace GAME
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_EXTENSION(
|
||||
JsonFont,
|
||||
JsonCompiledFont,
|
||||
pixelHeight,
|
||||
#ifdef FEATURE_T6
|
||||
isScalingAllowed,
|
||||
@@ -130,4 +132,62 @@ namespace GAME
|
||||
glyphs
|
||||
#endif
|
||||
);
|
||||
|
||||
class JsonFileFont
|
||||
{
|
||||
public:
|
||||
std::string file;
|
||||
std::optional<unsigned> fontSize;
|
||||
std::optional<int> yOffset;
|
||||
#ifdef FEATURE_T6
|
||||
std::optional<bool> isScalingAllowed;
|
||||
#endif
|
||||
std::optional<std::string> generatedMaterialName;
|
||||
std::optional<std::string> generatedGlowMaterialName;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_EXTENSION(
|
||||
JsonFileFont,
|
||||
file,
|
||||
fontSize,
|
||||
yOffset,
|
||||
#ifdef FEATURE_T6
|
||||
isScalingAllowed,
|
||||
#endif
|
||||
generatedMaterialName,
|
||||
generatedGlowMaterialName
|
||||
);
|
||||
|
||||
class JsonFont
|
||||
{
|
||||
public:
|
||||
std::variant<JsonCompiledFont, JsonFileFont> font;
|
||||
};
|
||||
|
||||
NLOHMANN_TO_JSON_METHOD(JsonFont)
|
||||
{
|
||||
if (std::holds_alternative<JsonCompiledFont>(nlohmann_json_t.font))
|
||||
{
|
||||
to_json(nlohmann_json_j, std::get<JsonCompiledFont>(nlohmann_json_t.font));
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(std::holds_alternative<JsonFileFont>(nlohmann_json_t.font));
|
||||
to_json(nlohmann_json_j, std::get<JsonFileFont>(nlohmann_json_t.font));
|
||||
}
|
||||
}
|
||||
|
||||
NLOHMANN_FROM_JSON_METHOD(JsonFont)
|
||||
{
|
||||
if (nlohmann_json_j.contains("file"))
|
||||
{
|
||||
nlohmann_json_t.font.emplace<JsonFileFont>();
|
||||
from_json(nlohmann_json_j, std::get<JsonFileFont>(nlohmann_json_t.font));
|
||||
}
|
||||
else
|
||||
{
|
||||
nlohmann_json_t.font.emplace<JsonCompiledFont>();
|
||||
from_json(nlohmann_json_j, std::get<JsonCompiledFont>(nlohmann_json_t.font));
|
||||
}
|
||||
}
|
||||
} // namespace GAME
|
||||
|
||||
@@ -15,6 +15,7 @@ end
|
||||
function ObjCompiling:link(links)
|
||||
links:add(self:name())
|
||||
links:linkto(minilzo)
|
||||
links:linkto(stb)
|
||||
links:linkto(Utils)
|
||||
links:linkto(ObjCommon)
|
||||
links:linkto(ObjLoading)
|
||||
@@ -58,4 +59,5 @@ function ObjCompiling:project()
|
||||
minilzo:include(includes)
|
||||
Utils:include(includes)
|
||||
json:include(includes)
|
||||
stb:include(includes)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,524 @@
|
||||
#options GAME (IW3, IW4, IW5, T4, T5, T6)
|
||||
|
||||
#filename "Game/" + GAME + "/Font/FontCompiler" + GAME + ".cpp"
|
||||
|
||||
#if GAME == "IW3"
|
||||
#define FEATURE_IW3
|
||||
#define IWI_NS iwi6
|
||||
#define GAME_LOWER "iw3"
|
||||
#elif GAME == "IW4"
|
||||
#define FEATURE_IW4
|
||||
#define IWI_NS iwi8
|
||||
#define GAME_LOWER "iw4"
|
||||
#elif GAME == "IW5"
|
||||
#define FEATURE_IW5
|
||||
#define IWI_NS iwi8
|
||||
#define GAME_LOWER "iw5"
|
||||
#elif GAME == "T4"
|
||||
#define FEATURE_T4
|
||||
#define IWI_NS iwi6
|
||||
#define GAME_LOWER "t4"
|
||||
#elif GAME == "T5"
|
||||
#define FEATURE_T5
|
||||
#define IWI_NS iwi13
|
||||
#define GAME_LOWER "t5"
|
||||
#elif GAME == "T6"
|
||||
#define FEATURE_T6
|
||||
#define IWI_NS iwi27
|
||||
#define GAME_LOWER "t6"
|
||||
#endif
|
||||
|
||||
// This file was templated.
|
||||
// See FontCompiler.cpp.template.
|
||||
// Do not modify, changes will be lost.
|
||||
|
||||
#set COMPILER_HEADER "\"FontCompiler" + GAME + ".h\""
|
||||
#include COMPILER_HEADER
|
||||
|
||||
#set COMMON_HEADER "\"Game/" + GAME + "/Common" + GAME + ".h\""
|
||||
#include COMMON_HEADER
|
||||
#include "Font/FontCommon.h"
|
||||
#set JSON_HEADER "\"Game/" + GAME + "/Font/JsonFont" + GAME + ".h\""
|
||||
#include JSON_HEADER
|
||||
#include "Image/IwiTypes.h"
|
||||
#include "Image/Texture.h"
|
||||
#include "Image/TextureConverter.h"
|
||||
#include "Utils/Alignment.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/StringUtils.h"
|
||||
|
||||
#include <format>
|
||||
#include <filesystem>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <stb_truetype.h>
|
||||
|
||||
using namespace nlohmann;
|
||||
using namespace GAME;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto REQUIRED_GLYPH_START = 0x20;
|
||||
constexpr auto OPTIONAL_GLYPH_END_INCLUSIVE = 0xFF;
|
||||
constexpr unsigned REQUIRED_PLUS_OPTIONAL_GLYPH_COUNT = OPTIONAL_GLYPH_END_INCLUSIVE + 1 - REQUIRED_GLYPH_START;
|
||||
|
||||
constexpr unsigned DEFAULT_FONT_SIZE = 20;
|
||||
constexpr unsigned DEFAULT_Y_OFFSET = 0;
|
||||
constexpr unsigned DEFAULT_TEXTURE_WIDTH = 256;
|
||||
|
||||
void PrintError(const Font_s& font, const std::string& message)
|
||||
{
|
||||
con::error("Cannot compile font \"{}\": {}", font.fontName, message);
|
||||
}
|
||||
|
||||
unsigned GetBakedTextureHeight(const stbtt_fontinfo& f, const float scale, const std::vector<Glyph>& glyphs, const unsigned textureWidth)
|
||||
{
|
||||
auto x = 1u;
|
||||
auto y = 1u;
|
||||
auto bottomY = 1u;
|
||||
|
||||
for (const auto& glyph : glyphs)
|
||||
{
|
||||
const auto fontGlyphIndex = stbtt_FindGlyphIndex(&f, glyph.letter);
|
||||
|
||||
int advance, lsb;
|
||||
stbtt_GetGlyphHMetrics(&f, fontGlyphIndex, &advance, &lsb);
|
||||
|
||||
int x0, y0, x1, y1;
|
||||
stbtt_GetGlyphBitmapBox(&f, fontGlyphIndex, scale, scale, &x0, &y0, &x1, &y1);
|
||||
|
||||
const auto glyphWidth = static_cast<unsigned>(x1 - x0);
|
||||
const auto glyphHeight = static_cast<unsigned>(y1 - y0);
|
||||
if (x + glyphWidth + 1u >= textureWidth)
|
||||
{
|
||||
// advance to next row
|
||||
x = 1u;
|
||||
y = bottomY;
|
||||
}
|
||||
|
||||
assert(x + glyphWidth < textureWidth);
|
||||
|
||||
if (fontGlyphIndex > 0)
|
||||
{
|
||||
x = x + glyphWidth + 1u;
|
||||
bottomY = std::max(y + glyphHeight + 1u, bottomY);
|
||||
}
|
||||
}
|
||||
|
||||
return utils::Align<unsigned>(bottomY + 1u, 16u);
|
||||
}
|
||||
|
||||
std::unique_ptr<image::Texture> BakeAlphaTexture(const unsigned char* fontData, const unsigned fontSize, const int yOffset, std::vector<Glyph>& glyphs)
|
||||
{
|
||||
stbtt_fontinfo f;
|
||||
if (!stbtt_InitFont(&f, fontData, 0))
|
||||
return nullptr;
|
||||
|
||||
const auto scale = stbtt_ScaleForPixelHeight(&f, static_cast<float>(fontSize));
|
||||
|
||||
constexpr auto textureWidth = DEFAULT_TEXTURE_WIDTH;
|
||||
const auto textureHeight = GetBakedTextureHeight(f, scale, glyphs, textureWidth);
|
||||
|
||||
auto texture = image::Texture::CreateForType(image::TextureType::T_2D, &image::format::A8, textureWidth, textureHeight, 1, false);
|
||||
texture->Allocate();
|
||||
auto* buf = texture->GetBufferForMipLevel(0);
|
||||
|
||||
f.userdata = nullptr;
|
||||
memset(buf, 0, textureWidth * textureHeight);
|
||||
|
||||
auto x = 1u;
|
||||
auto y = 1u;
|
||||
auto bottomY = 1u;
|
||||
for (auto& glyph : glyphs)
|
||||
{
|
||||
const auto fontGlyphIndex = stbtt_FindGlyphIndex(&f, glyph.letter);
|
||||
|
||||
int advance, lsb;
|
||||
stbtt_GetGlyphHMetrics(&f, fontGlyphIndex, &advance, &lsb);
|
||||
|
||||
int x0, y0, x1, y1;
|
||||
stbtt_GetGlyphBitmapBox(&f, fontGlyphIndex, scale, scale, &x0, &y0, &x1, &y1);
|
||||
|
||||
const auto glyphWidth = static_cast<unsigned>(x1 - x0);
|
||||
const auto glyphHeight = static_cast<unsigned>(y1 - y0);
|
||||
if (x + glyphWidth + 1 >= textureWidth)
|
||||
{
|
||||
// advance to next row
|
||||
x = 1;
|
||||
y = bottomY;
|
||||
}
|
||||
|
||||
glyph.x0 = static_cast<decltype(Glyph::x0)>(x0);
|
||||
glyph.y0 = static_cast<decltype(Glyph::y0)>(y0 + yOffset);
|
||||
glyph.dx = static_cast<decltype(Glyph::dx)>(std::round(scale * static_cast<float>(advance)));
|
||||
|
||||
// Glyph index 0 means no glyph found in font
|
||||
if (fontGlyphIndex > 0)
|
||||
{
|
||||
// check if it fits vertically AFTER potentially moving to next row
|
||||
assert(y + glyphHeight + 1u < textureHeight);
|
||||
if (y + glyphHeight + 1u >= textureHeight)
|
||||
return nullptr;
|
||||
|
||||
assert(x + glyphWidth < textureWidth);
|
||||
assert(y + glyphHeight < textureHeight);
|
||||
|
||||
stbtt_MakeGlyphBitmap(
|
||||
&f, buf + x + y * textureWidth, static_cast<int>(glyphWidth), static_cast<int>(glyphHeight), textureWidth, scale, scale, fontGlyphIndex);
|
||||
|
||||
glyph.pixelWidth = static_cast<decltype(Glyph::pixelWidth)>(glyphWidth);
|
||||
glyph.pixelHeight = static_cast<decltype(Glyph::pixelHeight)>(glyphHeight);
|
||||
glyph.s0 = static_cast<float>(x) / static_cast<float>(textureWidth);
|
||||
glyph.t0 = static_cast<float>(y) / static_cast<float>(textureHeight);
|
||||
glyph.s1 = static_cast<float>(x + glyphWidth) / static_cast<float>(textureWidth);
|
||||
glyph.t1 = static_cast<float>(y + glyphHeight) / static_cast<float>(textureHeight);
|
||||
|
||||
x = x + glyphWidth + 1u;
|
||||
bottomY = std::max(y + glyphHeight + 1u, bottomY);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the font does not have the glyph we set it to empty space
|
||||
glyph.pixelWidth = 0;
|
||||
glyph.pixelHeight = 0;
|
||||
glyph.s0 = 1.0f / static_cast<float>(textureWidth);
|
||||
glyph.t0 = 1.0f / static_cast<float>(textureHeight);
|
||||
glyph.s1 = glyph.s0;
|
||||
glyph.t1 = glyph.t0;
|
||||
}
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
XAssetInfo<GfxImage>* CreateFontImage(const std::string& name, const image::Texture& texture, MemoryManager& memory, AssetCreationContext& context)
|
||||
{
|
||||
auto* image = memory.Alloc<GfxImage>();
|
||||
image->name = memory.Dup(name.c_str());
|
||||
image->width = static_cast<decltype(GfxImage::width)>(texture.GetWidth());
|
||||
image->height = static_cast<decltype(GfxImage::height)>(texture.GetHeight());
|
||||
image->depth = static_cast<decltype(GfxImage::depth)>(texture.GetDepth());
|
||||
image->mapType = MAPTYPE_2D;
|
||||
image->semantic = TS_2D;
|
||||
image->noPicmip = true;
|
||||
|
||||
const auto textureSize = texture.GetFormat()->GetSizeOfMipLevel(0, image->width, image->height, image->depth);
|
||||
auto* loadDef = static_cast<GfxImageLoadDef*>(memory.AllocRaw(offsetof(GfxImageLoadDef, data) + textureSize));
|
||||
image->texture.loadDef = loadDef;
|
||||
|
||||
#if defined(FEATURE_IW3) || defined(FEATURE_T4)
|
||||
loadDef->dimensions[0] = image->width;
|
||||
loadDef->dimensions[1] = image->height;
|
||||
loadDef->dimensions[2] = image->depth;
|
||||
#endif
|
||||
|
||||
loadDef->resourceSize = static_cast<decltype(GfxImageLoadDef::resourceSize)>(textureSize);
|
||||
#ifdef FEATURE_T6
|
||||
loadDef->format = texture.GetFormat()->GetDxgiFormat();
|
||||
#else
|
||||
loadDef->format = texture.GetFormat()->GetD3DFormat();
|
||||
#endif
|
||||
loadDef->levelCount = 1;
|
||||
std::memcpy(loadDef->data, texture.GetBufferForMipLevel(0), textureSize);
|
||||
|
||||
loadDef->flags |= image::IWI_NS::IMG_FLAG_NOMIPMAPS;
|
||||
|
||||
return context.AddAsset<AssetImage>(name, image);
|
||||
}
|
||||
|
||||
XAssetInfo<Material>* CreateFontMaterialShared(Material* material, AssetRegistration<AssetMaterial>& registration, XAssetInfo<GfxImage>* image, MemoryManager& memory, AssetCreationContext& context)
|
||||
{
|
||||
material->cameraRegion = CAMERA_REGION_NONE;
|
||||
|
||||
#ifdef defined(FEATURE_IW4) || defined(FEATURE_IW5)
|
||||
material->stateFlags = 3;
|
||||
#endif
|
||||
#ifdef FEATURE_T6
|
||||
material->info.contents = 1;
|
||||
#endif
|
||||
#if defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||
material->info.layeredSurfaceTypes = 0x20000000;
|
||||
#endif
|
||||
material->info.textureAtlasColumnCount = 1;
|
||||
material->info.textureAtlasRowCount = 1;
|
||||
|
||||
for (auto& entry : material->stateBitsEntry)
|
||||
entry = -1;
|
||||
material->stateBitsEntry[TECHNIQUE_UNLIT] = 0;
|
||||
|
||||
material->stateBitsCount = 1;
|
||||
material->stateBitsTable = memory.Alloc<GfxStateBits>(1);
|
||||
auto& stateBits = material->stateBitsTable[0].loadBits.structured;
|
||||
stateBits.srcBlendRgb = GFXS_BLEND_SRCALPHA;
|
||||
stateBits.dstBlendRgb = GFXS_BLEND_INVSRCALPHA;
|
||||
stateBits.blendOpRgb = GFXS_BLENDOP_ADD;
|
||||
stateBits.alphaTest = GFXS_ALPHA_TEST_GT_0;
|
||||
stateBits.cullFace = GFXS_CULL_BACK;
|
||||
stateBits.srcBlendAlpha = GFXS_BLEND_INVDESTALPHA;
|
||||
stateBits.dstBlendAlpha = GFXS_BLEND_ONE;
|
||||
stateBits.blendOpAlpha = GFXS_BLENDOP_ADD;
|
||||
stateBits.colorWriteRgb = 1;
|
||||
stateBits.colorWriteAlpha = 1;
|
||||
stateBits.polymodeLine = 0;
|
||||
stateBits.depthWrite = 0;
|
||||
stateBits.depthTestDisabled = 1;
|
||||
stateBits.polygonOffset = GFXS_POLYGON_OFFSET_0;
|
||||
|
||||
material->textureCount = 1;
|
||||
material->textureTable = memory.Alloc<MaterialTextureDef>(1);
|
||||
auto& textureDef = material->textureTable[0];
|
||||
|
||||
#if defined(FEATURE_T6)
|
||||
// Yes, there is a typo. That's what the game does.
|
||||
textureDef.nameHash = Common::R_HashString("FontTextutre");
|
||||
textureDef.nameStart = 'F';
|
||||
textureDef.nameEnd = 'e';
|
||||
#else
|
||||
textureDef.nameHash = Common::R_HashString("colorMap");
|
||||
textureDef.nameStart = 'c';
|
||||
textureDef.nameEnd = 'p';
|
||||
#endif
|
||||
|
||||
textureDef.samplerState.filter = TEXTURE_FILTER_LINEAR;
|
||||
textureDef.samplerState.mipMap = SAMPLER_MIPMAP_ENUM_DISABLED;
|
||||
textureDef.semantic = TS_2D;
|
||||
#ifdef FEATURE_T6
|
||||
textureDef.image = image->Asset();
|
||||
#else
|
||||
textureDef.u.image = image->Asset();
|
||||
#endif
|
||||
registration.AddDependency(image);
|
||||
|
||||
return context.AddAsset<AssetMaterial>(std::move(registration));
|
||||
}
|
||||
|
||||
XAssetInfo<Material>* CreateFontMaterial(const std::string& name, XAssetInfo<GfxImage>* image, MemoryManager& memory, AssetCreationContext& context)
|
||||
{
|
||||
auto* material = memory.Alloc<Material>();
|
||||
material->info.name = memory.Dup(name.c_str());
|
||||
AssetRegistration<AssetMaterial> registration(name, material);
|
||||
|
||||
CreateFontMaterialShared(material, registration, image, memory, context);
|
||||
|
||||
#if defined(FEATURE_IW4)
|
||||
material->info.sortKey = 47;
|
||||
#elif defined(FEATURE_IW5)
|
||||
material->info.sortKey = 54;
|
||||
#elif defined(FEATURE_IW3) || defined(FEATURE_T4) || defined(FEATURE_T5)
|
||||
material->info.sortKey = 43;
|
||||
#elif defined(FEATURE_T6)
|
||||
material->info.sortKey = 40;
|
||||
#endif
|
||||
|
||||
auto* techsetAsset = context.LoadDependency<AssetTechniqueSet>(
|
||||
#if defined(FEATURE_T6)
|
||||
"sw4_2d_alphafont_3q76z816"
|
||||
#else
|
||||
"2d"
|
||||
#endif
|
||||
);
|
||||
if (!techsetAsset)
|
||||
return nullptr;
|
||||
material->techniqueSet = techsetAsset->Asset();
|
||||
registration.AddDependency(techsetAsset);
|
||||
|
||||
auto& stateBits = material->stateBitsTable[0].loadBits.structured;
|
||||
stateBits.dstBlendRgb = GFXS_BLEND_INVSRCALPHA;
|
||||
#if defined(FEATURE_IW4) || defined(FEATURE_IW5)
|
||||
stateBits.dstBlendAlpha = GFXS_BLEND_ZERO;
|
||||
#endif
|
||||
|
||||
return context.AddAsset<AssetMaterial>(std::move(registration));
|
||||
}
|
||||
|
||||
XAssetInfo<Material>* CreateFontGlowMaterial(const std::string& name, XAssetInfo<GfxImage>* image, MemoryManager& memory, AssetCreationContext& context)
|
||||
{
|
||||
auto* material = memory.Alloc<Material>();
|
||||
material->info.name = memory.Dup(name.c_str());
|
||||
AssetRegistration<AssetMaterial> registration(name, material);
|
||||
|
||||
CreateFontMaterialShared(material, registration, image, memory, context);
|
||||
|
||||
#if defined(FEATURE_IW4)
|
||||
material->info.sortKey = 34;
|
||||
#elif defined(FEATURE_IW5)
|
||||
material->info.sortKey = 54;
|
||||
#elif defined(FEATURE_IW3) || defined(FEATURE_T4) || defined(FEATURE_T5) || defined(FEATURE_T6)
|
||||
material->info.sortKey = 4;
|
||||
#endif
|
||||
|
||||
auto* techsetAsset = context.LoadDependency<AssetTechniqueSet>(
|
||||
#if defined(FEATURE_T6)
|
||||
"sw4_2d_alphafont_3340wf8q"
|
||||
#else
|
||||
"2d"
|
||||
#endif
|
||||
);
|
||||
if (!techsetAsset)
|
||||
return nullptr;
|
||||
material->techniqueSet = techsetAsset->Asset();
|
||||
registration.AddDependency(techsetAsset);
|
||||
|
||||
auto& stateBits = material->stateBitsTable[0].loadBits.structured;
|
||||
stateBits.dstBlendRgb = GFXS_BLEND_ONE;
|
||||
|
||||
return context.AddAsset<AssetMaterial>(std::move(registration));
|
||||
}
|
||||
|
||||
class FontCompiler final : public AssetCreator<AssetFont>
|
||||
{
|
||||
public:
|
||||
FontCompiler(MemoryManager& memory, ISearchPath& searchPath)
|
||||
: m_memory(memory),
|
||||
m_search_path(searchPath)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
|
||||
{
|
||||
const auto file = m_search_path.Open(font::GetJsonFileNameForAssetName(assetName));
|
||||
if (!file.IsOpen())
|
||||
return AssetCreationResult::NoAction();
|
||||
|
||||
auto* font = m_memory.Alloc<Font_s>();
|
||||
font->fontName = m_memory.Dup(assetName.c_str());
|
||||
AssetRegistration<AssetFont> registration(assetName, font);
|
||||
|
||||
try
|
||||
{
|
||||
const auto jRoot = json::parse(*file.m_stream);
|
||||
std::string type;
|
||||
unsigned version;
|
||||
|
||||
jRoot.at("_type").get_to(type);
|
||||
jRoot.at("_version").get_to(version);
|
||||
|
||||
if (type != "font" || version != 1u)
|
||||
{
|
||||
con::error(R"(Tried to load font "{}" but did not find expected type font of version 1)", assetName);
|
||||
return AssetCreationResult::Failure();
|
||||
}
|
||||
|
||||
std::string game;
|
||||
jRoot.at("_game").get_to(game);
|
||||
if (game != GAME_LOWER)
|
||||
{
|
||||
con::error(R"(Tried to load font "{}" but "_game" did not have expected value {})", assetName, GAME_LOWER);
|
||||
return AssetCreationResult::Failure();
|
||||
}
|
||||
|
||||
const auto jFont = jRoot.get<JsonFont>();
|
||||
if (!std::holds_alternative<JsonFileFont>(jFont.font))
|
||||
return AssetCreationResult::NoAction();
|
||||
|
||||
if (CreateFontFromJson(std::get<JsonFileFont>(jFont.font), *font, registration, context))
|
||||
return AssetCreationResult::Success(context.AddAsset(std::move(registration)));
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
con::error("Failed to parse json of font: {}", e.what());
|
||||
}
|
||||
|
||||
return AssetCreationResult::Failure();
|
||||
}
|
||||
|
||||
private:
|
||||
bool CreateFontFromJson(const JsonFileFont& jFont, Font_s& font, AssetRegistration<AssetFont>& registration, AssetCreationContext& context) const
|
||||
{
|
||||
const auto fontSize = jFont.fontSize.value_or(DEFAULT_FONT_SIZE);
|
||||
const auto yOffset = jFont.yOffset.value_or(DEFAULT_Y_OFFSET);
|
||||
font.pixelHeight = static_cast<decltype(Font_s::pixelHeight)>(fontSize);
|
||||
|
||||
#ifdef FEATURE_T6
|
||||
const auto scalingAllowed = jFont.isScalingAllowed.value_or(false);
|
||||
font.isScalingAllowed = scalingAllowed ? 1 : 0;
|
||||
#endif
|
||||
|
||||
const auto extension = fs::path(jFont.file).extension().string();
|
||||
if (!utils::StringEqualsIgnoreCase(extension, ".ttf"))
|
||||
{
|
||||
PrintError(font, std::format("Only ttf files are supported: {}", jFont.file));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto fontFile = m_search_path.Open(jFont.file);
|
||||
if (!fontFile.IsOpen())
|
||||
{
|
||||
PrintError(font, std::format("Failed to open font file: {}", jFont.file));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> fontData(static_cast<size_t>(fontFile.m_length));
|
||||
fontFile.m_stream->read(reinterpret_cast<char*>(fontData.data()), fontFile.m_length);
|
||||
|
||||
std::vector<Glyph> glyphs;
|
||||
glyphs.reserve(REQUIRED_PLUS_OPTIONAL_GLYPH_COUNT);
|
||||
for (auto letter = REQUIRED_GLYPH_START; letter <= OPTIONAL_GLYPH_END_INCLUSIVE; letter++)
|
||||
{
|
||||
Glyph glyph{};
|
||||
glyph.letter = static_cast<decltype(Glyph::letter)>(letter);
|
||||
glyphs.emplace_back(glyph);
|
||||
}
|
||||
|
||||
auto texture = BakeAlphaTexture(fontData.data(), fontSize, yOffset, glyphs);
|
||||
if (!texture)
|
||||
{
|
||||
PrintError(font, std::format("Failed to bake font alpha texture: {}", jFont.file));
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef FEATURE_T6
|
||||
{
|
||||
image::TextureConverter converter(texture.get(), &image::format::B8_G8_R8_A8);
|
||||
converter.SetColorFill(true, true, true);
|
||||
texture = converter.Convert();
|
||||
}
|
||||
#endif
|
||||
|
||||
font.glyphCount = static_cast<decltype(Glyph::letter)>(glyphs.size());
|
||||
font.glyphs = m_memory.Alloc<Glyph>(font.glyphCount);
|
||||
std::memcpy(font.glyphs, glyphs.data(), font.glyphCount * sizeof(Glyph));
|
||||
|
||||
#ifdef FEATURE_T6
|
||||
// The game is bugged here and accesses the kerning pairs even when kerningPairsCount is set to 0.
|
||||
// It always checks the first entry so we add a nonsense entry here that never matches
|
||||
// to ensure that no kerning is applied on randomly matching bytes.
|
||||
font.kerningPairsCount = 1;
|
||||
font.kerningPairs = m_memory.Alloc<KerningPairs>(1);
|
||||
font.kerningPairs[0].wFirst = 0;
|
||||
font.kerningPairs[0].wSecond = 0;
|
||||
font.kerningPairs[0].iKernAmount = 0;
|
||||
#endif
|
||||
|
||||
auto materialName = jFont.generatedMaterialName.value_or(font.fontName);
|
||||
auto* image = CreateFontImage(materialName, *texture, m_memory, context);
|
||||
|
||||
auto fontMaterial = CreateFontMaterial(materialName, image, m_memory, context);
|
||||
if (!fontMaterial)
|
||||
return false;
|
||||
font.material = fontMaterial->Asset();
|
||||
registration.AddDependency(fontMaterial);
|
||||
|
||||
auto glowMaterialName = jFont.generatedGlowMaterialName.value_or(std::format("{}_glow", materialName));
|
||||
auto fontGlowMaterial = CreateFontGlowMaterial(glowMaterialName, image, m_memory, context);
|
||||
if (!fontGlowMaterial)
|
||||
return false;
|
||||
font.glowMaterial = fontGlowMaterial->Asset();
|
||||
registration.AddDependency(fontGlowMaterial);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryManager& m_memory;
|
||||
ISearchPath& m_search_path;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace font
|
||||
{
|
||||
#set CREATE_COMPILER_METHOD "CreateCompiler" + GAME
|
||||
std::unique_ptr<AssetCreator<AssetFont>> CREATE_COMPILER_METHOD(MemoryManager& memory, ISearchPath& searchPath)
|
||||
{
|
||||
return std::make_unique<FontCompiler>(memory, searchPath);
|
||||
}
|
||||
} // namespace font
|
||||
@@ -0,0 +1,23 @@
|
||||
#options GAME (IW3, IW4, IW5, T4, T5, T6)
|
||||
|
||||
#filename "Game/" + GAME + "/Font/FontCompiler" + GAME + ".h"
|
||||
|
||||
// This file was templated.
|
||||
// See FontCompiler.h.template.
|
||||
// Do not modify, changes will be lost.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Asset/IAssetCreator.h"
|
||||
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
|
||||
#include GAME_HEADER
|
||||
#include "SearchPath/ISearchPath.h"
|
||||
#include "Utils/MemoryManager.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace font
|
||||
{
|
||||
#set CREATE_COMPILER_METHOD "CreateCompiler" + GAME
|
||||
std::unique_ptr<AssetCreator<GAME::AssetFont>> CREATE_COMPILER_METHOD(MemoryManager& memory, ISearchPath& searchPath);
|
||||
} // namespace font
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ObjCompilerIW3.h"
|
||||
|
||||
#include "Game/IW3/Font/FontCompilerIW3.h"
|
||||
#include "Game/IW3/IW3.h"
|
||||
#include "Game/IW3/Techset/TechniqueCompilerIW3.h"
|
||||
#include "Game/IW3/Techset/TechsetCompilerIW3.h"
|
||||
@@ -17,6 +18,7 @@ namespace
|
||||
auto& memory = zone.Memory();
|
||||
|
||||
collection.AddAssetCreator(techset::CreateTechsetCompilerIW3(memory, searchPath));
|
||||
collection.AddAssetCreator(font::CreateCompilerIW3(memory, searchPath));
|
||||
|
||||
collection.AddSubAssetCreator(techset::CreateTechniqueCompilerIW3(memory, zone, searchPath));
|
||||
collection.AddSubAssetCreator(techset::CreateVertexDeclCompilerIW3(memory));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ObjCompilerIW4.h"
|
||||
|
||||
#include "Game/IW4/Font/FontCompilerIW4.h"
|
||||
#include "Game/IW4/IW4.h"
|
||||
#include "Game/IW4/Techset/TechniqueCompilerIW4.h"
|
||||
#include "Game/IW4/Techset/TechsetCompilerIW4.h"
|
||||
@@ -22,6 +23,7 @@ namespace
|
||||
#endif
|
||||
collection.AddAssetCreator(techset::CreateVertexDeclCompilerIW4(memory));
|
||||
collection.AddAssetCreator(techset::CreateTechsetCompilerIW4(memory, searchPath));
|
||||
collection.AddAssetCreator(font::CreateCompilerIW4(memory, searchPath));
|
||||
|
||||
collection.AddSubAssetCreator(techset::CreateTechniqueCompilerIW4(memory, zone, searchPath));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ObjCompilerIW5.h"
|
||||
|
||||
#include "Game/IW5/Font/FontCompilerIW5.h"
|
||||
#include "Game/IW5/IW5.h"
|
||||
#include "Game/IW5/Techset/TechniqueCompilerIW5.h"
|
||||
#include "Game/IW5/Techset/TechsetCompilerIW5.h"
|
||||
@@ -18,6 +19,7 @@ namespace
|
||||
|
||||
collection.AddAssetCreator(techset::CreateVertexDeclCompilerIW5(memory));
|
||||
collection.AddAssetCreator(techset::CreateTechsetCompilerIW5(memory, searchPath));
|
||||
collection.AddAssetCreator(font::CreateCompilerIW5(memory, searchPath));
|
||||
|
||||
collection.AddSubAssetCreator(techset::CreateTechniqueCompilerIW5(memory, zone, searchPath));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ObjCompilerT4.h"
|
||||
|
||||
#include "Game/T4/Font/FontCompilerT4.h"
|
||||
#include "Game/T4/T4.h"
|
||||
#include "Image/ImageIwdPostProcessor.h"
|
||||
|
||||
@@ -9,7 +10,12 @@ using namespace T4;
|
||||
|
||||
namespace
|
||||
{
|
||||
void ConfigureCompilers(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath) {}
|
||||
void ConfigureCompilers(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath)
|
||||
{
|
||||
auto& memory = zone.Memory();
|
||||
|
||||
collection.AddAssetCreator(font::CreateCompilerT4(memory, searchPath));
|
||||
}
|
||||
|
||||
void ConfigurePostProcessors(AssetCreatorCollection& collection,
|
||||
Zone& zone,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ObjCompilerT5.h"
|
||||
|
||||
#include "Game/T5/Font/FontCompilerT5.h"
|
||||
#include "Game/T5/T5.h"
|
||||
#include "Game/T5/Techset/TechniqueCompilerT5.h"
|
||||
#include "Game/T5/Techset/TechsetCompilerT5.h"
|
||||
@@ -17,6 +18,7 @@ namespace
|
||||
auto& memory = zone.Memory();
|
||||
|
||||
collection.AddAssetCreator(techset::CreateTechsetCompilerT5(memory, searchPath));
|
||||
collection.AddAssetCreator(font::CreateCompilerT5(memory, searchPath));
|
||||
|
||||
collection.AddSubAssetCreator(techset::CreateTechniqueCompilerT5(memory, zone, searchPath));
|
||||
collection.AddSubAssetCreator(techset::CreateVertexDeclCompilerT5(memory));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ObjCompilerT6.h"
|
||||
|
||||
#include "Game/T6/Font/FontCompilerT6.h"
|
||||
#include "Game/T6/T6.h"
|
||||
#include "Game/T6/Techset/TechniqueCompilerT6.h"
|
||||
#include "Game/T6/Techset/TechsetCompilerT6.h"
|
||||
@@ -24,6 +25,7 @@ namespace
|
||||
|
||||
collection.AddAssetCreator(key_value_pairs::CreateCompilerT6(memory, zone, zoneDefinition.m_zone_definition, zoneStates));
|
||||
collection.AddAssetCreator(techset::CreateTechsetCompilerT6(memory, searchPath));
|
||||
collection.AddAssetCreator(font::CreateCompilerT6(memory, searchPath));
|
||||
|
||||
collection.AddSubAssetCreator(techset::CreateTechniqueCompilerT6(memory, zone, searchPath));
|
||||
collection.AddSubAssetCreator(techset::CreateVertexDeclCompilerT6(memory));
|
||||
|
||||
@@ -115,7 +115,10 @@ namespace image
|
||||
}
|
||||
|
||||
TextureConverter::TextureConverter(const Texture* inputTexture, const ImageFormat* targetFormat)
|
||||
: m_input_texture(inputTexture),
|
||||
: m_fill_r(false),
|
||||
m_fill_g(false),
|
||||
m_fill_b(false),
|
||||
m_input_texture(inputTexture),
|
||||
m_output_texture(nullptr),
|
||||
m_input_format(inputTexture->GetFormat()),
|
||||
m_output_format(targetFormat)
|
||||
@@ -158,10 +161,13 @@ namespace image
|
||||
const auto gInputMask = inputFormat->HasG() ? Mask1(inputFormat->m_g_size) << inputFormat->m_g_offset : 0;
|
||||
const auto bInputMask = inputFormat->HasB() ? Mask1(inputFormat->m_b_size) << inputFormat->m_b_offset : 0;
|
||||
const auto aInputMask = inputFormat->HasA() ? Mask1(inputFormat->m_a_size) << inputFormat->m_a_offset : 0;
|
||||
const auto rFill = m_fill_r ? Mask1(outputFormat->m_r_size) << outputFormat->m_r_offset : 0;
|
||||
const auto gFill = m_fill_g ? Mask1(outputFormat->m_g_size) << outputFormat->m_g_offset : 0;
|
||||
const auto bFill = m_fill_b ? Mask1(outputFormat->m_b_size) << outputFormat->m_b_offset : 0;
|
||||
const auto aOutputMask = outputFormat->HasA() ? Mask1(outputFormat->m_a_size) << outputFormat->m_a_offset : 0;
|
||||
const bool rConvert = rInputMask != 0 && outputFormat->m_r_size > 0;
|
||||
const bool gConvert = gInputMask != 0 && outputFormat->m_g_size > 0;
|
||||
const bool bConvert = bInputMask != 0 && outputFormat->m_b_size > 0;
|
||||
const bool rConvert = (rInputMask != 0 || rFill) && outputFormat->m_r_size > 0;
|
||||
const bool gConvert = (gInputMask != 0 || gFill) && outputFormat->m_g_size > 0;
|
||||
const bool bConvert = (bInputMask != 0 || bFill) && outputFormat->m_b_size > 0;
|
||||
|
||||
// alpha has a default of 1 so we need to convert even input has no alpha
|
||||
const bool aConvert = outputFormat->m_a_size > 0;
|
||||
@@ -182,11 +188,11 @@ namespace image
|
||||
const auto inPixel = m_read_pixel_func(&inputBuffer[inputOffset], inputFormat->m_bits_per_pixel);
|
||||
|
||||
if (rConvert)
|
||||
outPixel |= (inPixel & rInputMask) >> inputFormat->m_r_offset << outputFormat->m_r_offset;
|
||||
outPixel |= ((inPixel & rInputMask) >> inputFormat->m_r_offset << outputFormat->m_r_offset) | rFill;
|
||||
if (gConvert)
|
||||
outPixel |= (inPixel & gInputMask) >> inputFormat->m_g_offset << outputFormat->m_g_offset;
|
||||
outPixel |= ((inPixel & gInputMask) >> inputFormat->m_g_offset << outputFormat->m_g_offset) | gFill;
|
||||
if (bConvert)
|
||||
outPixel |= (inPixel & bInputMask) >> inputFormat->m_b_offset << outputFormat->m_b_offset;
|
||||
outPixel |= ((inPixel & bInputMask) >> inputFormat->m_b_offset << outputFormat->m_b_offset) | bFill;
|
||||
|
||||
if (aConvert)
|
||||
{
|
||||
@@ -222,6 +228,13 @@ namespace image
|
||||
}
|
||||
}
|
||||
|
||||
void TextureConverter::SetColorFill(const bool fillR, const bool fillG, const bool fillB)
|
||||
{
|
||||
m_fill_r = fillR;
|
||||
m_fill_g = fillG;
|
||||
m_fill_b = fillB;
|
||||
}
|
||||
|
||||
std::unique_ptr<Texture> TextureConverter::Convert()
|
||||
{
|
||||
CreateOutputTexture();
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace image
|
||||
public:
|
||||
TextureConverter(const Texture* inputTexture, const ImageFormat* targetFormat);
|
||||
|
||||
void SetColorFill(bool fillR, bool fillG, bool fillB);
|
||||
|
||||
std::unique_ptr<Texture> Convert();
|
||||
|
||||
private:
|
||||
@@ -25,6 +27,13 @@ namespace image
|
||||
std::function<uint64_t(const void* offset, unsigned bitCount)> m_read_pixel_func;
|
||||
std::function<void(void* offset, uint64_t pixel, unsigned bitCount)> m_write_pixel_func;
|
||||
|
||||
struct
|
||||
{
|
||||
bool m_fill_r : 1;
|
||||
bool m_fill_g : 1;
|
||||
bool m_fill_b : 1;
|
||||
};
|
||||
|
||||
const Texture* m_input_texture;
|
||||
std::unique_ptr<Texture> m_output_texture;
|
||||
const ImageFormat* m_input_format;
|
||||
|
||||
@@ -238,7 +238,10 @@ namespace
|
||||
}
|
||||
|
||||
const auto jFont = jRoot.get<JsonFont>();
|
||||
if (CreateFontFromJson(jFont, *font, registration, context))
|
||||
if (!std::holds_alternative<JsonCompiledFont>(jFont.font))
|
||||
return AssetCreationResult::NoAction();
|
||||
|
||||
if (CreateFontFromJson(std::get<JsonCompiledFont>(jFont.font), *font, registration, context))
|
||||
return AssetCreationResult::Success(context.AddAsset(std::move(registration)));
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
@@ -250,7 +253,7 @@ namespace
|
||||
}
|
||||
|
||||
private:
|
||||
bool CreateFontFromJson(const JsonFont& jFont, Font_s& font, AssetRegistration<AssetFont>& registration, AssetCreationContext& context) const
|
||||
bool CreateFontFromJson(const JsonCompiledFont& jFont, Font_s& font, AssetRegistration<AssetFont>& registration, AssetCreationContext& context) const
|
||||
{
|
||||
font.pixelHeight = static_cast<decltype(Font_s::pixelHeight)>(jFont.pixelHeight);
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace
|
||||
}
|
||||
#endif
|
||||
|
||||
void CreateJsonFont(JsonFont& jFont, const Font_s& font)
|
||||
void CreateCompiledJsonFont(JsonCompiledFont& jFont, const Font_s& font)
|
||||
{
|
||||
jFont.pixelHeight = font.pixelHeight;
|
||||
|
||||
@@ -125,7 +125,8 @@ namespace
|
||||
void Dump(const Font_s& font) const
|
||||
{
|
||||
JsonFont jFont;
|
||||
CreateJsonFont(jFont, font);
|
||||
jFont.font.emplace<JsonCompiledFont>();
|
||||
CreateCompiledJsonFont(std::get<JsonCompiledFont>(jFont.font), font);
|
||||
|
||||
ordered_json jRoot;
|
||||
jRoot["$schema"] = "http://openassettools.dev/schema/font.v1.json";
|
||||
|
||||
+1
Submodule thirdparty/stb added at 31c1ad3745
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
stb = {}
|
||||
|
||||
function stb:include(includes)
|
||||
if includes:handle(self:name()) then
|
||||
includedirs {
|
||||
path.join(ThirdPartyFolder(), "stb")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function stb:link(links)
|
||||
links:add(self:name())
|
||||
end
|
||||
|
||||
function stb:use()
|
||||
|
||||
end
|
||||
|
||||
function stb:name()
|
||||
return "stb"
|
||||
end
|
||||
|
||||
function stb:project()
|
||||
local folder = ThirdPartyFolder()
|
||||
local includes = Includes:create()
|
||||
|
||||
project(self:name())
|
||||
targetdir(TargetDirectoryLib)
|
||||
location "%{wks.location}/thirdparty/%{prj.name}"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
|
||||
files {
|
||||
path.join(folder, "stb/*.h"),
|
||||
path.join(folder, "stb_impl/*.cpp")
|
||||
}
|
||||
|
||||
self:include(includes)
|
||||
|
||||
-- Disable warnings. They do not have any value to us since it is not our code.
|
||||
warnings "off"
|
||||
end
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "stb_truetype.h"
|
||||
Reference in New Issue
Block a user