CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/ext/native/tools/atlastool.cpp
Views: 1401
// Sprite packing method borrowed from glorp engine and heavily modified.1// For license safety, just run this as a build tool, don't build it into your game/program.2// https://github.com/zorbathut/glorp34// Horrible build instructions:5// * Download freetype, put in ppsspp/ext as freetype/6// * Open tools.sln7// * In Code Generation on freetype, change from Multithreaded DLL to Multithreaded.8// * Build9// * Move exe file to ext/native/tools/build1011// data we need to provide:12// sx, sy13// dx, dy14// ox, oy15// wx1617// line height18// dist-per-pixel1920#include <assert.h>21#include <libpng17/png.h>22#include <ft2build.h>23#include <freetype/ftbitmap.h>24#include <set>25#include <map>26#include <vector>27#include <algorithm>28#include <string>29#include <cmath>30#include <zstd.h>3132#include "Common/Render/TextureAtlas.h"3334#include "Common/Data/Format/PNGLoad.h"35#include "Common/Data/Format/ZIMSave.h"3637#include "kanjifilter.h"38// extracted only JIS Kanji on the CJK Unified Ideographs of UCS2. Cannot reading BlockAllocator. (texture size over)39//#define USE_KANJI KANJI_STANDARD | KANJI_RARELY_USED | KANJI_LEVEL440// daily-use character only. However, it is too enough this.41//#define USE_KANJI KANJI_STANDARD (texture size over)42// Shift-JIS filtering. (texture size over)43//#define USE_KANJI KANJI_SJIS_L1 | KANJI_SJIS_L244// more conpact daily-use character. but, not enough this.45// if when you find the unintelligible sequence of characters,46// add kanjiFilter Array with KANJI_LEARNING_ORDER_ADDTIONAL.47#define USE_KANJI KANJI_LEARNING_ORDER_ALL4849#include "Common/Data/Encoding/Utf8.h"5051using namespace std;52static int global_id;53static bool highcolor = false; // Low color mode is used by PPGE, can't delete.5455typedef unsigned short u16;5657struct CharRange : public AtlasCharRange {58std::set<u16> filter;59};6061enum class Effect {62FX_COPY = 0,63FX_RED_TO_ALPHA_SOLID_WHITE = 1, // for alpha fonts64FX_RED_TO_INTENSITY_ALPHA_255 = 2,65FX_PREMULTIPLY_ALPHA = 3,66FX_PINK_TO_ALPHA = 4, // for alpha fonts67FX_INVALID = 5,68};6970const char *effect_str[5] = {71"copy", "r2a", "r2i", "pre", "p2a",72};7374Effect GetEffect(const char *text) {75for (int i = 0; i < 5; i++) {76if (!strcmp(text, effect_str[i])) {77return (Effect)i;78}79}80return Effect::FX_INVALID;81}8283struct FontReference {84FontReference(string name, string file, vector<CharRange> ranges, int pixheight, float vertOffset)85: name_(name), file_(file), ranges_(ranges), size_(pixheight), vertOffset_(vertOffset) {86}8788string name_;89string file_;90vector<CharRange> ranges_;91int size_;92float vertOffset_;93};9495typedef vector<FontReference> FontReferenceList;9697template<class T>98struct Image {99vector<vector<T> > dat;100void resize(int x, int y) {101dat.resize(y);102for (int i = 0; i < y; i++)103dat[i].resize(x);104}105int width() const {106return (int)dat[0].size();107}108int height() const {109return (int)dat.size();110}111void copyfrom(const Image &img, int ox, int oy, Effect effect) {112assert(img.dat[0].size() + ox <= dat[0].size());113assert(img.dat.size() + oy <= dat.size());114for (int y = 0; y < (int)img.dat.size(); y++) {115for (int x = 0; x < (int)img.dat[y].size(); x++) {116switch (effect) {117case Effect::FX_COPY:118dat[y + oy][ox + x] = img.dat[y][x];119break;120case Effect::FX_RED_TO_ALPHA_SOLID_WHITE:121dat[y + oy][ox + x] = 0x00FFFFFF | (img.dat[y][x] << 24);122break;123case Effect::FX_RED_TO_INTENSITY_ALPHA_255:124dat[y + oy][ox + x] = 0xFF000000 | img.dat[y][x] | (img.dat[y][x] << 8) | (img.dat[y][x] << 16);125break;126case Effect::FX_PREMULTIPLY_ALPHA:127{128unsigned int color = img.dat[y][x];129unsigned int a = color >> 24;130unsigned int r = (color & 0xFF) * a >> 8, g = (color & 0xFF00) * a >> 8, b = (color & 0xFF0000) * a >> 8;131color = (color & 0xFF000000) | (r & 0xFF) | (g & 0xFF00) | (b & 0xFF0000);132// Simulate 4444133color = color & 0xF0F0F0F0;134color |= color >> 4;135dat[y + oy][ox + x] = color;136break;137}138case Effect::FX_PINK_TO_ALPHA:139dat[y + oy][ox + x] = ((img.dat[y][x] & 0xFFFFFF) == 0xFF00FF) ? 0x00FFFFFF : (img.dat[y][x] | 0xFF000000);140break;141default:142dat[y + oy][ox + x] = 0xFFFF00FF;143break;144}145}146}147}148void set(int sx, int sy, int ex, int ey, unsigned char fil) {149for (int y = sy; y < ey; y++)150fill(dat[y].begin() + sx, dat[y].begin() + ex, fil);151}152bool LoadPNG(const char *png_name) {153unsigned char *img_data;154int w, h;155if (1 != pngLoad(png_name, &w, &h, &img_data)) {156printf("Failed to load %s\n", png_name);157exit(1);158return false;159}160dat.resize(h);161for (int y = 0; y < h; y++) {162dat[y].resize(w);163memcpy(&dat[y][0], img_data + 4 * y * w, 4 * w);164}165free(img_data);166return true;167}168void SavePNG(const char *png_name) {169// Save PNG170FILE *fil = fopen(png_name, "wb");171png_structp png_ptr;172png_infop info_ptr;173png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);174assert(png_ptr);175info_ptr = png_create_info_struct(png_ptr);176assert(info_ptr);177png_init_io(png_ptr, fil);178//png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);179png_set_IHDR(png_ptr, info_ptr, (uint32_t)dat[0].size(), (uint32_t)dat.size(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);180png_write_info(png_ptr, info_ptr);181for (int y = 0; y < (int)dat.size(); y++) {182png_write_row(png_ptr, (png_byte*)&dat[y][0]);183}184png_write_end(png_ptr, NULL);185png_destroy_write_struct(&png_ptr, &info_ptr);186}187void SaveZIM(const char *zim_name, int zim_format) {188uint8_t *image_data = new uint8_t[width() * height() * 4];189for (int y = 0; y < height(); y++) {190memcpy(image_data + y * width() * 4, &dat[y][0], width() * 4);191}192FILE *f = fopen(zim_name, "wb");193// SaveZIM takes ownership over image_data, there's no leak.194::SaveZIM(f, width(), height(), width() * 4, zim_format | ZIM_DITHER, image_data);195fclose(f);196}197};198199template<class S, class T>200bool operator<(const Image<S> &lhs, const Image<T> &rhs) {201return lhs.dat.size() * lhs.dat[0].size() > rhs.dat.size() * rhs.dat[0].size();202}203204struct Data {205// item ID206int id;207// dimensions of its spot in the world208int sx, sy, ex, ey;209// offset from the origin210float ox, oy;211float voffset; // to apply at the end212// distance to move the origin forward213float wx;214215int effect;216int charNum;217};218219bool operator<(const Data &lhs, const Data &rhs) {220return lhs.id < rhs.id; // should be unique221}222223string out_prefix;224225int NextPowerOf2(int x) {226int powof2 = 1;227// Double powof2 until >= val228while (powof2 < x) powof2 <<= 1;229return powof2;230}231232struct Bucket {233vector<pair<Image<unsigned int>, Data> > items;234void AddItem(const Image<unsigned int> &img, const Data &dat) {235items.push_back(make_pair(img, dat));236}237vector<Data> Resolve(int image_width, Image<unsigned int> &dest) {238// Place all the little images - whatever they are.239// Uses greedy fill algorithm. Slow but works surprisingly well, CPUs are fast.240Image<unsigned char> masq;241masq.resize(image_width, 1);242dest.resize(image_width, 1);243sort(items.begin(), items.end());244for (int i = 0; i < (int)items.size(); i++) {245if ((i + 1) % 2000 == 0) {246printf("Resolving (%i / %i)\n", i, (int)items.size());247}248int idx = (int)items[i].first.dat[0].size();249int idy = (int)items[i].first.dat.size();250if (idx > 1 && idy > 1) {251assert(idx <= image_width);252for (int ty = 0; ty < 2047; ty++) {253if (ty + idy + 1 > (int)dest.dat.size()) {254masq.resize(image_width, ty + idy + 16);255dest.resize(image_width, ty + idy + 16);256}257// Brute force packing.258int sz = (int)items[i].first.dat[0].size();259auto &masq_ty = masq.dat[ty];260auto &masq_idy = masq.dat[ty + idy - 1];261for (int tx = 0; tx < image_width - sz; tx++) {262bool valid = !(masq_ty[tx] || masq_idy[tx] || masq_ty[tx + idx - 1] || masq_idy[tx + idx - 1]);263if (valid) {264for (int ity = 0; ity < idy && valid; ity++) {265for (int itx = 0; itx < idx && valid; itx++) {266if (masq.dat[ty + ity][tx + itx]) {267goto skip;268}269}270}271dest.copyfrom(items[i].first, tx, ty, (Effect)items[i].second.effect);272masq.set(tx, ty, tx + idx + 1, ty + idy + 1, 255);273274items[i].second.sx = tx;275items[i].second.sy = ty;276277items[i].second.ex = tx + idx;278items[i].second.ey = ty + idy;279280// printf("Placed %d at %dx%d-%dx%d\n", items[i].second.id, tx, ty, tx + idx, ty + idy);281goto found;282}283skip:284;285}286}287found:288;289}290}291292if ((int)dest.dat.size() > image_width * 2) {293printf("PACKING FAIL : height=%i", (int)dest.dat.size());294exit(1);295}296dest.resize(image_width, (int)dest.dat.size());297298// Output the glyph data.299vector<Data> dats;300for (int i = 0; i < (int)items.size(); i++)301dats.push_back(items[i].second);302return dats;303}304};305306const int supersample = 16;307const int distmult = 64 * 3; // this is "one pixel in the final version equals 64 difference". reduce this number to increase the "blur" radius, increase it to make things "sharper"308const int maxsearch = (128 * supersample + distmult - 1) / distmult;309310struct Closest {311FT_Bitmap bmp;312Closest(FT_Bitmap bmp) : bmp(bmp) { }313float find_closest(int x, int y, char search) {314int best = 1 << 30;315for (int i = 1; i <= maxsearch; i++) {316if (i * i >= best)317break;318for (int f = -i; f < i; f++) {319int dist = i * i + f * f;320if (dist >= best) continue;321if (safe_access(x + i, y + f) == search || safe_access(x - f, y + i) == search || safe_access(x - i, y - f) == search || safe_access(x + f, y - i) == search)322best = dist;323}324}325return sqrt((float)best);326}327char safe_access(int x, int y) {328if (x < 0 || y < 0 || x >= (int)bmp.width || y >= (int)bmp.rows)329return 0;330return bmp.buffer[x + y * bmp.width];331}332};333334typedef vector<FT_Face> FT_Face_List;335336inline vector<CharRange> merge(const vector<CharRange> &a, const vector<CharRange> &b) {337vector<CharRange> result = a;338for (size_t i = 0, in = b.size(); i < in; ++i) {339bool insert = true;340for (size_t j = 0, jn = a.size(); j < jn; ++j) {341// Should never overlap, so same start is always a duplicate.342if (b[i].start == a[j].start) {343insert = false;344break;345}346}347348if (insert) {349result.push_back(b[i]);350}351}352353return result;354}355356void RasterizeFonts(const FontReferenceList &fontRefs, vector<CharRange> &ranges, float *metrics_height, Bucket *bucket) {357FT_Library freetype = 0;358if (FT_Init_FreeType(&freetype) != 0) {359printf("ERROR: Failed to init freetype\n");360exit(1);361}362363vector<FT_Face> fonts;364fonts.resize(fontRefs.size());365366// The ranges may overlap, so build a list of fonts per range.367map<int, FT_Face_List> fontsByRange;368// TODO: Better way than average?369float totalHeight = 0.0f;370371for (size_t i = 0, n = fontRefs.size(); i < n; ++i) {372FT_Face &font = fonts[i];373int err = FT_New_Face(freetype, fontRefs[i].file_.c_str(), 0, &font);374if (err != 0) {375printf("Failed to load font file %s (%d)\n", fontRefs[i].file_.c_str(), err);376printf("bailing");377exit(1);378}379printf("TTF info: %d glyphs, %08x flags, %d units, %d strikes\n", (int)font->num_glyphs, (int)font->face_flags, (int)font->units_per_EM, (int)font->num_fixed_sizes);380381if (FT_Set_Pixel_Sizes(font, 0, fontRefs[i].size_ * supersample) != 0) {382printf("ERROR: Failed to set font size\n");383exit(1);384}385386ranges = merge(ranges, fontRefs[i].ranges_);387for (size_t r = 0, rn = fontRefs[i].ranges_.size(); r < rn; ++r) {388const CharRange &range = fontRefs[i].ranges_[r];389fontsByRange[range.start].push_back(fonts[i]);390}391392totalHeight += font->size->metrics.height;393}394395// Wait what - how does this make sense?396*metrics_height = totalHeight / (float)fontRefs.size();397398size_t missing_chars = 0;399400// Convert all characters to bitmaps.401for (size_t r = 0, rn = ranges.size(); r < rn; r++) {402FT_Face_List &tryFonts = fontsByRange[ranges[r].start];403ranges[r].result_index = global_id;404for (int kar = ranges[r].start; kar < ranges[r].end; kar++) {405bool filtered = false;406if (ranges[r].filter.size()) {407if (ranges[r].filter.find((u16)kar) == ranges[r].filter.end())408filtered = true;409}410411FT_Face font = nullptr;412bool foundMatch = false;413float vertOffset = 0;414for (size_t i = 0, n = tryFonts.size(); i < n; ++i) {415font = tryFonts[i];416vertOffset = fontRefs[i].vertOffset_;417if (FT_Get_Char_Index(font, kar) != 0) {418foundMatch = true;419break;420}421}422if (!foundMatch) {423// fprintf(stderr, "WARNING: No font contains character %x.\n", kar);424missing_chars++;425}426427Image<unsigned int> img;428if (!foundMatch || filtered || 0 != FT_Load_Char(font, kar, FT_LOAD_RENDER | FT_LOAD_MONOCHROME)) {429img.resize(1, 1);430Data dat;431432dat.id = global_id++;433434dat.sx = 0;435dat.sy = 0;436dat.ex = 0;437dat.ey = 0;438dat.ox = 0;439dat.oy = 0;440dat.wx = 0;441dat.voffset = 0;442dat.charNum = kar;443dat.effect = (int)Effect::FX_RED_TO_ALPHA_SOLID_WHITE;444bucket->AddItem(img, dat);445continue;446}447448// printf("%dx%d %p\n", font->glyph->bitmap.width, font->glyph->bitmap.rows, font->glyph->bitmap.buffer);449const int bord = (128 + distmult - 1) / distmult + 1;450if (font->glyph->bitmap.buffer) {451FT_Bitmap tempbitmap;452FT_Bitmap_New(&tempbitmap);453FT_Bitmap_Convert(freetype, &font->glyph->bitmap, &tempbitmap, 1);454Closest closest(tempbitmap);455456// No resampling, just sets the size of the image.457img.resize((tempbitmap.width + supersample - 1) / supersample + bord * 2, (tempbitmap.rows + supersample - 1) / supersample + bord * 2);458int lmx = (int)img.dat[0].size();459int lmy = (int)img.dat.size();460461// AA by finding distance to character. Probably a fairly decent approximation but why not do it right?462for (int y = 0; y < lmy; y++) {463int cty = (y - bord) * supersample + supersample / 2;464for (int x = 0; x < lmx; x++) {465int ctx = (x - bord) * supersample + supersample / 2;466float dist;467if (closest.safe_access(ctx, cty)) {468dist = closest.find_closest(ctx, cty, 0);469} else {470dist = -closest.find_closest(ctx, cty, 1);471}472dist = dist / supersample * distmult + 127.5f;473dist = floor(dist + 0.5f);474if (dist < 0) dist = 0;475if (dist > 255) dist = 255;476477// Only set the red channel. We process when adding the image.478img.dat[y][x] = (unsigned char)dist;479}480}481FT_Bitmap_Done(freetype, &tempbitmap);482} else {483img.resize(1, 1);484}485486Data dat;487488dat.id = global_id++;489490dat.sx = 0;491dat.sy = 0;492dat.ex = (int)img.dat[0].size();493dat.ey = (int)img.dat.size();494dat.ox = (float)font->glyph->metrics.horiBearingX / 64 / supersample - bord;495dat.oy = -(float)font->glyph->metrics.horiBearingY / 64 / supersample - bord;496dat.voffset = vertOffset;497dat.wx = (float)font->glyph->metrics.horiAdvance / 64 / supersample;498dat.charNum = kar;499500dat.effect = (int)Effect::FX_RED_TO_ALPHA_SOLID_WHITE;501bucket->AddItem(img, dat);502}503}504505if (missing_chars) {506printf("Chars not found in any font: %d\n", (int)missing_chars);507}508509for (size_t i = 0, n = fonts.size(); i < n; ++i) {510FT_Done_Face(fonts[i]);511}512FT_Done_FreeType(freetype);513}514515516bool LoadImage(const char *imagefile, Effect effect, Bucket *bucket) {517Image<unsigned int> img;518519bool success = false;520if (!strcmp(imagefile, "white.png")) {521img.dat.resize(16);522for (int i = 0; i < 16; i++) {523img.dat[i].resize(16);524for (int j = 0; j < 16; j++) {525img.dat[i][j] = 0xFFFFFFFF;526}527}528success = true;529} else {530success = img.LoadPNG(imagefile);531// printf("loaded image: %ix%i\n", (int)img.dat[0].size(), (int)img.dat.size());532}533if (!success) {534return false;535}536537Data dat;538memset(&dat, 0, sizeof(dat));539dat.id = global_id++;540dat.sx = 0;541dat.sy = 0;542dat.ex = (int)img.dat[0].size();543dat.ey = (int)img.dat.size();544dat.effect = (int)effect;545bucket->AddItem(img, dat);546return true;547}548549// Use the result array, and recorded data, to generate C++ tables for everything.550struct FontDesc {551string name;552553int first_char_id = -1;554555float ascend = 0.0f;556float descend = 0.0f;557float height = 0.0f;558559float metrics_height = 0.0f;560561std::vector<CharRange> ranges;562563void ComputeHeight(const vector<Data> &results, float distmult) {564ascend = 0;565descend = 0;566for (size_t r = 0; r < ranges.size(); r++) {567for (int i = ranges[r].start; i < ranges[r].end; i++) {568int idx = i - ranges[r].start + ranges[r].result_index;569ascend = max(ascend, -results[idx].oy);570descend = max(descend, results[idx].ey - results[idx].sy + results[idx].oy);571}572}573574height = metrics_height / 64.0f / supersample;575}576577void OutputSelf(FILE *fil, float tw, float th, const vector<Data> &results) const {578// Dump results as chardata.579fprintf(fil, "const AtlasChar font_%s_chardata[] = {\n", name.c_str());580int start_index = 0;581for (size_t r = 0; r < ranges.size(); r++) {582fprintf(fil, "// RANGE: 0x%x - 0x%x, start %d, result %d\n", ranges[r].start, ranges[r].end, start_index, ranges[r].result_index);583for (int i = ranges[r].start; i < ranges[r].end; i++) {584int idx = i - ranges[r].start + ranges[r].result_index;585fprintf(fil, " {%ff, %ff, %ff, %ff, %1.4ff, %1.4ff, %1.4ff, %i, %i}, // %04x\n",586/*results[i].id, */587results[idx].sx / tw,588results[idx].sy / th,589results[idx].ex / tw,590results[idx].ey / th,591results[idx].ox,592results[idx].oy + results[idx].voffset,593results[idx].wx,594results[idx].ex - results[idx].sx, results[idx].ey - results[idx].sy,595results[idx].charNum);596}597start_index += ranges[r].end - ranges[r].start;598}599fprintf(fil, "};\n");600601fprintf(fil, "const AtlasCharRange font_%s_ranges[] = {\n", name.c_str());602// Write range information.603start_index = 0;604for (size_t r = 0; r < ranges.size(); r++) {605int first_char_id = ranges[r].start;606int last_char_id = ranges[r].end;607fprintf(fil, " { %i, %i, %i },\n", first_char_id, last_char_id, start_index);608start_index += last_char_id - first_char_id;609}610fprintf(fil, "};\n");611612fprintf(fil, "const AtlasFont font_%s = {\n", name.c_str());613fprintf(fil, " %ff, // padding\n", height - ascend - descend);614fprintf(fil, " %ff, // height\n", ascend + descend);615fprintf(fil, " %ff, // ascend\n", ascend);616fprintf(fil, " %ff, // distslope\n", distmult / 256.0);617fprintf(fil, " font_%s_chardata,\n", name.c_str());618fprintf(fil, " font_%s_ranges,\n", name.c_str());619fprintf(fil, " %i,\n", (int)ranges.size());620fprintf(fil, " \"%s\", // name\n", name.c_str());621fprintf(fil, "};\n");622}623624void OutputIndex(FILE *fil) const {625fprintf(fil, " &font_%s,\n", name.c_str());626}627628void OutputHeader(FILE *fil, int index) const {629fprintf(fil, "#define %s %i\n", name.c_str(), index);630}631632AtlasFontHeader GetHeader() const {633int numChars = 0;634for (size_t r = 0; r < ranges.size(); r++) {635numChars += ranges[r].end - ranges[r].start;636}637AtlasFontHeader header;638header.padding = height - ascend - descend;639header.height = ascend + descend;640header.ascend = ascend;641header.distslope = distmult / 256.0;642strncpy(header.name, name.c_str(), sizeof(header.name));643header.name[sizeof(header.name) - 1] = '\0';644header.numChars = numChars;645header.numRanges = (int)ranges.size();646return header;647}648649vector<AtlasCharRange> GetRanges() const {650int start_index = 0;651vector<AtlasCharRange> out_ranges;652for (size_t r = 0; r < ranges.size(); r++) {653int first_char_id = ranges[r].start;654int last_char_id = ranges[r].end;655AtlasCharRange range;656range.start = first_char_id;657range.end = last_char_id;658range.result_index = start_index;659start_index += last_char_id - first_char_id;660out_ranges.push_back(range);661}662return out_ranges;663}664665vector<AtlasChar> GetChars(float tw, float th, const vector<Data> &results) const {666vector<AtlasChar> chars;667for (size_t r = 0; r < ranges.size(); r++) {668for (int i = ranges[r].start; i < ranges[r].end; i++) {669int idx = i - ranges[r].start + ranges[r].result_index;670AtlasChar c;671c.sx = results[idx].sx / tw; // sx, sy, ex, ey672c.sy = results[idx].sy / th;673c.ex = results[idx].ex / tw;674c.ey = results[idx].ey / th;675c.ox = results[idx].ox; // ox, oy676c.oy = results[idx].oy + results[idx].voffset;677c.wx = results[idx].wx; //wx678c.pw = results[idx].ex - results[idx].sx; // pw, ph679c.ph = results[idx].ey - results[idx].sy;680chars.push_back(c);681}682}683return chars;684}685};686687struct ImageDesc {688string name;689Effect effect;690int result_index;691692AtlasImage ToAtlasImage(float tw, float th, const vector<Data> &results) {693AtlasImage img;694int i = result_index;695float toffx = 0.5f / tw;696float toffy = 0.5f / th;697img.u1 = results[i].sx / tw + toffx;698img.v1 = results[i].sy / th + toffy;699img.u2 = results[i].ex / tw - toffx;700img.v2 = results[i].ey / th - toffy;701img.w = results[i].ex - results[i].sx;702img.h = results[i].ey - results[i].sy;703strncpy(img.name, name.c_str(), sizeof(img.name));704img.name[sizeof(img.name) - 1] = 0;705return img;706}707708void OutputSelf(FILE *fil, float tw, float th, const vector<Data> &results) {709int i = result_index;710float toffx = 0.5f / tw;711float toffy = 0.5f / th;712fprintf(fil, " {%ff, %ff, %ff, %ff, %d, %d, \"%s\"},\n",713results[i].sx / tw + toffx,714results[i].sy / th + toffy,715results[i].ex / tw - toffx,716results[i].ey / th - toffy,717results[i].ex - results[i].sx,718results[i].ey - results[i].sy,719name.c_str());720}721722void OutputHeader(FILE *fil, int index) {723fprintf(fil, "#define %s %i\n", name.c_str(), index);724}725};726727728CharRange range(int start, int end, const std::set<u16> &filter) {729CharRange r;730r.start = start;731r.end = end + 1;732r.result_index = 0;733r.filter = filter;734return r;735}736737CharRange range(int start, int end) {738CharRange r;739r.start = start;740r.end = end + 1;741r.result_index = 0;742return r;743}744745inline bool operator <(const CharRange &a, const CharRange &b) {746// These ranges should never overlap so this should be enough.747return a.start < b.start;748}749750751void LearnFile(const char *filename, const char *desc, std::set<u16> &chars, uint32_t lowerLimit, uint32_t upperLimit) {752FILE *f = fopen(filename, "rb");753if (f) {754fseek(f, 0, SEEK_END);755size_t sz = ftell(f);756fseek(f, 0, SEEK_SET);757char *data = new char[sz + 1];758fread(data, 1, sz, f);759fclose(f);760data[sz] = 0;761762UTF8 utf(data);763int learnCount = 0;764while (!utf.end()) {765uint32_t c = utf.next();766if (c >= lowerLimit && c <= upperLimit) {767if (chars.find(c) == chars.end()) {768learnCount++;769chars.insert(c);770}771}772}773delete[] data;774printf("%d %s characters learned.\n", learnCount, desc);775}776}777778void GetLocales(const char *locales, std::vector<CharRange> &ranges)779{780std::set<u16> kanji;781std::set<u16> hangul1, hangul2, hangul3;782for (int i = 0; i < sizeof(kanjiFilter) / sizeof(kanjiFilter[0]); i += 2)783{784// Kanji filtering.785if ((kanjiFilter[i + 1] & USE_KANJI) > 0) {786kanji.insert(kanjiFilter[i]);787}788}789790LearnFile("assets/lang/zh_CN.ini", "Chinese", kanji, 0x3400, 0xFFFF);791LearnFile("assets/lang/zh_TW.ini", "Chinese", kanji, 0x3400, 0xFFFF);792LearnFile("assets/langregion.ini", "Chinese", kanji, 0x3400, 0xFFFF);793LearnFile("assets/lang/ko_KR.ini", "Korean", hangul1, 0x1100, 0x11FF);794LearnFile("assets/lang/ko_KR.ini", "Korean", hangul2, 0x3130, 0x318F);795LearnFile("assets/lang/ko_KR.ini", "Korean", hangul3, 0xAC00, 0xD7A3);796LearnFile("assets/langregion.ini", "Korean", hangul1, 0x1100, 0x11FF);797LearnFile("assets/langregion.ini", "Korean", hangul2, 0x3130, 0x318F);798LearnFile("assets/langregion.ini", "Korean", hangul3, 0xAC00, 0xD7A3);799// The end point of a range is now inclusive!800801for (size_t i = 0; i < strlen(locales); i++) {802switch (locales[i]) {803case 'U': // US ASCII804ranges.push_back(range(32, 127));805break;806case 'W': // Latin-1 extras 1807ranges.push_back(range(0x80, 0x80)); // euro sign (??? It's not here?)808ranges.push_back(range(0xA2, 0xFF)); // 80 - A0 appears to contain nothing interesting809ranges.push_back(range(0x2121, 0x2122)); // TEL symbol and trademark symbol810break;811case 'E': // Latin-1 Extended A (needed for Hungarian etc)812ranges.push_back(range(0x100, 0x17F));813break;814case 'e': // Latin-1 Extended B (for some African and latinized asian languages?)815ranges.push_back(range(0x180, 0x250));816break;817case 'k': // Katakana818ranges.push_back(range(0x30A0, 0x30FF));819ranges.push_back(range(0x31F0, 0x31FF));820ranges.push_back(range(0xFF00, 0xFFEF)); // half-width ascii821break;822case 'h': // Hiragana823ranges.push_back(range(0x3041, 0x3097));824ranges.push_back(range(0x3099, 0x309F));825break;826case 's': // ShiftJIS symbols827ranges.push_back(range(0x2010, 0x2312)); // General Punctuation, Letterlike Symbols, Arrows,828// Mathematical Operators, Miscellaneous Technical829ranges.push_back(range(0x2500, 0x254B)); // Box drawing830ranges.push_back(range(0x25A0, 0x266F)); // Geometric Shapes, Miscellaneous Symbols831ranges.push_back(range(0x3231, 0x3231)); // Co,.Ltd. symbol832ranges.push_back(range(0x2116, 0x2116)); // "No." symbol833ranges.push_back(range(0x33CD, 0x33CD)); // "K.K." symbol834break;835case 'H': // Hebrew836ranges.push_back(range(0x0590, 0x05FF));837break;838case 'G': // Greek839ranges.push_back(range(0x0370, 0x03FF));840break;841case 'R': // Russian842ranges.push_back(range(0x0400, 0x04FF));843break;844case 'c': // All Kanji, filtered though!845ranges.push_back(range(0x3000, 0x303f)); // Ideographic symbols846ranges.push_back(range(0x4E00, 0x9FFF, kanji));847// ranges.push_back(range(0xFB00, 0xFAFF, kanji));848break;849case 'T': // Thai850ranges.push_back(range(0x0E00, 0x0E5B));851break;852case 'K': // Korean (hangul)853ranges.push_back(range(0xAC00, 0xD7A3, hangul3));854break;855case 'V': // Vietnamese (need 'e' too)856ranges.push_back(range(0x1EA0, 0x1EF9));857break;858}859}860861ranges.push_back(range(0xFFFD, 0xFFFD));862std::sort(ranges.begin(), ranges.end());863}864865static bool WriteCompressed(const void *src, size_t sz, size_t num, FILE *fp) {866size_t src_size = sz * num;867size_t compressed_size = ZSTD_compressBound(src_size);868uint8_t *compressed = new uint8_t[compressed_size];869compressed_size = ZSTD_compress(compressed, compressed_size, src, src_size, 22);870if (ZSTD_isError(compressed_size)) {871delete[] compressed;872return false;873}874875uint32_t write_size = (uint32_t)compressed_size;876if (fwrite(&write_size, sizeof(uint32_t), 1, fp) != 1) {877delete[] compressed;878return false;879}880if (fwrite(compressed, 1, compressed_size, fp) != compressed_size) {881delete[] compressed;882return false;883}884885delete[] compressed;886return true;887}888889int main(int argc, char **argv) {890// initProgram(&argc, const_cast<const char ***>(&argv));891// /usr/share/fonts/truetype/msttcorefonts/Arial_Black.ttf892// /usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-R.ttf893if (argc < 3) {894printf("Not enough arguments");895return 1;896}897assert(argc >= 3);898if (argc > 3) {899if (!strcmp(argv[3], "8888")) {900highcolor = true;901printf("RGBA8888 enabled!\n");902}903}904printf("Reading script %s\n", argv[1]);905const char *atlas_name = argv[2];906string image_name = string(atlas_name) + "_atlas.zim";907string meta_name = string(atlas_name) + "_atlas.meta";908out_prefix = argv[2];909910map<string, FontReferenceList> fontRefs;911vector<FontDesc> fonts;912vector<ImageDesc> images;913914Bucket bucket;915916char line[512]{};917FILE *script = fopen(argv[1], "r");918if (!fgets(line, 512, script)) {919printf("Error fgets-ing\n");920}921int image_width = 0;922sscanf(line, "%i", &image_width);923printf("Texture width: %i\n", image_width);924while (!feof(script)) {925if (!fgets(line, 511, script)) break;926if (!strlen(line)) break;927if (line[0] == '#') continue;928char *rest = strchr(line, ' ');929if (rest) {930*rest = 0;931rest++;932}933char *word = line;934if (!strcmp(word, "font")) {935// Font!936char fontname[256];937char fontfile[256];938char locales[256];939int pixheight;940float vertOffset = 0;941sscanf(rest, "%255s %255s %255s %i %f", fontname, fontfile, locales, &pixheight, &vertOffset);942printf("Font: %s (%s) in size %i. Locales: %s\n", fontname, fontfile, pixheight, locales);943944std::vector<CharRange> ranges;945GetLocales(locales, ranges);946printf("locales fetched.\n");947948FontReference fnt(fontname, fontfile, ranges, pixheight, vertOffset);949fontRefs[fontname].push_back(fnt);950} else if (!strcmp(word, "image")) {951char imagename[256];952char imagefile[256];953char effectname[256];954sscanf(rest, "%255s %255s %255s", imagename, imagefile, effectname);955Effect effect = GetEffect(effectname);956printf("Image %s with effect %s (%i)\n", imagefile, effectname, (int)effect);957ImageDesc desc;958desc.name = imagename;959desc.effect = effect;960desc.result_index = (int)bucket.items.size();961images.push_back(desc);962if (!LoadImage(imagefile, effect, &bucket)) {963fprintf(stderr, "Failed to load image %s\n", imagefile);964}965} else {966fprintf(stderr, "Warning: Failed to parse line starting with %s\n", line);967}968}969fclose(script);970971// Script fully read, now rasterize the fonts.972for (auto it = fontRefs.begin(), end = fontRefs.end(); it != end; ++it) {973FontDesc fnt;974fnt.first_char_id = (int)bucket.items.size();975976vector<CharRange> finalRanges;977float metrics_height;978RasterizeFonts(it->second, finalRanges, &metrics_height, &bucket);979printf("font rasterized.\n");980981fnt.ranges = finalRanges;982fnt.name = it->first;983fnt.metrics_height = metrics_height;984985fonts.push_back(fnt);986}987988// Script read, all subimages have been generated.989990// Place the subimages onto the main texture. Also writes to png.991Image<unsigned int> dest;992// Place things on the bitmap.993printf("Resolving...\n");994995vector<Data> results = bucket.Resolve(image_width, dest);996if (highcolor) {997printf("Writing .ZIM %ix%i RGBA8888...\n", dest.width(), dest.height());998dest.SaveZIM(image_name.c_str(), ZIM_RGBA8888 | ZIM_ZSTD_COMPRESSED);999} else {1000printf("Writing .ZIM %ix%i RGBA4444...\n", dest.width(), dest.height());1001dest.SaveZIM(image_name.c_str(), ZIM_RGBA4444 | ZIM_ZSTD_COMPRESSED);1002}10031004// Also save PNG for debugging.1005printf("Writing .PNG %s\n", (image_name + ".png").c_str());1006dest.SavePNG((image_name + ".png").c_str());10071008printf("Done. Outputting source and meta files %s_atlas.cpp/h/meta.\n", out_prefix.c_str());1009// Sort items by ID.1010sort(results.begin(), results.end());10111012// Save all the metadata.1013{1014FILE *meta = fopen(meta_name.c_str(), "wb");1015AtlasHeader header;1016header.magic = ATLAS_MAGIC;1017header.version = 1;1018header.numFonts = (int)fonts.size();1019header.numImages = (int)images.size();1020fwrite(&header, 1, sizeof(header), meta);1021// For each image1022AtlasImage *atalas_images = new AtlasImage[images.size()];1023for (int i = 0; i < (int)images.size(); i++) {1024atalas_images[i] = images[i].ToAtlasImage((float)dest.width(), (float)dest.height(), results);1025}1026WriteCompressed(atalas_images, sizeof(AtlasImage), images.size(), meta);1027// For each font1028for (int i = 0; i < (int)fonts.size(); i++) {1029auto &font = fonts[i];1030font.ComputeHeight(results, distmult);1031AtlasFontHeader font_header = font.GetHeader();1032fwrite(&font_header, 1, sizeof(font_header), meta);1033auto ranges = font.GetRanges();1034WriteCompressed(ranges.data(), sizeof(AtlasCharRange), ranges.size(), meta);1035auto chars = font.GetChars((float)dest.width(), (float)dest.height(), results);1036WriteCompressed(chars.data(), sizeof(AtlasChar), chars.size(), meta);1037}1038fclose(meta);1039}10401041FILE *cpp_file = fopen((out_prefix + "_atlas.cpp").c_str(), "wb");1042fprintf(cpp_file, "// C++ generated by atlastool from %s ([email protected])\n\n", argv[1]);1043fprintf(cpp_file, "#include \"%s\"\n\n", (out_prefix + "_atlas.h").c_str());1044for (int i = 0; i < (int)fonts.size(); i++) {1045FontDesc &xfont = fonts[i];1046xfont.ComputeHeight(results, distmult);1047xfont.OutputSelf(cpp_file, (float)dest.width(), (float)dest.height(), results);1048}10491050if (fonts.size()) {1051fprintf(cpp_file, "const AtlasFont *%s_fonts[%i] = {\n", atlas_name, (int)fonts.size());1052for (int i = 0; i < (int)fonts.size(); i++) {1053fonts[i].OutputIndex(cpp_file);1054}1055fprintf(cpp_file, "};\n");1056}10571058if (images.size()) {1059fprintf(cpp_file, "const AtlasImage %s_images[%i] = {\n", atlas_name, (int)images.size());1060for (int i = 0; i < (int)images.size(); i++) {1061images[i].OutputSelf(cpp_file, (float)dest.width(), (float)dest.height(), results);1062}1063fprintf(cpp_file, "};\n");1064}10651066fprintf(cpp_file, "const Atlas %s_atlas = {\n", atlas_name);1067fprintf(cpp_file, " \"%s\",\n", image_name.c_str());1068if (fonts.size()) {1069fprintf(cpp_file, " %s_fonts, %i,\n", atlas_name, (int)fonts.size());1070} else {1071fprintf(cpp_file, " 0, 0,\n");1072}1073if (images.size()) {1074fprintf(cpp_file, " %s_images, %i,\n", atlas_name, (int)images.size());1075} else {1076fprintf(cpp_file, " 0, 0,\n");1077}1078fprintf(cpp_file, "};\n");1079// Should output a list pointing to all the fonts as well.1080fclose(cpp_file);10811082FILE *h_file = fopen((out_prefix + "_atlas.h").c_str(), "wb");1083fprintf(h_file, "// Header generated by atlastool from %s ([email protected])\n\n", argv[1]);1084fprintf(h_file, "#pragma once\n");1085fprintf(h_file, "#include \"gfx/texture_atlas.h\"\n\n");1086if (fonts.size()) {1087fprintf(h_file, "// FONTS_%s\n", atlas_name);1088for (int i = 0; i < (int)fonts.size(); i++) {1089fonts[i].OutputHeader(h_file, i);1090}1091fprintf(h_file, "\n\n");1092}1093if (images.size()) {1094fprintf(h_file, "// IMAGES_%s\n", atlas_name);1095for (int i = 0; i < (int)images.size(); i++) {1096images[i].OutputHeader(h_file, i);1097}1098fprintf(h_file, "\n\n");1099}1100fprintf(h_file, "extern const Atlas %s_atlas;\n", atlas_name);1101fprintf(h_file, "extern const AtlasImage %s_images[%i];\n", atlas_name, (int)images.size());1102fclose(h_file);1103}110411051106