diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1ff02088..37654247 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 9aceb3f4..135998d2 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 d770ac85..7c4e752f 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 64060db4..50ebedca 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 5dd4a188..85ac7e2d 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 40173063..68420571 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 90060880..3dcd3b58 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 78555510..a6eaf013 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 9e32ba72..c8b320c4 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 58057024..b4a7c907 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 ab05b724..849f8eae 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 e068dd09..ed73f780 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 58079b7a..633b15bc 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 d3d0545b..8def106a 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); }