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); -}