589 lines
16 KiB
C++
589 lines
16 KiB
C++
|
|
#include "msdf_font_builder.h"
|
||
|
|
|
||
|
|
#include <msdfgen/msdfgen.h>
|
||
|
|
|
||
|
|
#include <ft2build.h>
|
||
|
|
#include FT_FREETYPE_H
|
||
|
|
#include FT_OUTLINE_H
|
||
|
|
|
||
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||
|
|
#include <stb_image_write.h>
|
||
|
|
|
||
|
|
#include <fstream>
|
||
|
|
#include <sstream>
|
||
|
|
#include <algorithm>
|
||
|
|
#include <cmath>
|
||
|
|
#include <map>
|
||
|
|
|
||
|
|
namespace extra2d {
|
||
|
|
namespace tools {
|
||
|
|
|
||
|
|
using namespace msdfgen;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Freetype 轮廓转换上下文
|
||
|
|
*/
|
||
|
|
struct OutlineContext {
|
||
|
|
Shape* shape;
|
||
|
|
Contour* currentContour;
|
||
|
|
Point2 lastPoint;
|
||
|
|
bool hasLastPoint;
|
||
|
|
std::map<char32_t, double> advanceMap;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief MSDF 字体构建器实现
|
||
|
|
*/
|
||
|
|
struct MSDFFontBuilder::Impl {
|
||
|
|
FT_Library ftLibrary = nullptr;
|
||
|
|
FT_Face ftFace = nullptr;
|
||
|
|
OutlineContext outlineCtx;
|
||
|
|
|
||
|
|
struct GlyphBitmap {
|
||
|
|
char32_t codepoint;
|
||
|
|
Bitmap<float, 3> bitmap;
|
||
|
|
double advance;
|
||
|
|
double l, b, r, t;
|
||
|
|
double pl, pb, pr, pt;
|
||
|
|
};
|
||
|
|
|
||
|
|
std::vector<GlyphBitmap> glyphBitmaps;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Freetype 移动到点回调函数
|
||
|
|
*/
|
||
|
|
static int ftMoveTo(const FT_Vector* to, void* user) {
|
||
|
|
OutlineContext* ctx = static_cast<OutlineContext*>(user);
|
||
|
|
ctx->currentContour = &ctx->shape->addContour();
|
||
|
|
ctx->lastPoint = Point2(to->x / 64.0, to->y / 64.0);
|
||
|
|
ctx->hasLastPoint = true;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Freetype 画线回调函数
|
||
|
|
*/
|
||
|
|
static int ftLineTo(const FT_Vector* to, void* user) {
|
||
|
|
OutlineContext* ctx = static_cast<OutlineContext*>(user);
|
||
|
|
if (ctx->currentContour && ctx->hasLastPoint) {
|
||
|
|
Point2 endPoint(to->x / 64.0, to->y / 64.0);
|
||
|
|
ctx->currentContour->addEdge(EdgeHolder(ctx->lastPoint, endPoint));
|
||
|
|
ctx->lastPoint = endPoint;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Freetype 二次贝塞尔曲线回调函数
|
||
|
|
*/
|
||
|
|
static int ftConicTo(const FT_Vector* control, const FT_Vector* to, void* user) {
|
||
|
|
OutlineContext* ctx = static_cast<OutlineContext*>(user);
|
||
|
|
if (ctx->currentContour && ctx->hasLastPoint) {
|
||
|
|
Point2 controlPoint(control->x / 64.0, control->y / 64.0);
|
||
|
|
Point2 endPoint(to->x / 64.0, to->y / 64.0);
|
||
|
|
ctx->currentContour->addEdge(EdgeHolder(ctx->lastPoint, controlPoint, endPoint));
|
||
|
|
ctx->lastPoint = endPoint;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Freetype 三次贝塞尔曲线回调函数
|
||
|
|
*/
|
||
|
|
static int ftCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user) {
|
||
|
|
OutlineContext* ctx = static_cast<OutlineContext*>(user);
|
||
|
|
if (ctx->currentContour && ctx->hasLastPoint) {
|
||
|
|
Point2 cp1(control1->x / 64.0, control1->y / 64.0);
|
||
|
|
Point2 cp2(control2->x / 64.0, control2->y / 64.0);
|
||
|
|
Point2 endPoint(to->x / 64.0, to->y / 64.0);
|
||
|
|
ctx->currentContour->addEdge(EdgeHolder(ctx->lastPoint, cp1, cp2, endPoint));
|
||
|
|
ctx->lastPoint = endPoint;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
MSDFFontBuilder::MSDFFontBuilder() : impl_(new Impl()) {
|
||
|
|
}
|
||
|
|
|
||
|
|
MSDFFontBuilder::~MSDFFontBuilder() {
|
||
|
|
if (impl_) {
|
||
|
|
if (impl_->ftFace) {
|
||
|
|
FT_Done_Face(impl_->ftFace);
|
||
|
|
}
|
||
|
|
if (impl_->ftLibrary) {
|
||
|
|
FT_Done_FreeType(impl_->ftLibrary);
|
||
|
|
}
|
||
|
|
delete impl_;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MSDFFontBuilder::setInputFont(const std::string& path) {
|
||
|
|
inputFont_ = path;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MSDFFontBuilder::setOutputPath(const std::string& path) {
|
||
|
|
outputPath_ = path;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MSDFFontBuilder::setCharset(const std::string& charset) {
|
||
|
|
charset_ = charset;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MSDFFontBuilder::setCharsetFile(const std::string& path) {
|
||
|
|
charsetFile_ = path;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MSDFFontBuilder::setFontSize(int size) {
|
||
|
|
fontSize_ = size;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MSDFFontBuilder::setPxRange(float range) {
|
||
|
|
pxRange_ = range;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MSDFFontBuilder::setAtlasSize(int width, int height) {
|
||
|
|
atlasWidth_ = width;
|
||
|
|
atlasHeight_ = height;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MSDFFontBuilder::build() {
|
||
|
|
if (inputFont_.empty()) {
|
||
|
|
error_ = "Input font path not set";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (outputPath_.empty()) {
|
||
|
|
error_ = "Output path not set";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!loadCharset()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
FT_Error error = FT_Init_FreeType(&impl_->ftLibrary);
|
||
|
|
if (error) {
|
||
|
|
error_ = "Failed to initialize FreeType";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
error = FT_New_Face(impl_->ftLibrary, inputFont_.c_str(), 0, &impl_->ftFace);
|
||
|
|
if (error) {
|
||
|
|
error_ = "Failed to load font: " + inputFont_;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
error = FT_Set_Char_Size(impl_->ftFace, fontSize_ * 64, fontSize_ * 64, 72, 72);
|
||
|
|
if (error) {
|
||
|
|
error_ = "Failed to set font size";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!generateAllGlyphs()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!packGlyphs()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!savePngWithMetadata()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MSDFFontBuilder::loadCharset() {
|
||
|
|
if (!charsetFile_.empty()) {
|
||
|
|
std::ifstream file(charsetFile_);
|
||
|
|
if (!file) {
|
||
|
|
error_ = "Failed to open charset file: " + charsetFile_;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
std::stringstream buffer;
|
||
|
|
buffer << file.rdbuf();
|
||
|
|
charset_ = buffer.str();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (charset_.empty()) {
|
||
|
|
charset_ = " !\"#$%&'()*+,-./0123456789:;<=>?@"
|
||
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
|
||
|
|
"abcdefghijklmnopqrstuvwxyz{|}~";
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief 从 Freetype 字形加载 msdfgen Shape
|
||
|
|
*/
|
||
|
|
static bool loadGlyphShape(FT_Face face, char32_t codepoint, Shape& shape, double& advance) {
|
||
|
|
FT_UInt glyphIndex = FT_Get_Char_Index(face, codepoint);
|
||
|
|
if (glyphIndex == 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
FT_Error error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_NO_SCALE);
|
||
|
|
if (error) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
advance = face->glyph->metrics.horiAdvance / 64.0;
|
||
|
|
|
||
|
|
FT_Outline* outline = &face->glyph->outline;
|
||
|
|
if (outline->n_contours == 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
OutlineContext ctx;
|
||
|
|
ctx.shape = &shape;
|
||
|
|
ctx.currentContour = nullptr;
|
||
|
|
ctx.hasLastPoint = false;
|
||
|
|
|
||
|
|
FT_Outline_Funcs funcs;
|
||
|
|
funcs.move_to = ftMoveTo;
|
||
|
|
funcs.line_to = ftLineTo;
|
||
|
|
funcs.conic_to = ftConicTo;
|
||
|
|
funcs.cubic_to = ftCubicTo;
|
||
|
|
funcs.shift = 0;
|
||
|
|
funcs.delta = 0;
|
||
|
|
|
||
|
|
error = FT_Outline_Decompose(outline, &funcs, &ctx);
|
||
|
|
if (error) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MSDFFontBuilder::generateAllGlyphs() {
|
||
|
|
impl_->glyphBitmaps.clear();
|
||
|
|
|
||
|
|
double unitsPerEM = impl_->ftFace->units_per_EM;
|
||
|
|
if (unitsPerEM == 0) {
|
||
|
|
unitsPerEM = 1000.0;
|
||
|
|
}
|
||
|
|
double scale = fontSize_ / unitsPerEM;
|
||
|
|
|
||
|
|
for (char c : charset_) {
|
||
|
|
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||
|
|
|
||
|
|
Shape shape;
|
||
|
|
double glyphAdvance = 0.0;
|
||
|
|
if (!loadGlyphShape(impl_->ftFace, codepoint, shape, glyphAdvance)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
shape.normalize();
|
||
|
|
|
||
|
|
edgeColoringSimple(shape, 3.0);
|
||
|
|
|
||
|
|
Shape::Bounds bounds = shape.getBounds();
|
||
|
|
double l = bounds.l;
|
||
|
|
double b = bounds.b;
|
||
|
|
double r = bounds.r;
|
||
|
|
double t = bounds.t;
|
||
|
|
|
||
|
|
if (l >= r || b >= t) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
double pl = l * scale;
|
||
|
|
double pb = b * scale;
|
||
|
|
double pr = r * scale;
|
||
|
|
double pt = t * scale;
|
||
|
|
|
||
|
|
double range = pxRange_ / scale;
|
||
|
|
l -= range;
|
||
|
|
b -= range;
|
||
|
|
r += range;
|
||
|
|
t += range;
|
||
|
|
|
||
|
|
double width = (r - l) * scale;
|
||
|
|
double height = (t - b) * scale;
|
||
|
|
|
||
|
|
int w = static_cast<int>(std::ceil(width));
|
||
|
|
int h = static_cast<int>(std::ceil(height));
|
||
|
|
|
||
|
|
w = std::max(w, 1);
|
||
|
|
h = std::max(h, 1);
|
||
|
|
|
||
|
|
Impl::GlyphBitmap gb;
|
||
|
|
gb.codepoint = codepoint;
|
||
|
|
gb.advance = glyphAdvance * scale;
|
||
|
|
gb.l = l;
|
||
|
|
gb.b = b;
|
||
|
|
gb.r = r;
|
||
|
|
gb.t = t;
|
||
|
|
gb.pl = pl;
|
||
|
|
gb.pb = pb;
|
||
|
|
gb.pr = pr;
|
||
|
|
gb.pt = pt;
|
||
|
|
|
||
|
|
gb.bitmap = Bitmap<float, 3>(w, h);
|
||
|
|
|
||
|
|
MSDFGeneratorConfig config;
|
||
|
|
config.overlapSupport = true;
|
||
|
|
|
||
|
|
Projection projection(Vector2(scale, scale), Vector2(-l * scale, -b * scale));
|
||
|
|
Range distanceRange(range * scale);
|
||
|
|
generateMSDF(gb.bitmap, shape, projection, distanceRange, config);
|
||
|
|
|
||
|
|
impl_->glyphBitmaps.push_back(std::move(gb));
|
||
|
|
}
|
||
|
|
|
||
|
|
return !impl_->glyphBitmaps.empty();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MSDFFontBuilder::packGlyphs() {
|
||
|
|
std::vector<int> positions(impl_->glyphBitmaps.size() * 2, 0);
|
||
|
|
|
||
|
|
std::vector<std::pair<int, int>> sizes;
|
||
|
|
for (const auto& gb : impl_->glyphBitmaps) {
|
||
|
|
sizes.emplace_back(gb.bitmap.width(), gb.bitmap.height());
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<int> indices(sizes.size());
|
||
|
|
for (size_t i = 0; i < indices.size(); ++i) {
|
||
|
|
indices[i] = static_cast<int>(i);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::sort(indices.begin(), indices.end(), [&](int a, int b) {
|
||
|
|
return sizes[a].second > sizes[b].second;
|
||
|
|
});
|
||
|
|
|
||
|
|
int x = 0, y = 0;
|
||
|
|
int rowHeight = 0;
|
||
|
|
|
||
|
|
for (int idx : indices) {
|
||
|
|
int w = sizes[idx].first;
|
||
|
|
int h = sizes[idx].second;
|
||
|
|
|
||
|
|
if (x + w > atlasWidth_) {
|
||
|
|
x = 0;
|
||
|
|
y += rowHeight;
|
||
|
|
rowHeight = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (y + h > atlasHeight_) {
|
||
|
|
error_ = "Atlas size too small for all glyphs";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
positions[idx * 2] = x;
|
||
|
|
positions[idx * 2 + 1] = y;
|
||
|
|
|
||
|
|
x += w;
|
||
|
|
rowHeight = std::max(rowHeight, h);
|
||
|
|
}
|
||
|
|
|
||
|
|
Bitmap<float, 3> atlas(atlasWidth_, atlasHeight_);
|
||
|
|
|
||
|
|
for (int py = 0; py < atlasHeight_; ++py) {
|
||
|
|
for (int px = 0; px < atlasWidth_; ++px) {
|
||
|
|
float* pixel = atlas(px, py);
|
||
|
|
pixel[0] = 0.0f;
|
||
|
|
pixel[1] = 0.0f;
|
||
|
|
pixel[2] = 0.0f;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (size_t i = 0; i < impl_->glyphBitmaps.size(); ++i) {
|
||
|
|
const auto& gb = impl_->glyphBitmaps[i];
|
||
|
|
int px = positions[i * 2];
|
||
|
|
int py = positions[i * 2 + 1];
|
||
|
|
|
||
|
|
for (int gy = 0; gy < gb.bitmap.height(); ++gy) {
|
||
|
|
for (int gx = 0; gx < gb.bitmap.width(); ++gx) {
|
||
|
|
int targetY = atlasHeight_ - 1 - (py + gy);
|
||
|
|
float* pixel = atlas(px + gx, targetY);
|
||
|
|
const float* srcPixel = gb.bitmap(gx, gy);
|
||
|
|
pixel[0] = srcPixel[0];
|
||
|
|
pixel[1] = srcPixel[1];
|
||
|
|
pixel[2] = srcPixel[2];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
GlyphData glyph;
|
||
|
|
glyph.codepoint = gb.codepoint;
|
||
|
|
glyph.advance = static_cast<float>(gb.advance);
|
||
|
|
|
||
|
|
glyph.left = static_cast<double>(px);
|
||
|
|
glyph.top = static_cast<double>(atlasHeight_ - py - gb.bitmap.height());
|
||
|
|
glyph.right = static_cast<double>(px + gb.bitmap.width());
|
||
|
|
glyph.bottom = static_cast<double>(atlasHeight_ - py);
|
||
|
|
|
||
|
|
glyph.uvMin.x = static_cast<float>(glyph.left / atlasWidth_);
|
||
|
|
glyph.uvMin.y = static_cast<float>(glyph.top / atlasHeight_);
|
||
|
|
glyph.uvMax.x = static_cast<float>(glyph.right / atlasWidth_);
|
||
|
|
glyph.uvMax.y = static_cast<float>(glyph.bottom / atlasHeight_);
|
||
|
|
|
||
|
|
glyph.size.x = static_cast<float>(gb.pr - gb.pl);
|
||
|
|
glyph.size.y = static_cast<float>(gb.pt - gb.pb);
|
||
|
|
glyph.bearing.x = static_cast<float>(gb.pl);
|
||
|
|
glyph.bearing.y = static_cast<float>(gb.pt);
|
||
|
|
|
||
|
|
metadata_.glyphs.push_back(glyph);
|
||
|
|
}
|
||
|
|
|
||
|
|
metadata_.fontSize = fontSize_;
|
||
|
|
metadata_.pxRange = pxRange_;
|
||
|
|
metadata_.atlasWidth = atlasWidth_;
|
||
|
|
metadata_.atlasHeight = atlasHeight_;
|
||
|
|
metadata_.lineHeight = static_cast<int>(fontSize_ * 1.25);
|
||
|
|
metadata_.baseline = static_cast<int>(fontSize_ * 0.25);
|
||
|
|
|
||
|
|
impl_->glyphBitmaps.clear();
|
||
|
|
impl_->glyphBitmaps.push_back({0, std::move(atlas), 0, 0, 0, 0, 0, 0, 0, 0, 0});
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MSDFFontBuilder::savePngWithMetadata() {
|
||
|
|
if (impl_->glyphBitmaps.empty()) {
|
||
|
|
error_ = "No atlas generated";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const auto& atlas = impl_->glyphBitmaps[0].bitmap;
|
||
|
|
|
||
|
|
std::vector<uint8_t> rgbaData(atlasWidth_ * atlasHeight_ * 4);
|
||
|
|
for (int y = 0; y < atlasHeight_; ++y) {
|
||
|
|
for (int x = 0; x < atlasWidth_; ++x) {
|
||
|
|
const float* pixel = atlas(x, y);
|
||
|
|
int idx = (y * atlasWidth_ + x) * 4;
|
||
|
|
rgbaData[idx + 0] = static_cast<uint8_t>(std::clamp(pixel[0] * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f);
|
||
|
|
rgbaData[idx + 1] = static_cast<uint8_t>(std::clamp(pixel[1] * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f);
|
||
|
|
rgbaData[idx + 2] = static_cast<uint8_t>(std::clamp(pixel[2] * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f);
|
||
|
|
rgbaData[idx + 3] = 255;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string jsonStr = generateMetadataJson();
|
||
|
|
std::vector<uint8_t> textChunk;
|
||
|
|
|
||
|
|
std::string keyword = "msdf";
|
||
|
|
textChunk.insert(textChunk.end(), keyword.begin(), keyword.end());
|
||
|
|
textChunk.push_back(0);
|
||
|
|
textChunk.insert(textChunk.end(), jsonStr.begin(), jsonStr.end());
|
||
|
|
|
||
|
|
uint32_t length = static_cast<uint32_t>(textChunk.size());
|
||
|
|
std::vector<uint8_t> lengthBytes(4);
|
||
|
|
lengthBytes[0] = (length >> 24) & 0xFF;
|
||
|
|
lengthBytes[1] = (length >> 16) & 0xFF;
|
||
|
|
lengthBytes[2] = (length >> 8) & 0xFF;
|
||
|
|
lengthBytes[3] = length & 0xFF;
|
||
|
|
|
||
|
|
std::vector<uint8_t> crcData;
|
||
|
|
crcData.insert(crcData.end(), {'t', 'E', 'X', 't'});
|
||
|
|
crcData.insert(crcData.end(), textChunk.begin(), textChunk.end());
|
||
|
|
|
||
|
|
uint32_t crc = 0xFFFFFFFF;
|
||
|
|
for (uint8_t byte : crcData) {
|
||
|
|
crc ^= byte;
|
||
|
|
for (int i = 0; i < 8; ++i) {
|
||
|
|
crc = (crc >> 1) ^ ((crc & 1) ? 0xEDB88320 : 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
crc ^= 0xFFFFFFFF;
|
||
|
|
|
||
|
|
std::vector<uint8_t> crcBytes(4);
|
||
|
|
crcBytes[0] = (crc >> 24) & 0xFF;
|
||
|
|
crcBytes[1] = (crc >> 16) & 0xFF;
|
||
|
|
crcBytes[2] = (crc >> 8) & 0xFF;
|
||
|
|
crcBytes[3] = crc & 0xFF;
|
||
|
|
|
||
|
|
std::string tempPng = outputPath_ + ".tmp.png";
|
||
|
|
|
||
|
|
if (!stbi_write_png(tempPng.c_str(), atlasWidth_, atlasHeight_, 4, rgbaData.data(), atlasWidth_ * 4)) {
|
||
|
|
error_ = "Failed to save PNG: " + tempPng;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::ifstream inputFile(tempPng, std::ios::binary);
|
||
|
|
if (!inputFile) {
|
||
|
|
error_ = "Failed to open temporary PNG file";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<uint8_t> pngData((std::istreambuf_iterator<char>(inputFile)),
|
||
|
|
std::istreambuf_iterator<char>());
|
||
|
|
inputFile.close();
|
||
|
|
|
||
|
|
std::vector<uint8_t> newPng;
|
||
|
|
newPng.insert(newPng.end(), pngData.begin(), pngData.begin() + 8);
|
||
|
|
|
||
|
|
size_t pos = 8;
|
||
|
|
bool inserted = false;
|
||
|
|
while (pos < pngData.size()) {
|
||
|
|
if (pos + 8 > pngData.size()) break;
|
||
|
|
|
||
|
|
std::string chunkType(pngData.begin() + pos + 4, pngData.begin() + pos + 8);
|
||
|
|
|
||
|
|
if (!inserted && chunkType != "IHDR") {
|
||
|
|
newPng.insert(newPng.end(), lengthBytes.begin(), lengthBytes.end());
|
||
|
|
newPng.insert(newPng.end(), {'t', 'E', 'X', 't'});
|
||
|
|
newPng.insert(newPng.end(), textChunk.begin(), textChunk.end());
|
||
|
|
newPng.insert(newPng.end(), crcBytes.begin(), crcBytes.end());
|
||
|
|
inserted = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint32_t chunkLen = (static_cast<uint32_t>(pngData[pos]) << 24) |
|
||
|
|
(static_cast<uint32_t>(pngData[pos + 1]) << 16) |
|
||
|
|
(static_cast<uint32_t>(pngData[pos + 2]) << 8) |
|
||
|
|
static_cast<uint32_t>(pngData[pos + 3]);
|
||
|
|
|
||
|
|
size_t chunkEnd = pos + 12 + chunkLen;
|
||
|
|
newPng.insert(newPng.end(), pngData.begin() + pos, pngData.begin() + chunkEnd);
|
||
|
|
pos = chunkEnd;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::ofstream outputFile(outputPath_, std::ios::binary);
|
||
|
|
if (!outputFile) {
|
||
|
|
error_ = "Failed to write PNG file: " + outputPath_;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
outputFile.write(reinterpret_cast<const char*>(newPng.data()), newPng.size());
|
||
|
|
|
||
|
|
std::remove(tempPng.c_str());
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string MSDFFontBuilder::generateMetadataJson() {
|
||
|
|
nlohmann::json j;
|
||
|
|
|
||
|
|
j["size"] = metadata_.fontSize;
|
||
|
|
j["pxRange"] = metadata_.pxRange;
|
||
|
|
j["lineHeight"] = metadata_.lineHeight;
|
||
|
|
j["baseline"] = metadata_.baseline;
|
||
|
|
|
||
|
|
j["atlas"]["width"] = metadata_.atlasWidth;
|
||
|
|
j["atlas"]["height"] = metadata_.atlasHeight;
|
||
|
|
|
||
|
|
for (const auto& glyph : metadata_.glyphs) {
|
||
|
|
nlohmann::json glyphJson;
|
||
|
|
glyphJson["unicode"] = static_cast<uint32_t>(glyph.codepoint);
|
||
|
|
glyphJson["advance"] = glyph.advance;
|
||
|
|
|
||
|
|
glyphJson["atlasBounds"]["left"] = glyph.left;
|
||
|
|
glyphJson["atlasBounds"]["bottom"] = glyph.bottom;
|
||
|
|
glyphJson["atlasBounds"]["right"] = glyph.right;
|
||
|
|
glyphJson["atlasBounds"]["top"] = glyph.top;
|
||
|
|
|
||
|
|
glyphJson["planeBounds"]["left"] = glyph.bearing.x;
|
||
|
|
glyphJson["planeBounds"]["bottom"] = glyph.bearing.y - glyph.size.y;
|
||
|
|
glyphJson["planeBounds"]["right"] = glyph.bearing.x + glyph.size.x;
|
||
|
|
glyphJson["planeBounds"]["top"] = glyph.bearing.y;
|
||
|
|
|
||
|
|
j["glyphs"].push_back(glyphJson);
|
||
|
|
}
|
||
|
|
|
||
|
|
return j.dump();
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace tools
|
||
|
|
} // namespace extra2d
|