From 9d2a035a7193d9529adac3efa373755845cc8ec1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 5 Nov 2015 22:39:27 +0300 Subject: [PATCH] Rasterize non-ASCII glyphs in the UI. Now it is possible to give non-ASCII names to groups as well as see non-ASCII filenames of imported files. In the future this makes localization possible. This works for LTR languages, such as European and CJK, but not RTL such as Arabic. Does Arabic even exist in monospaced form? I have no idea. --- src/CMakeLists.txt | 1 + src/confscreen.cpp | 16 ++--- src/describescreen.cpp | 2 +- src/generate.cpp | 2 +- src/glhelper.cpp | 128 +++++++++++++++++++++++++++++--------- src/group.cpp | 6 +- src/solvespace.h | 14 +++-- src/style.cpp | 30 ++++----- src/textscreens.cpp | 42 ++++++------- src/textwin.cpp | 43 ++++++++----- src/toolbar.cpp | 2 +- src/ui.h | 12 ++-- src/util.cpp | 24 ++++++++ tools/unifont2c.cpp | 137 +++++++++++++++++++++++++++++++++-------- 14 files changed, 323 insertions(+), 136 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1ff0208..3765424 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -306,6 +306,7 @@ add_executable(solvespace WIN32 MACOSX_BUNDLE target_link_libraries(solvespace "${OPENGL_LIBRARIES}" "${PNG_LIBRARIES}" + "${ZLIB_LIBRARIES}" "${platform_LIBRARIES}") if(WIN32 AND NOT MINGW) diff --git a/src/confscreen.cpp b/src/confscreen.cpp index 9aceb3f..135998d 100644 --- a/src/confscreen.cpp +++ b/src/confscreen.cpp @@ -237,25 +237,25 @@ void TextWindow::ShowConfiguration(void) { &ScreenChangeExportOffset, 0); Printf(false, ""); - Printf(false, " %Fd%f%Ll%c export shaded 2d triangles%E", + Printf(false, " %Fd%f%Ll%s export shaded 2d triangles%E", &ScreenChangeShadedTriangles, SS.exportShadedTriangles ? CHECK_TRUE : CHECK_FALSE); if(fabs(SS.exportOffset) > LENGTH_EPS) { - Printf(false, " %Fd%c curves as piecewise linear%E " + Printf(false, " %Fd%s curves as piecewise linear%E " "(since cutter radius is not zero)", CHECK_TRUE); } else { - Printf(false, " %Fd%f%Ll%c export curves as piecewise linear%E", + Printf(false, " %Fd%f%Ll%s export curves as piecewise linear%E", &ScreenChangePwlCurves, SS.exportPwlCurves ? CHECK_TRUE : CHECK_FALSE); } - Printf(false, " %Fd%f%Ll%c fix white exported lines%E", + Printf(false, " %Fd%f%Ll%s fix white exported lines%E", &ScreenChangeFixExportColors, SS.fixExportColors ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "%Ft export canvas size: " - "%f%Fd%Lf%c fixed%E " - "%f%Fd%Lt%c auto%E", + "%f%Fd%Lf%s fixed%E " + "%f%Fd%Lt%s auto%E", &ScreenChangeCanvasSizeAuto, !SS.exportCanvasSizeAuto ? RADIO_TRUE : RADIO_FALSE, &ScreenChangeCanvasSizeAuto, @@ -295,10 +295,10 @@ void TextWindow::ShowConfiguration(void) { SS.MmToString(SS.gCode.plungeFeed), &ScreenChangeGCodeParameter); Printf(false, ""); - Printf(false, " %Fd%f%Ll%c draw triangle back faces in red%E", + Printf(false, " %Fd%f%Ll%s draw triangle back faces in red%E", &ScreenChangeBackFaces, SS.drawBackFaces ? CHECK_TRUE : CHECK_FALSE); - Printf(false, " %Fd%f%Ll%c check sketch for closed contour%E", + Printf(false, " %Fd%f%Ll%s check sketch for closed contour%E", &ScreenChangeCheckClosedContour, SS.checkClosedContour ? CHECK_TRUE : CHECK_FALSE); diff --git a/src/describescreen.cpp b/src/describescreen.cpp index d770ac8..7c4e752 100644 --- a/src/describescreen.cpp +++ b/src/describescreen.cpp @@ -306,7 +306,7 @@ void TextWindow::DescribeSelection(void) { if(c->type == Constraint::DIAMETER) { Printf(false, "%FtDIAMETER CONSTRAINT"); - Printf(true, " %Fd%f%D%Ll%c show as radius", + Printf(true, " %Fd%f%D%Ll%s show as radius", &ScreenConstraintShowAsRadius, gs.constraint[0], c->other ? CHECK_TRUE : CHECK_FALSE); } else { diff --git a/src/generate.cpp b/src/generate.cpp index 64060db..50ebedc 100644 --- a/src/generate.cpp +++ b/src/generate.cpp @@ -213,7 +213,7 @@ void SolveSpaceUI::GenerateAll(int first, int last, bool andFindFree) { glColor3d(0.0, 0.0, 0.0); ssglAxisAlignedLineLoop(left, left+width, top, top-height); - ssglCreateBitmapFont(); + ssglInitializeBitmapFont(); glColor3d(0, 0, 0); glPushMatrix(); glTranslated(left+8, top-20, 0); diff --git a/src/glhelper.cpp b/src/glhelper.cpp index 5dd4a18..85ac7e2 100644 --- a/src/glhelper.cpp +++ b/src/glhelper.cpp @@ -3,6 +3,7 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- +#include #include "solvespace.h" namespace SolveSpace { @@ -506,58 +507,126 @@ void ssglDepthRangeLockToFront(bool yes) } } -void ssglCreateBitmapFont(void) -{ - // Place the font in our texture in a two-dimensional grid; 1d would - // be simpler, but long skinny textures (256*16 = 4096 pixels wide) - // won't work. - static uint8_t MappedTexture[4*16*64*16]; - int a, i; - for(a = 0; a < 256; a++) { - int row = a / 4, col = a % 4; +const int BitmapFontChunkSize = 64 * 64; +static bool BitmapFontChunkInitialized[0x10000 / BitmapFontChunkSize]; +static int BitmapFontCurrentChunk = -1; - for(i = 0; i < 16; i++) { - memcpy(MappedTexture + row*4*16*16 + col*16 + i*4*16, - FontTexture + a*16*16 + i*16, +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); } } - glBindTexture(GL_TEXTURE_2D, TEXTURE_BITMAP_FONT); + 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*4, 64*16, + 16*64, 64*16, 0, GL_ALPHA, GL_UNSIGNED_BYTE, - MappedTexture); + mappedTexture); + + free(mappedTexture); } -void ssglBitmapCharQuad(char c, double x, double y) +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) + oops(); + return CodepointProperties[chr].isWide ? 2 : 1; +} + +void ssglBitmapCharQuad(char32_t chr, double x, double y) { - uint8_t b = (uint8_t)c; int w, h; - if(b & 0x80) { + h = 16; + if(chr >= 0xe000 && chr <= 0xefff) { // Special character, like a checkbox or a radio button - w = h = 16; + w = 16; x -= 3; + } else if(CodepointProperties[chr].isWide) { + // Wide (usually CJK or reserved) character + w = 16; } else { - // Normal character from our font - w = SS.TW.CHAR_WIDTH, - h = SS.TW.CHAR_HEIGHT; + // Normal character + w = 8; } - if(b != ' ' && b != 0) { - int row = b / 4, col = b % 4; - double s0 = col/4.0, - s1 = (col+1)/4.0, + 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); @@ -577,10 +646,11 @@ void ssglBitmapText(const char *str, Vector p) glEnable(GL_TEXTURE_2D); glBegin(GL_QUADS); while(*str) { - ssglBitmapCharQuad(*str, p.x, p.y); + char32_t chr; + str = ReadUTF8(str, &chr); - str++; - p.x += SS.TW.CHAR_WIDTH; + ssglBitmapCharQuad(chr, p.x, p.y); + p.x += 8 * ssglBitmapCharWidth(chr); } glEnd(); glDisable(GL_TEXTURE_2D); diff --git a/src/group.cpp b/src/group.cpp index 4017306..6842057 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -205,10 +205,8 @@ void Group::MenuGroup(int id) { groupName.erase(pos); for(int i = 0; i < groupName.length(); i++) { - if(isalnum(groupName[i])) { - // do nothing, valid character - } else { - // convert invalid characters (like spaces) to dashes + if(!(isalnum(groupName[i]) || (unsigned)groupName[i] >= 0x80)) { + // convert punctuation to dashes groupName[i] = '-'; } } diff --git a/src/solvespace.h b/src/solvespace.h index 9006088..3dcd3b5 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -331,14 +331,15 @@ void ssglColorRGBa(RgbaColor rgb, double a); void ssglDepthRangeOffset(int units); void ssglDepthRangeLockToFront(bool yes); void ssglDrawPixelsWithTexture(uint8_t *data, int w, int h); -void ssglCreateBitmapFont(void); +void ssglInitializeBitmapFont(); void ssglBitmapText(const char *str, Vector p); -void ssglBitmapCharQuad(char c, double x, double y); +void ssglBitmapCharQuad(char32_t chr, double x, double y); +int ssglBitmapCharWidth(char32_t chr); #define TEXTURE_BACKGROUND_IMG 10 -#define TEXTURE_BITMAP_FONT 20 -#define TEXTURE_DRAW_PIXELS 30 -#define TEXTURE_COLOR_PICKER_2D 40 -#define TEXTURE_COLOR_PICKER_1D 50 +#define TEXTURE_DRAW_PIXELS 20 +#define TEXTURE_COLOR_PICKER_2D 30 +#define TEXTURE_COLOR_PICKER_1D 40 +#define TEXTURE_BITMAP_FONT 50 #define arraylen(x) (sizeof((x))/sizeof((x)[0])) @@ -348,6 +349,7 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, double a31, double a32, double a33, double a34, double a41, double a42, double a43, double a44); bool MakeAcceleratorLabel(int accel, char *out); +const char *ReadUTF8(const char *str, char32_t *chr); bool StringAllPrintable(const char *str); bool FilenameHasExtension(const std::string &str, const char *ext); void Message(const char *str, ...); diff --git a/src/style.cpp b/src/style.cpp index 7855551..a6eaf01 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -695,8 +695,8 @@ bool TextWindow::EditControlDoneForStyles(const char *str) { break; } case EDIT_STYLE_NAME: - if(!StringAllPrintable(str) || !*str) { - Error("Invalid characters. Allowed are: A-Z a-z 0-9 _ -"); + if(!*str) { + Error("Style name cannot be empty"); } else { SS.UndoRemember(); s = Style::Get(edit.style); @@ -760,8 +760,8 @@ void TextWindow::ShowStyleInfo(void) { Printf(false,"%Ba %Ftin units of %Fdpixels%E"); } else { Printf(false,"%Ba %Ftin units of %Fd" - "%D%f%LW%c pixels%E " - "%D%f%Lw%c %s", + "%D%f%LW%s pixels%E " + "%D%f%Lw%s %s", s->h.v, &ScreenChangeStyleYesNo, widthpx ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, @@ -780,7 +780,7 @@ void TextWindow::ShowStyleInfo(void) { s->fillColor.redF(), s->fillColor.greenF(), s->fillColor.blueF(), s->h.v, ScreenChangeStyleColor); - Printf(false, "%Bd %D%f%Lf%c contours are filled%E", + Printf(false, "%Bd %D%f%Lf%s contours are filled%E", s->h.v, &ScreenChangeStyleYesNo, s->filled ? CHECK_TRUE : CHECK_FALSE); } @@ -807,8 +807,8 @@ void TextWindow::ShowStyleInfo(void) { Printf(false,"%Bd %Ftin units of %Fdpixels"); } else { Printf(false,"%Bd %Ftin units of %Fd" - "%D%f%LG%c pixels%E " - "%D%f%Lg%c %s", + "%D%f%LG%s pixels%E " + "%D%f%Lg%s %s", s->h.v, &ScreenChangeStyleYesNo, textHeightpx ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, @@ -826,9 +826,9 @@ void TextWindow::ShowStyleInfo(void) { bool neither; neither = !(s->textOrigin & (Style::ORIGIN_LEFT | Style::ORIGIN_RIGHT)); Printf(false, "%Ba " - "%D%f%LL%c left%E " - "%D%f%LH%c center%E " - "%D%f%LR%c right%E ", + "%D%f%LL%s left%E " + "%D%f%LH%s center%E " + "%D%f%LR%s right%E ", s->h.v, &ScreenChangeStyleYesNo, (s->textOrigin & Style::ORIGIN_LEFT) ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, @@ -838,9 +838,9 @@ void TextWindow::ShowStyleInfo(void) { neither = !(s->textOrigin & (Style::ORIGIN_BOT | Style::ORIGIN_TOP)); Printf(false, "%Bd " - "%D%f%LB%c bottom%E " - "%D%f%LV%c center%E " - "%D%f%LT%c top%E ", + "%D%f%LB%s bottom%E " + "%D%f%LV%s center%E " + "%D%f%LT%s top%E ", s->h.v, &ScreenChangeStyleYesNo, (s->textOrigin & Style::ORIGIN_BOT) ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, @@ -852,11 +852,11 @@ void TextWindow::ShowStyleInfo(void) { if(s->h.v >= Style::FIRST_CUSTOM) { Printf(false, ""); - Printf(false, " %Fd%D%f%Lv%c show these objects on screen%E", + Printf(false, " %Fd%D%f%Lv%s show these objects on screen%E", s->h.v, &ScreenChangeStyleYesNo, s->visible ? CHECK_TRUE : CHECK_FALSE); - Printf(false, " %Fd%D%f%Le%c export these objects%E", + Printf(false, " %Fd%D%f%Le%s export these objects%E", s->h.v, &ScreenChangeStyleYesNo, s->exportable ? CHECK_TRUE : CHECK_FALSE); diff --git a/src/textscreens.cpp b/src/textscreens.cpp index 9e32ba7..c8b320c 100644 --- a/src/textscreens.cpp +++ b/src/textscreens.cpp @@ -97,10 +97,10 @@ void TextWindow::ScreenGoToWebsite(int link, uint32_t v) { OpenWebsite("http://solvespace.com/txtlink"); } void TextWindow::ShowListOfGroups(void) { - char radioTrue[] = { ' ', (char)RADIO_TRUE, ' ', 0 }, - radioFalse[] = { ' ', (char)RADIO_FALSE, ' ', 0 }, - checkTrue[] = { ' ', (char)CHECK_TRUE, ' ', 0 }, - checkFalse[] = { ' ', (char)CHECK_FALSE, ' ', 0 }; + const char *radioTrue = " " RADIO_TRUE " ", + *radioFalse = " " RADIO_FALSE " ", + *checkTrue = " " CHECK_TRUE " ", + *checkFalse = " " CHECK_FALSE " "; Printf(true, "%Ft active"); Printf(false, "%Ft shown ok group-name%E"); @@ -304,8 +304,8 @@ void TextWindow::ShowGroupInfo(void) { bool one = (g->subtype == Group::ONE_SIDED); Printf(false, - "%Ba %f%Ls%Fd%c one-sided%E " - "%f%LS%Fd%c two-sided%E", + "%Ba %f%Ls%Fd%s one-sided%E " + "%f%LS%Fd%s two-sided%E", &TextWindow::ScreenChangeGroupOption, one ? RADIO_TRUE : RADIO_FALSE, &TextWindow::ScreenChangeGroupOption, @@ -315,8 +315,8 @@ void TextWindow::ShowGroupInfo(void) { if(g->subtype == Group::ONE_SIDED) { bool skip = g->skipFirst; Printf(false, - "%Bd %Ftstart %f%LK%Fd%c with original%E " - "%f%Lk%Fd%c with copy #1%E", + "%Bd %Ftstart %f%LK%Fd%s with original%E " + "%f%Lk%Fd%s with copy #1%E", &ScreenChangeGroupOption, !skip ? RADIO_TRUE : RADIO_FALSE, &ScreenChangeGroupOption, @@ -354,9 +354,9 @@ void TextWindow::ShowGroupInfo(void) { bool asa = (g->type == Group::IMPORTED); Printf(false, " %Ftsolid model as"); - Printf(false, "%Ba %f%D%Lc%Fd%c union%E " - "%f%D%Lc%Fd%c difference%E " - "%f%D%Lc%Fd%c%s%E ", + Printf(false, "%Ba %f%D%Lc%Fd%s union%E " + "%f%D%Lc%Fd%s difference%E " + "%f%D%Lc%Fd%s%s%E ", &TextWindow::ScreenChangeGroupOption, Group::COMBINE_AS_UNION, un ? RADIO_TRUE : RADIO_FALSE, @@ -365,7 +365,7 @@ void TextWindow::ShowGroupInfo(void) { diff ? RADIO_TRUE : RADIO_FALSE, &TextWindow::ScreenChangeGroupOption, Group::COMBINE_AS_ASSEMBLE, - asa ? (asy ? RADIO_TRUE : RADIO_FALSE) : 0, + asa ? (asy ? RADIO_TRUE : RADIO_FALSE) : " ", asa ? " assemble" : ""); if(g->type == Group::EXTRUDE || @@ -381,7 +381,7 @@ void TextWindow::ShowGroupInfo(void) { &TextWindow::ScreenOpacity); } else if(g->type == Group::IMPORTED) { bool sup = g->suppress; - Printf(false, " %Fd%f%LP%c suppress this group's solid model", + Printf(false, " %Fd%f%LP%s suppress this group's solid model", &TextWindow::ScreenChangeGroupOption, g->suppress ? CHECK_TRUE : CHECK_FALSE); } @@ -389,24 +389,24 @@ void TextWindow::ShowGroupInfo(void) { Printf(false, ""); } - Printf(false, " %f%Lv%Fd%c show entities from this group", + Printf(false, " %f%Lv%Fd%s show entities from this group", &TextWindow::ScreenChangeGroupOption, g->visible ? CHECK_TRUE : CHECK_FALSE); Group *pg; pg = g->PreviousGroup(); if(pg && pg->runningMesh.IsEmpty() && g->thisMesh.IsEmpty()) { - Printf(false, " %f%Lf%Fd%c force NURBS surfaces to triangle mesh", + Printf(false, " %f%Lf%Fd%s force NURBS surfaces to triangle mesh", &TextWindow::ScreenChangeGroupOption, g->forceToMesh ? CHECK_TRUE : CHECK_FALSE); } else { Printf(false, " (model already forced to triangle mesh)"); } - Printf(true, " %f%Lr%Fd%c relax constraints and dimensions", + Printf(true, " %f%Lr%Fd%s relax constraints and dimensions", &TextWindow::ScreenChangeGroupOption, g->relaxConstraints ? CHECK_TRUE : CHECK_FALSE); - Printf(false, " %f%Ld%Fd%c treat all dimensions as reference", + Printf(false, " %f%Ld%Fd%s treat all dimensions as reference", &TextWindow::ScreenChangeGroupOption, g->allDimsReference ? CHECK_TRUE : CHECK_FALSE); @@ -604,10 +604,10 @@ void TextWindow::ShowTangentArc(void) { } Printf(false, ""); - Printf(false, " %Fd%f%La%c choose radius automatically%E", + Printf(false, " %Fd%f%La%s choose radius automatically%E", &ScreenChangeTangentArc, !SS.tangentArcManual ? CHECK_TRUE : CHECK_FALSE); - Printf(false, " %Fd%f%Ld%c delete original entities afterward%E", + Printf(false, " %Fd%f%Ld%s delete original entities afterward%E", &ScreenChangeTangentArc, SS.tangentArcDeleteOld ? CHECK_TRUE : CHECK_FALSE); @@ -665,8 +665,8 @@ void TextWindow::EditControlDone(const char *s) { break; } case EDIT_GROUP_NAME: { - if(!StringAllPrintable(s) || !*s) { - Error("Invalid characters. Allowed are: A-Z a-z 0-9 _ -"); + if(!*s) { + Error("Group name cannot be empty"); } else { SS.UndoRemember(); diff --git a/src/textwin.cpp b/src/textwin.cpp index 5805702..b4a7c90 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -250,21 +250,29 @@ void TextWindow::Printf(bool halfLine, const char *fmt, ...) { break; } } else { - buf[0] = *fmt; - buf[1] = '\0'; + char32_t chr; + const char *fmtNext = ReadUTF8(fmt, &chr); + strncpy(buf, fmt, fmtNext - fmt); + buf[fmtNext - fmt] = '\0'; } - for(unsigned i = 0; i < strlen(buf); i++) { - if(c >= MAX_COLS) goto done; - text[r][c] = buf[i]; - meta[r][c].fg = fg; - meta[r][c].bg = bg; - meta[r][c].bgRgb = bgRgb; - meta[r][c].link = link; - meta[r][c].data = data; - meta[r][c].f = f; - meta[r][c].h = h; - c++; + const char *bufIter = buf; + while(*bufIter) { + char32_t chr; + bufIter = ReadUTF8(bufIter, &chr); + + for(int i = 0; i < ssglBitmapCharWidth(chr); i++) { + if(c >= MAX_COLS) goto done; + text[r][c] = (i == 0) ? chr : ' '; + meta[r][c].fg = fg; + meta[r][c].bg = bg; + meta[r][c].bgRgb = bgRgb; + meta[r][c].link = link; + meta[r][c].data = data; + meta[r][c].f = f; + meta[r][c].h = h; + c++; + } } fmt++; @@ -449,7 +457,7 @@ void TextWindow::DrawOrHitTestIcons(int how, double mx, double my) ox = min(ox, (double) (width - 25) - tw); oy = max(oy, 5.0); - ssglCreateBitmapFont(); + ssglInitializeBitmapFont(); glLineWidth(1); glColor4d(1.0, 1.0, 0.6, 1.0); ssglAxisAlignedQuad(ox, ox+tw, oy, oy+LINE_HEIGHT); @@ -833,7 +841,7 @@ void TextWindow::Paint(void) { glBegin(GL_QUADS); } else if(a == 1) { glEnable(GL_TEXTURE_2D); - ssglCreateBitmapFont(); + ssglInitializeBitmapFont(); glBegin(GL_QUADS); } @@ -895,8 +903,9 @@ void TextWindow::Paint(void) { } // But don't underline checkboxes or radio buttons - while((text[r][cs] & 0x80 || text[r][cs] == ' ') && - cs < cf) + while(((text[r][cs] >= 0xe000 && text[r][cs] <= 0xefff) || + text[r][cs] == ' ') && + cs < cf) { cs++; } diff --git a/src/toolbar.cpp b/src/toolbar.cpp index ab05b72..849f8ea 100644 --- a/src/toolbar.cpp +++ b/src/toolbar.cpp @@ -204,7 +204,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, if(paint) { // Do this last so that nothing can draw over it. if(toolTip.show) { - ssglCreateBitmapFont(); + ssglInitializeBitmapFont(); char str[1024]; if(strlen(toolTip.str) >= 200) oops(); strcpy(str, toolTip.str); diff --git a/src/ui.h b/src/ui.h index e068dd0..ed73f78 100644 --- a/src/ui.h +++ b/src/ui.h @@ -31,17 +31,17 @@ public: CHAR_HEIGHT = 16, LINE_HEIGHT = 20, LEFT_MARGIN = 6, - - CHECK_FALSE = 0x80, - CHECK_TRUE = 0x81, - RADIO_FALSE = 0x82, - RADIO_TRUE = 0x83 }; +#define CHECK_FALSE "\xEE\x80\x80" // U+E000 +#define CHECK_TRUE "\xEE\x80\x81" +#define RADIO_FALSE "\xEE\x80\x82" +#define RADIO_TRUE "\xEE\x80\x83" + int scrollPos; // The scrollbar position, in half-row units int halfRows; // The height of our window, in half-row units - uint8_t text[MAX_ROWS][MAX_COLS]; + uint32_t text[MAX_ROWS][MAX_COLS]; typedef void LinkFunction(int link, uint32_t v); enum { NOT_A_LINK = 0 }; struct { diff --git a/src/util.cpp b/src/util.cpp index 58079b7..633b15b 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -6,6 +6,30 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +// See https://github.com/GNOME/glibmm/blob/2fbd9f23/glib/glibmm/ustring.cc#L227 +const char *SolveSpace::ReadUTF8(const char *str, char32_t *result) +{ + *result = (unsigned char) *str; + + if((*result & 0x80) != 0) + { + unsigned int mask = 0x40; + + do + { + *result <<= 6; + const unsigned int c = (unsigned char) (*++str); + mask <<= 5; + *result += c - 0x80; + } + while((*result & mask) != 0); + + *result &= mask - 1; + } + + return str + 1; +} + bool SolveSpace::StringAllPrintable(const char *str) { const char *t; diff --git a/tools/unifont2c.cpp b/tools/unifont2c.cpp index d3d0545..8def106 100644 --- a/tools/unifont2c.cpp +++ b/tools/unifont2c.cpp @@ -3,10 +3,15 @@ #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) @@ -63,8 +68,7 @@ unsigned short* read_png(const char *filename) { b = image[y][x + 2]; if(r + g + b >= 11) { - int pos = y * width + (width - x / 3); - glyph[pos / 16] |= 1 << (pos % 16); + glyph[y] |= 1 << (width - x / 3); } } } @@ -75,6 +79,8 @@ unsigned short* read_png(const char *filename) { fclose(fp); + png_destroy_read_struct(&png, &png_info, NULL); + return glyph; } @@ -85,20 +91,31 @@ const static unsigned short replacement[16] = { 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+0080.\n", + " starting at U+E000.\n", argv[0]); return 1; } - unsigned short *font[256] = {}; + 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 = 0x80, private_count = argc - 3; + const int private_start = 0xE000; for(int i = 3; i < argc; i++) { - font[private_start + i - 3] = read_png(argv[i]); + int codepoint = private_start + i - 3; + font[codepoint] = read_png(argv[i]); + properties[codepoint].exists = true; } gzFile unifont = gzopen(argv[2], "rb"); @@ -106,9 +123,6 @@ int main(int argc, char** argv) { die("unifont fopen failed"); while(1) { - unsigned short codepoint; - unsigned short *glyph = (unsigned short *) calloc(32, 1); - char buf[100]; if(!gzgets(unifont, buf, sizeof(buf))){ if(gzeof(unifont)) { @@ -118,6 +132,9 @@ int main(int argc, char** argv) { } } + 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, @@ -126,6 +143,7 @@ int main(int argc, char** argv) { &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, @@ -136,15 +154,14 @@ int main(int argc, char** argv) { /* read 8x16 character */ for(int i = 0; i < 16; i++) glyph[i] <<= 8; + isWide = false; } else { die("parse unifont character"); } - if(codepoint >= 0x00 && codepoint < 0x80) { - font[codepoint] = glyph; - } else { - free(glyph); - } + font[codepoint] = glyph; + properties[codepoint].exists = true; + properties[codepoint].isWide = isWide; } gzclose(unifont); @@ -153,26 +170,92 @@ int main(int argc, char** argv) { if(!source) die("source fopen failed"); - fprintf(source, "/**** This is a generated file - do not edit ****/\n\n"); - fprintf(source, "static const unsigned char FontTexture[256 * 16 * 16] = {\n"); + const int chunk_size = 64 * 64, + chunks = codepoint_count / chunk_size; - for(int codepoint = 0; codepoint < 0x100; codepoint++) { - const unsigned short *glyph = font[codepoint] != NULL ? font[codepoint] : replacement; - for(int x = 15; x >= 0; x--) { + 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++) { - int pos = y * 16 + x; - if(glyph[pos / 16] & (1 << (pos % 16))) { - fprintf(source, "255, "); - } else { - fprintf(source, " 0, "); + chunk_data[chunk_data_index++] = (glyph[y] & (1 << x)) ? 0xff : 0; } } - fprintf(source, "\n"); + + if(font[codepoint] != NULL) + free(font[codepoint]); } - fprintf(source, "\n"); + + 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(int 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); }