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.pull/10/head
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B |
Before Width: | Height: | Size: 243 B After Width: | Height: | Size: 243 B |
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 228 B |
Before Width: | Height: | Size: 231 B After Width: | Height: | Size: 231 B |
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 936 B |
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
Before Width: | Height: | Size: 950 B After Width: | Height: | Size: 950 B |
|
@ -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 $<TARGET_FILE:unifont2c>
|
||||
${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 $<TARGET_FILE:lff2c>
|
||||
|
@ -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
|
||||
|
||||
|
|
244
src/glhelper.cpp
|
@ -3,7 +3,6 @@
|
|||
//
|
||||
// Copyright 2008-2013 Jonathan Westhues.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include <zlib.h>
|
||||
#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<uint8_t[]> 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);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
219
src/resource.cpp
|
@ -4,6 +4,7 @@
|
|||
// Copyright 2016 whitequark
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "solvespace.h"
|
||||
#include <zlib.h>
|
||||
#include <png.h>
|
||||
|
||||
namespace SolveSpace {
|
||||
|
@ -20,6 +21,37 @@ std::string LoadString(const std::string &name) {
|
|||
return std::string(static_cast<const char *>(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<uint8_t[]>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<char32_t, Glyph> glyphs;
|
||||
std::unique_ptr<uint8_t[]> 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -1,261 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <zlib.h>
|
||||
#include <png.h>
|
||||
|
||||
#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 <header/source out> <unifont.hex> <png glyph>...\n"
|
||||
" where <png glyph>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);
|
||||
}
|