From 645c2d90ace3e48c639c49512fe1d7a43beb1755 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 23 Apr 2016 04:37:42 +0000 Subject: [PATCH] Move bitmap font to res/fonts/; remove unifont2c. This commit integrates the bitmap font in the resource system, so that cross-compilation would be easier. The font handling code was carefully written to do glyph parsing lazily; in practice this means that after this commit, startup is less than 25ms slower, most of it spent in inflate(). This should also result in faster rendering, since there is no rampant plane switching anymore; instead, all characters that are actually used are stashed into same one texture. --- res/CMakeLists.txt | 11 +- {src => res}/fonts/private/0-check-false.png | Bin {src => res}/fonts/private/1-check-true.png | Bin {src => res}/fonts/private/2-radio-false.png | Bin {src => res}/fonts/private/3-radio-true.png | Bin {src => res}/fonts/private/4-stipple-dot.png | Bin .../fonts/private/5-stipple-dash-long.png | Bin {src => res}/fonts/private/6-stipple-dash.png | Bin .../fonts/private/7-stipple-zigzag.png | Bin .../fonts/unifont.hex.gz | Bin src/CMakeLists.txt | 16 +- src/glhelper.cpp | 244 ++++++---------- src/resource.cpp | 219 +++++++++++++++ src/resource.h | 26 ++ src/solvespace.h | 2 +- src/textwin.cpp | 6 +- tools/CMakeLists.txt | 12 - tools/unifont2c.cpp | 261 ------------------ 18 files changed, 351 insertions(+), 446 deletions(-) rename {src => res}/fonts/private/0-check-false.png (100%) rename {src => res}/fonts/private/1-check-true.png (100%) rename {src => res}/fonts/private/2-radio-false.png (100%) rename {src => res}/fonts/private/3-radio-true.png (100%) rename {src => res}/fonts/private/4-stipple-dot.png (100%) rename {src => res}/fonts/private/5-stipple-dash-long.png (100%) rename {src => res}/fonts/private/6-stipple-dash.png (100%) rename {src => res}/fonts/private/7-stipple-zigzag.png (100%) rename src/fonts/unifont-8.0.01.hex.gz => res/fonts/unifont.hex.gz (100%) delete mode 100644 tools/unifont2c.cpp diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index 153aeafa..db9db853 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -167,7 +167,16 @@ add_resources( icons/text-window/outlines.png icons/text-window/point.png icons/text-window/shaded.png - icons/text-window/workplane.png) + icons/text-window/workplane.png + fonts/unifont.hex.gz + fonts/private/0-check-false.png + fonts/private/1-check-true.png + fonts/private/2-radio-false.png + fonts/private/3-radio-true.png + fonts/private/4-stipple-dot.png + fonts/private/5-stipple-dash-long.png + fonts/private/6-stipple-dash.png + fonts/private/7-stipple-zigzag.png) # Third, distribute the resources. add_custom_target(resources diff --git a/src/fonts/private/0-check-false.png b/res/fonts/private/0-check-false.png similarity index 100% rename from src/fonts/private/0-check-false.png rename to res/fonts/private/0-check-false.png diff --git a/src/fonts/private/1-check-true.png b/res/fonts/private/1-check-true.png similarity index 100% rename from src/fonts/private/1-check-true.png rename to res/fonts/private/1-check-true.png diff --git a/src/fonts/private/2-radio-false.png b/res/fonts/private/2-radio-false.png similarity index 100% rename from src/fonts/private/2-radio-false.png rename to res/fonts/private/2-radio-false.png diff --git a/src/fonts/private/3-radio-true.png b/res/fonts/private/3-radio-true.png similarity index 100% rename from src/fonts/private/3-radio-true.png rename to res/fonts/private/3-radio-true.png diff --git a/src/fonts/private/4-stipple-dot.png b/res/fonts/private/4-stipple-dot.png similarity index 100% rename from src/fonts/private/4-stipple-dot.png rename to res/fonts/private/4-stipple-dot.png diff --git a/src/fonts/private/5-stipple-dash-long.png b/res/fonts/private/5-stipple-dash-long.png similarity index 100% rename from src/fonts/private/5-stipple-dash-long.png rename to res/fonts/private/5-stipple-dash-long.png diff --git a/src/fonts/private/6-stipple-dash.png b/res/fonts/private/6-stipple-dash.png similarity index 100% rename from src/fonts/private/6-stipple-dash.png rename to res/fonts/private/6-stipple-dash.png diff --git a/src/fonts/private/7-stipple-zigzag.png b/res/fonts/private/7-stipple-zigzag.png similarity index 100% rename from src/fonts/private/7-stipple-zigzag.png rename to res/fonts/private/7-stipple-zigzag.png diff --git a/src/fonts/unifont-8.0.01.hex.gz b/res/fonts/unifont.hex.gz similarity index 100% rename from src/fonts/unifont-8.0.01.hex.gz rename to res/fonts/unifont.hex.gz diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f1d53bb0..58ef943e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -84,19 +84,6 @@ endif() file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated) -file(GLOB chars ${CMAKE_CURRENT_SOURCE_DIR}/fonts/private/*.png) -list(SORT chars) -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated/bitmapfont.table.h - COMMAND $ - ${CMAKE_CURRENT_BINARY_DIR}/generated/bitmapfont.table.h - ${CMAKE_CURRENT_SOURCE_DIR}/fonts/unifont-8.0.01.hex.gz - ${chars} - DEPENDS unifont2c - ${CMAKE_CURRENT_SOURCE_DIR}/fonts/unifont-8.0.01.hex.gz - ${chars} - VERBATIM) - add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h COMMAND $ @@ -107,8 +94,7 @@ add_custom_command( VERBATIM) set(generated_HEADERS - ${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h - ${CMAKE_CURRENT_BINARY_DIR}/generated/bitmapfont.table.h) + ${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h) # platform dependencies diff --git a/src/glhelper.cpp b/src/glhelper.cpp index 95c12444..6bf904c2 100644 --- a/src/glhelper.cpp +++ b/src/glhelper.cpp @@ -3,7 +3,6 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#include #include "solvespace.h" namespace SolveSpace { @@ -11,9 +10,6 @@ namespace SolveSpace { // A vector font. #include "generated/vectorfont.table.h" -// A bitmap font. -#include "generated/bitmapfont.table.h" - static bool ColorLocked; static bool DepthOffsetLocked; @@ -726,156 +722,6 @@ void ssglDepthRangeLockToFront(bool yes) } } -const int BitmapFontChunkSize = 64 * 64; -static bool BitmapFontChunkInitialized[0x10000 / BitmapFontChunkSize]; -static int BitmapFontCurrentChunk = -1; - -static void CreateBitmapFontChunk(const uint8_t *source, size_t sourceLength, - int textureIndex) -{ - // Place the font in our texture in a two-dimensional grid. - // The maximum texture size that is reasonably supported is 1024x1024. - const size_t fontTextureSize = BitmapFontChunkSize*16*16; - uint8_t *fontTexture = (uint8_t *)malloc(fontTextureSize), - *mappedTexture = (uint8_t *)malloc(fontTextureSize); - - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.opaque = Z_NULL; - if(inflateInit(&stream) != Z_OK) - oops(); - - stream.next_in = (Bytef *)source; - stream.avail_in = sourceLength; - stream.next_out = fontTexture; - stream.avail_out = fontTextureSize; - if(inflate(&stream, Z_NO_FLUSH) != Z_STREAM_END) - oops(); - if(stream.avail_out != 0) - oops(); - - inflateEnd(&stream); - - for(int a = 0; a < BitmapFontChunkSize; a++) { - int row = a / 64, col = a % 64; - - for(int i = 0; i < 16; i++) { - memcpy(mappedTexture + row*64*16*16 + col*16 + i*64*16, - fontTexture + a*16*16 + i*16, - 16); - } - } - - free(fontTexture); - - glBindTexture(GL_TEXTURE_2D, textureIndex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, - 16*64, 64*16, - 0, - GL_ALPHA, GL_UNSIGNED_BYTE, - mappedTexture); - - free(mappedTexture); -} - -static void SwitchToBitmapFontChunkFor(char32_t chr) -{ - int plane = chr / BitmapFontChunkSize, - textureIndex = TEXTURE_BITMAP_FONT + plane; - - if(BitmapFontCurrentChunk != textureIndex) { - glEnd(); - - if(!BitmapFontChunkInitialized[plane]) { - CreateBitmapFontChunk(CompressedFontTexture[plane].data, - CompressedFontTexture[plane].length, - textureIndex); - BitmapFontChunkInitialized[plane] = true; - } else { - glBindTexture(GL_TEXTURE_2D, textureIndex); - } - - BitmapFontCurrentChunk = textureIndex; - - glBegin(GL_QUADS); - } -} - -void ssglInitializeBitmapFont() -{ - memset(BitmapFontChunkInitialized, 0, sizeof(BitmapFontChunkInitialized)); - BitmapFontCurrentChunk = -1; -} - -int ssglBitmapCharWidth(char32_t chr) { - if(!CodepointProperties[chr].exists) - chr = 0xfffd; // replacement character - - return CodepointProperties[chr].isWide ? 2 : 1; -} - -void ssglBitmapCharQuad(char32_t chr, double x, double y) -{ - int w, h; - - if(!CodepointProperties[chr].exists) - chr = 0xfffd; // replacement character - - h = 16; - if(chr >= 0xe000 && chr <= 0xefff) { - // Special character, like a checkbox or a radio button - w = 16; - x -= 3; - } else if(CodepointProperties[chr].isWide) { - // Wide (usually CJK or reserved) character - w = 16; - } else { - // Normal character - w = 8; - } - - if(chr != ' ' && chr != 0) { - int n = chr % BitmapFontChunkSize; - int row = n / 64, col = n % 64; - double s0 = col/64.0, - s1 = (col+1)/64.0, - t0 = row/64.0, - t1 = t0 + (w/16.0)/64; - - SwitchToBitmapFontChunkFor(chr); - - glTexCoord2d(s1, t0); - glVertex2d(x, y); - - glTexCoord2d(s1, t1); - glVertex2d(x + w, y); - - glTexCoord2d(s0, t1); - glVertex2d(x + w, y - h); - - glTexCoord2d(s0, t0); - glVertex2d(x, y - h); - } -} - -void ssglBitmapText(const std::string &str, Vector p) -{ - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); - for(char32_t chr : ReadUTF8(str)) { - ssglBitmapCharQuad(chr, p.x, p.y); - p.x += 8 * ssglBitmapCharWidth(chr); - } - glEnd(); - glDisable(GL_TEXTURE_2D); -} - void ssglDrawPixmap(const Pixmap &pixmap, bool flip) { glBindTexture(GL_TEXTURE_2D, TEXTURE_DRAW_PIXELS); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -905,4 +751,94 @@ void ssglDrawPixmap(const Pixmap &pixmap, bool flip) { glDisable(GL_TEXTURE_2D); } +//----------------------------------------------------------------------------- +// Bitmap font rendering +//----------------------------------------------------------------------------- + +static BitmapFont BuiltinBitmapFont; +static void LoadBitmapFont() { + if(!BuiltinBitmapFont.IsEmpty()) return; + + BuiltinBitmapFont = BitmapFont::From(LoadStringFromGzip("fonts/unifont.hex.gz")); + BuiltinBitmapFont.AddGlyph(0xE000, LoadPNG("fonts/private/0-check-false.png")); + BuiltinBitmapFont.AddGlyph(0xE001, LoadPNG("fonts/private/1-check-true.png")); + BuiltinBitmapFont.AddGlyph(0xE002, LoadPNG("fonts/private/2-radio-false.png")); + BuiltinBitmapFont.AddGlyph(0xE003, LoadPNG("fonts/private/3-radio-true.png")); + BuiltinBitmapFont.AddGlyph(0xE004, LoadPNG("fonts/private/4-stipple-dot.png")); + BuiltinBitmapFont.AddGlyph(0xE005, LoadPNG("fonts/private/5-stipple-dash-long.png")); + BuiltinBitmapFont.AddGlyph(0xE006, LoadPNG("fonts/private/6-stipple-dash.png")); + BuiltinBitmapFont.AddGlyph(0xE007, LoadPNG("fonts/private/7-stipple-zigzag.png")); + // Unifont doesn't have a glyph for U+0020. + std::unique_ptr blank(new uint8_t[8*16*3] {}); + BuiltinBitmapFont.AddGlyph(0x20, Pixmap({ 8, 16, 8*3, false, std::move(blank) })); +} + +void ssglInitializeBitmapFont() +{ + LoadBitmapFont(); + + glBindTexture(GL_TEXTURE_2D, TEXTURE_BITMAP_FONT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, + BitmapFont::TEXTURE_DIM, BitmapFont::TEXTURE_DIM, + 0, GL_ALPHA, GL_UNSIGNED_BYTE, BuiltinBitmapFont.texture.get()); +} + +int ssglBitmapCharWidth(char32_t codepoint) { + if(codepoint >= 0xe000 && codepoint <= 0xefff) { + // These are special-cased because checkboxes predate support for 2 cell wide + // characters; and so all Printf() calls pad them with spaces. + return 1; + } + + LoadBitmapFont(); + return BuiltinBitmapFont.GetGlyph(codepoint).advanceCells; +} + +double ssglBitmapCharQuad(char32_t codepoint, double x, double y) +{ + double s0, t0, s1, t1; + size_t w, h; + if(BuiltinBitmapFont.LocateGlyph(codepoint, &s0, &t0, &s1, &t1, &w, &h)) { + // LocateGlyph modified the texture, reload it. + glEnd(); + ssglInitializeBitmapFont(); + glBegin(GL_QUADS); + } + + if(codepoint >= 0xe000 && codepoint <= 0xefff) { + // Special character, like a checkbox or a radio button + x -= 3; + } + + glTexCoord2d(s0, t0); + glVertex2d(x, y - h); + + glTexCoord2d(s0, t1); + glVertex2d(x, y); + + glTexCoord2d(s1, t1); + glVertex2d(x + w, y); + + glTexCoord2d(s1, t0); + glVertex2d(x + w, y - h); + + return w; +} + +void ssglBitmapText(const std::string &str, Vector p) +{ + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + for(char32_t codepoint : ReadUTF8(str)) { + p.x += ssglBitmapCharQuad(codepoint, p.x, p.y); + } + glEnd(); + glDisable(GL_TEXTURE_2D); +} + }; diff --git a/src/resource.cpp b/src/resource.cpp index 3f3f5404..623b5a19 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -4,6 +4,7 @@ // Copyright 2016 whitequark //----------------------------------------------------------------------------- #include "solvespace.h" +#include #include namespace SolveSpace { @@ -20,6 +21,37 @@ std::string LoadString(const std::string &name) { return std::string(static_cast(data), size); } +std::string LoadStringFromGzip(const std::string &name) { + size_t size; + const void *data = LoadResource(name, &size); + if(data == NULL) oops(); + + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + if(inflateInit2(&stream, /*decode gzip header*/16) != Z_OK) + oops(); + + // Extract length mod 2**32 from the gzip trailer. + std::string result; + if(size < 4) oops(); + result.resize(*(uint32_t *)((uintptr_t)data + size - 4)); + + stream.next_in = (Bytef *)data; + stream.avail_in = size; + stream.next_out = (Bytef *)&result[0]; + stream.avail_out = result.length(); + if(inflate(&stream, Z_NO_FLUSH) != Z_STREAM_END) + oops(); + if(stream.avail_out != 0) + oops(); + + inflateEnd(&stream); + + return result; +} + Pixmap LoadPNG(const std::string &name) { size_t size; const void *data = LoadResource(name, &size); @@ -39,6 +71,16 @@ void Pixmap::Clear() { *this = {}; } +RgbaColor Pixmap::GetPixel(size_t x, size_t y) const { + uint8_t *pixel = &data[y * stride + x * GetBytesPerPixel()]; + + if(hasAlpha) { + return RgbaColor::From(pixel[0], pixel[1], pixel[2], pixel[3]); + } else { + return RgbaColor::From(pixel[0], pixel[1], pixel[2]); + } +} + static Pixmap ReadPNGIntoPixmap(png_struct *png_ptr, png_info *info_ptr) { png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB, NULL); @@ -121,4 +163,181 @@ exit: return pixmap; } +//----------------------------------------------------------------------------- +// ASCII sequence parsing +//----------------------------------------------------------------------------- + +class ASCIIReader { +public: + std::string::const_iterator pos, end; + + static ASCIIReader From(const std::string &str) { + return ASCIIReader({ str.cbegin(), str.cend() }); + } + + size_t LengthToEOL() { + return std::find(pos, end, '\n') - pos; + } + + void ReadChar(char c) { + if(pos == end) oops(); + if(*pos++ != c) oops(); + } + + uint8_t Read4HexBits() { + if(pos == end) oops(); + char c = *pos++; + if(c >= '0' && c <= '9') { + return c - '0'; + } else if(c >= 'a' && c <= 'f') { + return 10 + (c - 'a'); + } else if(c >= 'A' && c <= 'F') { + return 10 + (c - 'A'); + } else oops(); + } + + uint8_t Read8HexBits() { + uint8_t h = Read4HexBits(), + l = Read4HexBits(); + return (h << 4) + l; + } + + uint16_t Read16HexBits() { + uint16_t h = Read8HexBits(), + l = Read8HexBits(); + return (h << 8) + l; + } +}; + +//----------------------------------------------------------------------------- +// Bitmap font manipulation +//----------------------------------------------------------------------------- + +static const size_t CHARS_PER_ROW = BitmapFont::TEXTURE_DIM / 16; + +static uint8_t *BitmapFontTextureRow(uint8_t *texture, uint16_t position, size_t y) { + // position = 0; + size_t col = position % CHARS_PER_ROW, + row = position / CHARS_PER_ROW; + return &texture[BitmapFont::TEXTURE_DIM * (16 * row + y) + 16 * col]; +} + +BitmapFont BitmapFont::From(std::string &&unifontData) { + BitmapFont font = {}; + font.unifontData = std::move(unifontData); + font.texture = std::unique_ptr(new uint8_t[TEXTURE_DIM * TEXTURE_DIM]); + return font; +} + +void BitmapFont::AddGlyph(char32_t codepoint, const Pixmap &pixmap) { + if(pixmap.width != 8 && pixmap.width != 16 && pixmap.height != 16) oops(); + if(glyphs.find(codepoint) != glyphs.end()) oops(); + if(nextPosition == 0xffff) oops(); + + BitmapFont::Glyph glyph = {}; + glyph.advanceCells = pixmap.width / 8; + glyph.position = nextPosition++; + glyphs.emplace(codepoint, std::move(glyph)); + + for(size_t y = 0; y < pixmap.height; y++) { + uint8_t *row = BitmapFontTextureRow(texture.get(), glyph.position, y); + for(size_t x = 0; x < pixmap.width; x++) { + if(pixmap.GetPixel(x, y).ToPackedInt() != 0) { + row[x] = 255; + } + } + } +} + +const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) { + auto it = glyphs.find(codepoint); + if(it != glyphs.end()) { + return (*it).second; + } + + if(nextPosition == 0xffff) oops(); + + // Find the hex representation in the (sorted) Unifont file. + auto first = unifontData.cbegin(), + last = unifontData.cend(); + while(first <= last) { + auto mid = first + (last - first) / 2; + while(mid != unifontData.cbegin()) { + if(*mid == '\n') { + mid++; + break; + } + mid--; + } + + // Read the codepoint. + ASCIIReader reader = { mid, unifontData.cend() }; + char32_t foundCodepoint = reader.Read16HexBits(); + reader.ReadChar(':'); + + if(foundCodepoint > codepoint) { + last = mid - 1; + continue; // and first stays the same + } + if(foundCodepoint < codepoint) { + first = mid + 1; + while(first != unifontData.cend()) { + if(*first == '\n') break; + first++; + } + continue; // and last stays the same + } + + // Found the codepoint. + Glyph glyph = {}; + glyph.position = nextPosition++; + + // Read glyph bits. + unsigned short glyphBits[16]; + int glyphLength = reader.LengthToEOL(); + if(glyphLength == 4 * 16) { + glyph.advanceCells = 2; + for(size_t i = 0; i < 16; i++) { + glyphBits[i] = reader.Read16HexBits(); + } + } else if(glyphLength == 2 * 16) { + glyph.advanceCells = 1; + for(size_t i = 0; i < 16; i++) { + glyphBits[i] = (uint16_t)reader.Read8HexBits() << 8; + } + } else oops(); + + // Fill in the texture (one texture byte per glyph bit). + for(size_t y = 0; y < 16; y++) { + uint8_t *row = BitmapFontTextureRow(texture.get(), glyph.position, y); + for(size_t x = 0; x < 16; x++) { + if(glyphBits[y] & (1 << (15 - x))) { + row[x] = 255; + } + } + } + + it = glyphs.emplace(codepoint, std::move(glyph)).first; + return (*it).second; + } + + // Glyph doesn't exist; return replacement glyph instead. + if(codepoint == 0xfffd) oops(); + return GetGlyph(0xfffd); +} + +bool BitmapFont::LocateGlyph(char32_t codepoint, + double *s0, double *t0, double *s1, double *t1, + size_t *w, size_t *h) { + bool textureUpdated = (glyphs.find(codepoint) == glyphs.end()); + const Glyph &glyph = GetGlyph(codepoint); + *w = glyph.advanceCells * 8; + *h = 16; + *s0 = (16.0 * (glyph.position % CHARS_PER_ROW)) / TEXTURE_DIM; + *s1 = *s0 + (double)(*w) / TEXTURE_DIM; + *t0 = (16.0 * (glyph.position / CHARS_PER_ROW)) / TEXTURE_DIM; + *t1 = *t0 + (double)(*h) / TEXTURE_DIM; + return textureUpdated; +} + } diff --git a/src/resource.h b/src/resource.h index e597196c..a2f888d3 100644 --- a/src/resource.h +++ b/src/resource.h @@ -16,6 +16,7 @@ class Pixmap; const void *LoadResource(const std::string &name, size_t *size); std::string LoadString(const std::string &name); +std::string LoadStringFromGzip(const std::string &name); Pixmap LoadPNG(const std::string &name); class Pixmap { @@ -31,8 +32,33 @@ public: bool IsEmpty() const { return width == 0 && height == 0; } size_t GetBytesPerPixel() const { return hasAlpha ? 4 : 3; } + RgbaColor GetPixel(size_t x, size_t y) const; void Clear(); }; +class BitmapFont { +public: + struct Glyph { + uint8_t advanceCells; + uint16_t position; + }; + + static const size_t TEXTURE_DIM = 1024; + + std::string unifontData; + std::map glyphs; + std::unique_ptr texture; + uint16_t nextPosition; + + static BitmapFont From(std::string &&unifontData); + + bool IsEmpty() const { return unifontData.empty(); } + const Glyph &GetGlyph(char32_t codepoint); + bool LocateGlyph(char32_t codepoint, double *s0, double *t0, double *s1, double *t1, + size_t *advanceWidth, size_t *boundingHeight); + + void AddGlyph(char32_t codepoint, const Pixmap &pixmap); +}; + #endif diff --git a/src/solvespace.h b/src/solvespace.h index 165f16f8..41479a57 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -359,7 +359,7 @@ void ssglDepthRangeLockToFront(bool yes); void ssglDrawPixmap(const Pixmap &pixmap, bool flip = false); void ssglInitializeBitmapFont(); void ssglBitmapText(const std::string &str, Vector p); -void ssglBitmapCharQuad(char32_t chr, double x, double y); +double ssglBitmapCharQuad(char32_t chr, double x, double y); int ssglBitmapCharWidth(char32_t chr); #define TEXTURE_BACKGROUND_IMG 10 #define TEXTURE_DRAW_PIXELS 20 diff --git a/src/textwin.cpp b/src/textwin.cpp index 0249710e..0e9483a0 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -448,7 +448,7 @@ void TextWindow::DrawOrHitTestIcons(int how, double mx, double my) double ox = oldMousePos.x, oy = oldMousePos.y - LINE_HEIGHT; ox += 3; oy -= 3; - int tw = (str.length() + 1)*(CHAR_WIDTH - 1); + int tw = (str.length() + 1) * (CHAR_WIDTH - 1); ox = min(ox, (double) (width - 25) - tw); oy = max(oy, 5.0); @@ -872,7 +872,9 @@ void TextWindow::Paint(void) { } } else if(a == 1) { glColor3fv(&(fgColorTable[fg*3])); - ssglBitmapCharQuad(text[r][c], x, y + CHAR_HEIGHT); + if(text[r][c] != ' ') { + ssglBitmapCharQuad(text[r][c], x, y + CHAR_HEIGHT); + } // If this is a link and it's hovered, then draw the // underline diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 9f3e09db..cfea02df 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,15 +1,3 @@ -include_directories( - ${PNG_INCLUDE_DIRS}) - -link_directories( - ${PNG_LIBRARY_DIRS}) - -add_executable(unifont2c - unifont2c.cpp) - -target_link_libraries(unifont2c - ${PNG_LIBRARIES} ${ZLIB_LIBRARIES}) - add_executable(lff2c lff2c.cpp) diff --git a/tools/unifont2c.cpp b/tools/unifont2c.cpp deleted file mode 100644 index 7e9bf7a7..00000000 --- a/tools/unifont2c.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include -#include -#include -#include -#include - -#define die(msg) do { fprintf(stderr, "%s\n", msg); abort(); } while(0) - -#ifdef NDEBUG -#define COMPRESSION_LEVEL 9 -#else -#define COMPRESSION_LEVEL 5 -#endif - -unsigned short* read_png(const char *filename) { - FILE *fp = fopen(filename, "rb"); - if (!fp) - die("png fopen failed"); - - png_byte header[8] = {}; - if(fread(header, 1, 8, fp) != 8) - die("png fread failed"); - - if(png_sig_cmp(header, 0, 8)) - die("png_sig_cmp failed"); - - png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if(!png) - die("png_create_read_struct failed"); - - png_set_expand(png); - png_set_strip_alpha(png); - - png_infop png_info = png_create_info_struct(png); - if (!png_info) - die("png_create_info_struct failed"); - - if (setjmp(png_jmpbuf(png))) - die("png_init_io failed"); - - png_init_io(png, fp); - png_set_sig_bytes(png, 8); - - png_read_info(png, png_info); - - int width = png_get_image_width(png, png_info); - int height = png_get_image_height(png, png_info); - if(width != 16 || height != 16) - die("not a 16x16 png"); - - png_read_update_info(png, png_info); - - if (setjmp(png_jmpbuf(png))) - die("png_read_image failed"); - - png_bytepp image = (png_bytepp) malloc(sizeof(png_bytep) * height); - for (int y = 0; y < height; y++) - image[y] = (png_bytep) malloc(png_get_rowbytes(png, png_info)); - - png_read_image(png, (png_bytepp) image); - - unsigned short *glyph = (unsigned short *) calloc(16, 2); - - for(int y = 0; y < height; y++) { - for(int x = 0; x < (int)png_get_rowbytes(png, png_info); x += 3) { - unsigned char r = image[y][x + 0], - g = image[y][x + 1], - b = image[y][x + 2]; - - if(r + g + b >= 11) { - glyph[y] |= 1 << (width - x / 3); - } - } - } - - for (int y = 0; y < height; y++) - free(image[y]); - free(image); - - fclose(fp); - - png_destroy_read_struct(&png, &png_info, NULL); - - return glyph; -} - -const static unsigned short replacement[16] = { - 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, - 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, - 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, - 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, -}; - -struct CodepointProperties { - bool exists:1; - bool isWide:1; -}; - -int main(int argc, char** argv) { - if(argc < 3) { - fprintf(stderr, "Usage: %s
...\n" - " where s are mapped into private use area\n" - " starting at U+E000.\n", - argv[0]); - return 1; - } - - const int codepoint_count = 0x10000; - unsigned short **font = - (unsigned short **)calloc(sizeof(unsigned short*), codepoint_count); - CodepointProperties *properties = - (CodepointProperties *)calloc(sizeof(CodepointProperties), codepoint_count); - - const int private_start = 0xE000; - for(int i = 3; i < argc; i++) { - int codepoint = private_start + i - 3; - font[codepoint] = read_png(argv[i]); - properties[codepoint].exists = true; - } - - gzFile unifont = gzopen(argv[2], "rb"); - if(!unifont) - die("unifont fopen failed"); - - while(1) { - char buf[100]; - if(!gzgets(unifont, buf, sizeof(buf))){ - if(gzeof(unifont)) { - break; - } else { - die("unifont gzgets failed"); - } - } - - unsigned short codepoint; - unsigned short *glyph = (unsigned short *) calloc(32, 1); - bool isWide; - if( sscanf(buf, "%4hx:%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx" - "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx\n", - &codepoint, - &glyph[0], &glyph[1], &glyph[2], &glyph[3], - &glyph[4], &glyph[5], &glyph[6], &glyph[7], - &glyph[8], &glyph[9], &glyph[10], &glyph[11], - &glyph[12], &glyph[13], &glyph[14], &glyph[15]) == 17) { - /* read 16x16 character */ - isWide = true; - } else if(sscanf(buf, "%4hx:%2hx%2hx%2hx%2hx%2hx%2hx%2hx%2hx" - "%2hx%2hx%2hx%2hx%2hx%2hx%2hx%2hx\n", - &codepoint, - &glyph[0], &glyph[1], &glyph[2], &glyph[3], - &glyph[4], &glyph[5], &glyph[6], &glyph[7], - &glyph[8], &glyph[9], &glyph[10], &glyph[11], - &glyph[12], &glyph[13], &glyph[14], &glyph[15]) == 17) { - /* read 8x16 character */ - for(int i = 0; i < 16; i++) - glyph[i] <<= 8; - isWide = false; - } else { - die("parse unifont character"); - } - - font[codepoint] = glyph; - properties[codepoint].exists = true; - properties[codepoint].isWide = isWide; - } - - gzclose(unifont); - - FILE *source = fopen(argv[1], "wt"); - if(!source) - die("source fopen failed"); - - const int chunk_size = 64 * 64, - chunks = codepoint_count / chunk_size; - - const int chunk_input_size = chunk_size * 16 * 16; - unsigned int chunk_output_size[chunks] = {}; - - unsigned char *chunk_data = (unsigned char *)calloc(1, chunk_input_size); - unsigned int chunk_data_index; - - fprintf(source, "/**** This is a generated file - do not edit ****/\n\n"); - - for(int chunk_index = 0; chunk_index < chunks; chunk_index++) { - chunk_data_index = 0; - - const int chunk_start = chunk_index * chunk_size; - for(int codepoint = chunk_start; codepoint < chunk_start + chunk_size; codepoint++) { - const unsigned short *glyph = font[codepoint] != NULL ? font[codepoint] : replacement; - for(int x = 15; x >= 0; x--) { - for(int y = 0; y < 16; y++) { - chunk_data[chunk_data_index++] = (glyph[y] & (1 << x)) ? 0xff : 0; - } - } - - if(font[codepoint] != NULL) - free(font[codepoint]); - } - - fprintf(source, "static const uint8_t CompressedFontTextureChunk%d[] = {\n", - chunk_start / chunk_size); - - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.opaque = Z_NULL; - if(deflateInit(&stream, COMPRESSION_LEVEL) != Z_OK) - die("deflateInit failed"); - - stream.next_in = chunk_data; - stream.avail_in = chunk_input_size; - - do { - unsigned char compressed_chunk_data[16384] = {}; - stream.next_out = compressed_chunk_data; - stream.avail_out = sizeof(compressed_chunk_data); - deflate(&stream, Z_FINISH); - - chunk_output_size[chunk_index] += sizeof(compressed_chunk_data) - stream.avail_out; - for(size_t i = 0; i < sizeof(compressed_chunk_data) - stream.avail_out; i += 16) { - unsigned char *d = &compressed_chunk_data[i]; - fprintf(source, " %3d, %3d, %3d, %3d, %3d, %3d, %3d, %3d, " - "%3d, %3d, %3d, %3d, %3d, %3d, %3d, %3d,\n", - d[ 0], d[ 1], d[ 2], d[ 3], d[ 4], d[ 5], d[ 6], d[ 7], - d[ 8], d[ 9], d[10], d[11], d[12], d[13], d[14], d[15]); - } - } while(stream.avail_out == 0); - - deflateEnd(&stream); - - fprintf(source, "};\n\n"); - } - - free(chunk_data); - free(font); - - fprintf(source, "static const struct {\n" - " size_t length;" - " const uint8_t *data;" - "} CompressedFontTexture[%d] = {\n", chunks); - for(int i = 0; i < chunks; i++) { - fprintf(source, " { %d, CompressedFontTextureChunk%d },\n", - chunk_output_size[i], i); - } - fprintf(source, "};\n\n"); - - fprintf(source, "struct GlyphProperties {\n" - " bool exists:1;\n" - " bool isWide:1;\n" - "} CodepointProperties[%d] = {\n", codepoint_count); - for(int i = 0; i < codepoint_count; i++) { - fprintf(source, " { %s, %s },\n", - properties[i].exists ? "true" : "false", - properties[i].isWide ? "true" : "false"); - } - fprintf(source, "};\n"); - - free(properties); - - fclose(source); -}