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.
pull/4/head
whitequark 2015-11-05 22:39:27 +03:00
parent 31fd64af0a
commit 9d2a035a71
14 changed files with 323 additions and 136 deletions

View File

@ -306,6 +306,7 @@ add_executable(solvespace WIN32 MACOSX_BUNDLE
target_link_libraries(solvespace target_link_libraries(solvespace
"${OPENGL_LIBRARIES}" "${OPENGL_LIBRARIES}"
"${PNG_LIBRARIES}" "${PNG_LIBRARIES}"
"${ZLIB_LIBRARIES}"
"${platform_LIBRARIES}") "${platform_LIBRARIES}")
if(WIN32 AND NOT MINGW) if(WIN32 AND NOT MINGW)

View File

@ -237,25 +237,25 @@ void TextWindow::ShowConfiguration(void) {
&ScreenChangeExportOffset, 0); &ScreenChangeExportOffset, 0);
Printf(false, ""); 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, &ScreenChangeShadedTriangles,
SS.exportShadedTriangles ? CHECK_TRUE : CHECK_FALSE); SS.exportShadedTriangles ? CHECK_TRUE : CHECK_FALSE);
if(fabs(SS.exportOffset) > LENGTH_EPS) { 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); "(since cutter radius is not zero)", CHECK_TRUE);
} else { } 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, &ScreenChangePwlCurves,
SS.exportPwlCurves ? CHECK_TRUE : CHECK_FALSE); 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, &ScreenChangeFixExportColors,
SS.fixExportColors ? CHECK_TRUE : CHECK_FALSE); SS.fixExportColors ? CHECK_TRUE : CHECK_FALSE);
Printf(false, ""); Printf(false, "");
Printf(false, "%Ft export canvas size: " Printf(false, "%Ft export canvas size: "
"%f%Fd%Lf%c fixed%E " "%f%Fd%Lf%s fixed%E "
"%f%Fd%Lt%c auto%E", "%f%Fd%Lt%s auto%E",
&ScreenChangeCanvasSizeAuto, &ScreenChangeCanvasSizeAuto,
!SS.exportCanvasSizeAuto ? RADIO_TRUE : RADIO_FALSE, !SS.exportCanvasSizeAuto ? RADIO_TRUE : RADIO_FALSE,
&ScreenChangeCanvasSizeAuto, &ScreenChangeCanvasSizeAuto,
@ -295,10 +295,10 @@ void TextWindow::ShowConfiguration(void) {
SS.MmToString(SS.gCode.plungeFeed), &ScreenChangeGCodeParameter); SS.MmToString(SS.gCode.plungeFeed), &ScreenChangeGCodeParameter);
Printf(false, ""); 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, &ScreenChangeBackFaces,
SS.drawBackFaces ? CHECK_TRUE : CHECK_FALSE); 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, &ScreenChangeCheckClosedContour,
SS.checkClosedContour ? CHECK_TRUE : CHECK_FALSE); SS.checkClosedContour ? CHECK_TRUE : CHECK_FALSE);

View File

@ -306,7 +306,7 @@ void TextWindow::DescribeSelection(void) {
if(c->type == Constraint::DIAMETER) { if(c->type == Constraint::DIAMETER) {
Printf(false, "%FtDIAMETER CONSTRAINT"); 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], &ScreenConstraintShowAsRadius, gs.constraint[0],
c->other ? CHECK_TRUE : CHECK_FALSE); c->other ? CHECK_TRUE : CHECK_FALSE);
} else { } else {

View File

@ -213,7 +213,7 @@ void SolveSpaceUI::GenerateAll(int first, int last, bool andFindFree) {
glColor3d(0.0, 0.0, 0.0); glColor3d(0.0, 0.0, 0.0);
ssglAxisAlignedLineLoop(left, left+width, top, top-height); ssglAxisAlignedLineLoop(left, left+width, top, top-height);
ssglCreateBitmapFont(); ssglInitializeBitmapFont();
glColor3d(0, 0, 0); glColor3d(0, 0, 0);
glPushMatrix(); glPushMatrix();
glTranslated(left+8, top-20, 0); glTranslated(left+8, top-20, 0);

View File

@ -3,6 +3,7 @@
// //
// Copyright 2008-2013 Jonathan Westhues. // Copyright 2008-2013 Jonathan Westhues.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include <zlib.h>
#include "solvespace.h" #include "solvespace.h"
namespace SolveSpace { namespace SolveSpace {
@ -506,58 +507,126 @@ void ssglDepthRangeLockToFront(bool yes)
} }
} }
void ssglCreateBitmapFont(void) const int BitmapFontChunkSize = 64 * 64;
{ static bool BitmapFontChunkInitialized[0x10000 / BitmapFontChunkSize];
// Place the font in our texture in a two-dimensional grid; 1d would static int BitmapFontCurrentChunk = -1;
// 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;
for(i = 0; i < 16; i++) { static void CreateBitmapFontChunk(const uint8_t *source, size_t sourceLength,
memcpy(MappedTexture + row*4*16*16 + col*16 + i*4*16, int textureIndex)
FontTexture + a*16*16 + i*16, {
// 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); 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_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA,
16*4, 64*16, 16*64, 64*16,
0, 0,
GL_ALPHA, GL_UNSIGNED_BYTE, 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; int w, h;
if(b & 0x80) { h = 16;
if(chr >= 0xe000 && chr <= 0xefff) {
// Special character, like a checkbox or a radio button // Special character, like a checkbox or a radio button
w = h = 16; w = 16;
x -= 3; x -= 3;
} else if(CodepointProperties[chr].isWide) {
// Wide (usually CJK or reserved) character
w = 16;
} else { } else {
// Normal character from our font // Normal character
w = SS.TW.CHAR_WIDTH, w = 8;
h = SS.TW.CHAR_HEIGHT;
} }
if(b != ' ' && b != 0) { if(chr != ' ' && chr != 0) {
int row = b / 4, col = b % 4; int n = chr % BitmapFontChunkSize;
double s0 = col/4.0, int row = n / 64, col = n % 64;
s1 = (col+1)/4.0, double s0 = col/64.0,
s1 = (col+1)/64.0,
t0 = row/64.0, t0 = row/64.0,
t1 = t0 + (w/16.0)/64; t1 = t0 + (w/16.0)/64;
SwitchToBitmapFontChunkFor(chr);
glTexCoord2d(s1, t0); glTexCoord2d(s1, t0);
glVertex2d(x, y); glVertex2d(x, y);
@ -577,10 +646,11 @@ void ssglBitmapText(const char *str, Vector p)
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS); glBegin(GL_QUADS);
while(*str) { while(*str) {
ssglBitmapCharQuad(*str, p.x, p.y); char32_t chr;
str = ReadUTF8(str, &chr);
str++; ssglBitmapCharQuad(chr, p.x, p.y);
p.x += SS.TW.CHAR_WIDTH; p.x += 8 * ssglBitmapCharWidth(chr);
} }
glEnd(); glEnd();
glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_2D);

View File

@ -205,10 +205,8 @@ void Group::MenuGroup(int id) {
groupName.erase(pos); groupName.erase(pos);
for(int i = 0; i < groupName.length(); i++) { for(int i = 0; i < groupName.length(); i++) {
if(isalnum(groupName[i])) { if(!(isalnum(groupName[i]) || (unsigned)groupName[i] >= 0x80)) {
// do nothing, valid character // convert punctuation to dashes
} else {
// convert invalid characters (like spaces) to dashes
groupName[i] = '-'; groupName[i] = '-';
} }
} }

View File

@ -331,14 +331,15 @@ void ssglColorRGBa(RgbaColor rgb, double a);
void ssglDepthRangeOffset(int units); void ssglDepthRangeOffset(int units);
void ssglDepthRangeLockToFront(bool yes); void ssglDepthRangeLockToFront(bool yes);
void ssglDrawPixelsWithTexture(uint8_t *data, int w, int h); void ssglDrawPixelsWithTexture(uint8_t *data, int w, int h);
void ssglCreateBitmapFont(void); void ssglInitializeBitmapFont();
void ssglBitmapText(const char *str, Vector p); 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_BACKGROUND_IMG 10
#define TEXTURE_BITMAP_FONT 20 #define TEXTURE_DRAW_PIXELS 20
#define TEXTURE_DRAW_PIXELS 30 #define TEXTURE_COLOR_PICKER_2D 30
#define TEXTURE_COLOR_PICKER_2D 40 #define TEXTURE_COLOR_PICKER_1D 40
#define TEXTURE_COLOR_PICKER_1D 50 #define TEXTURE_BITMAP_FONT 50
#define arraylen(x) (sizeof((x))/sizeof((x)[0])) #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 a31, double a32, double a33, double a34,
double a41, double a42, double a43, double a44); double a41, double a42, double a43, double a44);
bool MakeAcceleratorLabel(int accel, char *out); bool MakeAcceleratorLabel(int accel, char *out);
const char *ReadUTF8(const char *str, char32_t *chr);
bool StringAllPrintable(const char *str); bool StringAllPrintable(const char *str);
bool FilenameHasExtension(const std::string &str, const char *ext); bool FilenameHasExtension(const std::string &str, const char *ext);
void Message(const char *str, ...); void Message(const char *str, ...);

View File

@ -695,8 +695,8 @@ bool TextWindow::EditControlDoneForStyles(const char *str) {
break; break;
} }
case EDIT_STYLE_NAME: case EDIT_STYLE_NAME:
if(!StringAllPrintable(str) || !*str) { if(!*str) {
Error("Invalid characters. Allowed are: A-Z a-z 0-9 _ -"); Error("Style name cannot be empty");
} else { } else {
SS.UndoRemember(); SS.UndoRemember();
s = Style::Get(edit.style); s = Style::Get(edit.style);
@ -760,8 +760,8 @@ void TextWindow::ShowStyleInfo(void) {
Printf(false,"%Ba %Ftin units of %Fdpixels%E"); Printf(false,"%Ba %Ftin units of %Fdpixels%E");
} else { } else {
Printf(false,"%Ba %Ftin units of %Fd" Printf(false,"%Ba %Ftin units of %Fd"
"%D%f%LW%c pixels%E " "%D%f%LW%s pixels%E "
"%D%f%Lw%c %s", "%D%f%Lw%s %s",
s->h.v, &ScreenChangeStyleYesNo, s->h.v, &ScreenChangeStyleYesNo,
widthpx ? RADIO_TRUE : RADIO_FALSE, widthpx ? RADIO_TRUE : RADIO_FALSE,
s->h.v, &ScreenChangeStyleYesNo, s->h.v, &ScreenChangeStyleYesNo,
@ -780,7 +780,7 @@ void TextWindow::ShowStyleInfo(void) {
s->fillColor.redF(), s->fillColor.greenF(), s->fillColor.blueF(), s->fillColor.redF(), s->fillColor.greenF(), s->fillColor.blueF(),
s->h.v, ScreenChangeStyleColor); 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->h.v, &ScreenChangeStyleYesNo,
s->filled ? CHECK_TRUE : CHECK_FALSE); s->filled ? CHECK_TRUE : CHECK_FALSE);
} }
@ -807,8 +807,8 @@ void TextWindow::ShowStyleInfo(void) {
Printf(false,"%Bd %Ftin units of %Fdpixels"); Printf(false,"%Bd %Ftin units of %Fdpixels");
} else { } else {
Printf(false,"%Bd %Ftin units of %Fd" Printf(false,"%Bd %Ftin units of %Fd"
"%D%f%LG%c pixels%E " "%D%f%LG%s pixels%E "
"%D%f%Lg%c %s", "%D%f%Lg%s %s",
s->h.v, &ScreenChangeStyleYesNo, s->h.v, &ScreenChangeStyleYesNo,
textHeightpx ? RADIO_TRUE : RADIO_FALSE, textHeightpx ? RADIO_TRUE : RADIO_FALSE,
s->h.v, &ScreenChangeStyleYesNo, s->h.v, &ScreenChangeStyleYesNo,
@ -826,9 +826,9 @@ void TextWindow::ShowStyleInfo(void) {
bool neither; bool neither;
neither = !(s->textOrigin & (Style::ORIGIN_LEFT | Style::ORIGIN_RIGHT)); neither = !(s->textOrigin & (Style::ORIGIN_LEFT | Style::ORIGIN_RIGHT));
Printf(false, "%Ba " Printf(false, "%Ba "
"%D%f%LL%c left%E " "%D%f%LL%s left%E "
"%D%f%LH%c center%E " "%D%f%LH%s center%E "
"%D%f%LR%c right%E ", "%D%f%LR%s right%E ",
s->h.v, &ScreenChangeStyleYesNo, s->h.v, &ScreenChangeStyleYesNo,
(s->textOrigin & Style::ORIGIN_LEFT) ? RADIO_TRUE : RADIO_FALSE, (s->textOrigin & Style::ORIGIN_LEFT) ? RADIO_TRUE : RADIO_FALSE,
s->h.v, &ScreenChangeStyleYesNo, s->h.v, &ScreenChangeStyleYesNo,
@ -838,9 +838,9 @@ void TextWindow::ShowStyleInfo(void) {
neither = !(s->textOrigin & (Style::ORIGIN_BOT | Style::ORIGIN_TOP)); neither = !(s->textOrigin & (Style::ORIGIN_BOT | Style::ORIGIN_TOP));
Printf(false, "%Bd " Printf(false, "%Bd "
"%D%f%LB%c bottom%E " "%D%f%LB%s bottom%E "
"%D%f%LV%c center%E " "%D%f%LV%s center%E "
"%D%f%LT%c top%E ", "%D%f%LT%s top%E ",
s->h.v, &ScreenChangeStyleYesNo, s->h.v, &ScreenChangeStyleYesNo,
(s->textOrigin & Style::ORIGIN_BOT) ? RADIO_TRUE : RADIO_FALSE, (s->textOrigin & Style::ORIGIN_BOT) ? RADIO_TRUE : RADIO_FALSE,
s->h.v, &ScreenChangeStyleYesNo, s->h.v, &ScreenChangeStyleYesNo,
@ -852,11 +852,11 @@ void TextWindow::ShowStyleInfo(void) {
if(s->h.v >= Style::FIRST_CUSTOM) { if(s->h.v >= Style::FIRST_CUSTOM) {
Printf(false, ""); 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->h.v, &ScreenChangeStyleYesNo,
s->visible ? CHECK_TRUE : CHECK_FALSE); 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->h.v, &ScreenChangeStyleYesNo,
s->exportable ? CHECK_TRUE : CHECK_FALSE); s->exportable ? CHECK_TRUE : CHECK_FALSE);

View File

@ -97,10 +97,10 @@ void TextWindow::ScreenGoToWebsite(int link, uint32_t v) {
OpenWebsite("http://solvespace.com/txtlink"); OpenWebsite("http://solvespace.com/txtlink");
} }
void TextWindow::ShowListOfGroups(void) { void TextWindow::ShowListOfGroups(void) {
char radioTrue[] = { ' ', (char)RADIO_TRUE, ' ', 0 }, const char *radioTrue = " " RADIO_TRUE " ",
radioFalse[] = { ' ', (char)RADIO_FALSE, ' ', 0 }, *radioFalse = " " RADIO_FALSE " ",
checkTrue[] = { ' ', (char)CHECK_TRUE, ' ', 0 }, *checkTrue = " " CHECK_TRUE " ",
checkFalse[] = { ' ', (char)CHECK_FALSE, ' ', 0 }; *checkFalse = " " CHECK_FALSE " ";
Printf(true, "%Ft active"); Printf(true, "%Ft active");
Printf(false, "%Ft shown ok group-name%E"); Printf(false, "%Ft shown ok group-name%E");
@ -304,8 +304,8 @@ void TextWindow::ShowGroupInfo(void) {
bool one = (g->subtype == Group::ONE_SIDED); bool one = (g->subtype == Group::ONE_SIDED);
Printf(false, Printf(false,
"%Ba %f%Ls%Fd%c one-sided%E " "%Ba %f%Ls%Fd%s one-sided%E "
"%f%LS%Fd%c two-sided%E", "%f%LS%Fd%s two-sided%E",
&TextWindow::ScreenChangeGroupOption, &TextWindow::ScreenChangeGroupOption,
one ? RADIO_TRUE : RADIO_FALSE, one ? RADIO_TRUE : RADIO_FALSE,
&TextWindow::ScreenChangeGroupOption, &TextWindow::ScreenChangeGroupOption,
@ -315,8 +315,8 @@ void TextWindow::ShowGroupInfo(void) {
if(g->subtype == Group::ONE_SIDED) { if(g->subtype == Group::ONE_SIDED) {
bool skip = g->skipFirst; bool skip = g->skipFirst;
Printf(false, Printf(false,
"%Bd %Ftstart %f%LK%Fd%c with original%E " "%Bd %Ftstart %f%LK%Fd%s with original%E "
"%f%Lk%Fd%c with copy #1%E", "%f%Lk%Fd%s with copy #1%E",
&ScreenChangeGroupOption, &ScreenChangeGroupOption,
!skip ? RADIO_TRUE : RADIO_FALSE, !skip ? RADIO_TRUE : RADIO_FALSE,
&ScreenChangeGroupOption, &ScreenChangeGroupOption,
@ -354,9 +354,9 @@ void TextWindow::ShowGroupInfo(void) {
bool asa = (g->type == Group::IMPORTED); bool asa = (g->type == Group::IMPORTED);
Printf(false, " %Ftsolid model as"); Printf(false, " %Ftsolid model as");
Printf(false, "%Ba %f%D%Lc%Fd%c union%E " Printf(false, "%Ba %f%D%Lc%Fd%s union%E "
"%f%D%Lc%Fd%c difference%E " "%f%D%Lc%Fd%s difference%E "
"%f%D%Lc%Fd%c%s%E ", "%f%D%Lc%Fd%s%s%E ",
&TextWindow::ScreenChangeGroupOption, &TextWindow::ScreenChangeGroupOption,
Group::COMBINE_AS_UNION, Group::COMBINE_AS_UNION,
un ? RADIO_TRUE : RADIO_FALSE, un ? RADIO_TRUE : RADIO_FALSE,
@ -365,7 +365,7 @@ void TextWindow::ShowGroupInfo(void) {
diff ? RADIO_TRUE : RADIO_FALSE, diff ? RADIO_TRUE : RADIO_FALSE,
&TextWindow::ScreenChangeGroupOption, &TextWindow::ScreenChangeGroupOption,
Group::COMBINE_AS_ASSEMBLE, Group::COMBINE_AS_ASSEMBLE,
asa ? (asy ? RADIO_TRUE : RADIO_FALSE) : 0, asa ? (asy ? RADIO_TRUE : RADIO_FALSE) : " ",
asa ? " assemble" : ""); asa ? " assemble" : "");
if(g->type == Group::EXTRUDE || if(g->type == Group::EXTRUDE ||
@ -381,7 +381,7 @@ void TextWindow::ShowGroupInfo(void) {
&TextWindow::ScreenOpacity); &TextWindow::ScreenOpacity);
} else if(g->type == Group::IMPORTED) { } else if(g->type == Group::IMPORTED) {
bool sup = g->suppress; 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, &TextWindow::ScreenChangeGroupOption,
g->suppress ? CHECK_TRUE : CHECK_FALSE); g->suppress ? CHECK_TRUE : CHECK_FALSE);
} }
@ -389,24 +389,24 @@ void TextWindow::ShowGroupInfo(void) {
Printf(false, ""); 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, &TextWindow::ScreenChangeGroupOption,
g->visible ? CHECK_TRUE : CHECK_FALSE); g->visible ? CHECK_TRUE : CHECK_FALSE);
Group *pg; pg = g->PreviousGroup(); Group *pg; pg = g->PreviousGroup();
if(pg && pg->runningMesh.IsEmpty() && g->thisMesh.IsEmpty()) { 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, &TextWindow::ScreenChangeGroupOption,
g->forceToMesh ? CHECK_TRUE : CHECK_FALSE); g->forceToMesh ? CHECK_TRUE : CHECK_FALSE);
} else { } else {
Printf(false, " (model already forced to triangle mesh)"); 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, &TextWindow::ScreenChangeGroupOption,
g->relaxConstraints ? CHECK_TRUE : CHECK_FALSE); 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, &TextWindow::ScreenChangeGroupOption,
g->allDimsReference ? CHECK_TRUE : CHECK_FALSE); g->allDimsReference ? CHECK_TRUE : CHECK_FALSE);
@ -604,10 +604,10 @@ void TextWindow::ShowTangentArc(void) {
} }
Printf(false, ""); Printf(false, "");
Printf(false, " %Fd%f%La%c choose radius automatically%E", Printf(false, " %Fd%f%La%s choose radius automatically%E",
&ScreenChangeTangentArc, &ScreenChangeTangentArc,
!SS.tangentArcManual ? CHECK_TRUE : CHECK_FALSE); !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, &ScreenChangeTangentArc,
SS.tangentArcDeleteOld ? CHECK_TRUE : CHECK_FALSE); SS.tangentArcDeleteOld ? CHECK_TRUE : CHECK_FALSE);
@ -665,8 +665,8 @@ void TextWindow::EditControlDone(const char *s) {
break; break;
} }
case EDIT_GROUP_NAME: { case EDIT_GROUP_NAME: {
if(!StringAllPrintable(s) || !*s) { if(!*s) {
Error("Invalid characters. Allowed are: A-Z a-z 0-9 _ -"); Error("Group name cannot be empty");
} else { } else {
SS.UndoRemember(); SS.UndoRemember();

View File

@ -250,13 +250,20 @@ void TextWindow::Printf(bool halfLine, const char *fmt, ...) {
break; break;
} }
} else { } else {
buf[0] = *fmt; char32_t chr;
buf[1] = '\0'; const char *fmtNext = ReadUTF8(fmt, &chr);
strncpy(buf, fmt, fmtNext - fmt);
buf[fmtNext - fmt] = '\0';
} }
for(unsigned i = 0; i < strlen(buf); i++) { 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; if(c >= MAX_COLS) goto done;
text[r][c] = buf[i]; text[r][c] = (i == 0) ? chr : ' ';
meta[r][c].fg = fg; meta[r][c].fg = fg;
meta[r][c].bg = bg; meta[r][c].bg = bg;
meta[r][c].bgRgb = bgRgb; meta[r][c].bgRgb = bgRgb;
@ -266,6 +273,7 @@ void TextWindow::Printf(bool halfLine, const char *fmt, ...) {
meta[r][c].h = h; meta[r][c].h = h;
c++; c++;
} }
}
fmt++; fmt++;
} }
@ -449,7 +457,7 @@ void TextWindow::DrawOrHitTestIcons(int how, double mx, double my)
ox = min(ox, (double) (width - 25) - tw); ox = min(ox, (double) (width - 25) - tw);
oy = max(oy, 5.0); oy = max(oy, 5.0);
ssglCreateBitmapFont(); ssglInitializeBitmapFont();
glLineWidth(1); glLineWidth(1);
glColor4d(1.0, 1.0, 0.6, 1.0); glColor4d(1.0, 1.0, 0.6, 1.0);
ssglAxisAlignedQuad(ox, ox+tw, oy, oy+LINE_HEIGHT); ssglAxisAlignedQuad(ox, ox+tw, oy, oy+LINE_HEIGHT);
@ -833,7 +841,7 @@ void TextWindow::Paint(void) {
glBegin(GL_QUADS); glBegin(GL_QUADS);
} else if(a == 1) { } else if(a == 1) {
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
ssglCreateBitmapFont(); ssglInitializeBitmapFont();
glBegin(GL_QUADS); glBegin(GL_QUADS);
} }
@ -895,7 +903,8 @@ void TextWindow::Paint(void) {
} }
// But don't underline checkboxes or radio buttons // But don't underline checkboxes or radio buttons
while((text[r][cs] & 0x80 || text[r][cs] == ' ') && while(((text[r][cs] >= 0xe000 && text[r][cs] <= 0xefff) ||
text[r][cs] == ' ') &&
cs < cf) cs < cf)
{ {
cs++; cs++;

View File

@ -204,7 +204,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
if(paint) { if(paint) {
// Do this last so that nothing can draw over it. // Do this last so that nothing can draw over it.
if(toolTip.show) { if(toolTip.show) {
ssglCreateBitmapFont(); ssglInitializeBitmapFont();
char str[1024]; char str[1024];
if(strlen(toolTip.str) >= 200) oops(); if(strlen(toolTip.str) >= 200) oops();
strcpy(str, toolTip.str); strcpy(str, toolTip.str);

View File

@ -31,17 +31,17 @@ public:
CHAR_HEIGHT = 16, CHAR_HEIGHT = 16,
LINE_HEIGHT = 20, LINE_HEIGHT = 20,
LEFT_MARGIN = 6, 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 scrollPos; // The scrollbar position, in half-row units
int halfRows; // The height of our window, 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); typedef void LinkFunction(int link, uint32_t v);
enum { NOT_A_LINK = 0 }; enum { NOT_A_LINK = 0 };
struct { struct {

View File

@ -6,6 +6,30 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "solvespace.h" #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) bool SolveSpace::StringAllPrintable(const char *str)
{ {
const char *t; const char *t;

View File

@ -3,10 +3,15 @@
#include <stdlib.h> #include <stdlib.h>
#include <zlib.h> #include <zlib.h>
#include <png.h> #include <png.h>
#include <map>
#define die(msg) do { fprintf(stderr, "%s\n", msg); abort(); } while(0) #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) { unsigned short* read_png(const char *filename) {
FILE *fp = fopen(filename, "rb"); FILE *fp = fopen(filename, "rb");
if (!fp) if (!fp)
@ -63,8 +68,7 @@ unsigned short* read_png(const char *filename) {
b = image[y][x + 2]; b = image[y][x + 2];
if(r + g + b >= 11) { if(r + g + b >= 11) {
int pos = y * width + (width - x / 3); glyph[y] |= 1 << (width - x / 3);
glyph[pos / 16] |= 1 << (pos % 16);
} }
} }
} }
@ -75,6 +79,8 @@ unsigned short* read_png(const char *filename) {
fclose(fp); fclose(fp);
png_destroy_read_struct(&png, &png_info, NULL);
return glyph; return glyph;
} }
@ -85,20 +91,31 @@ const static unsigned short replacement[16] = {
0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA,
}; };
struct CodepointProperties {
bool exists:1;
bool isWide:1;
};
int main(int argc, char** argv) { int main(int argc, char** argv) {
if(argc < 3) { if(argc < 3) {
fprintf(stderr, "Usage: %s <header/source out> <unifont.hex> <png glyph>...\n" fprintf(stderr, "Usage: %s <header/source out> <unifont.hex> <png glyph>...\n"
" where <png glyph>s are mapped into private use area\n" " where <png glyph>s are mapped into private use area\n"
" starting at U+0080.\n", " starting at U+E000.\n",
argv[0]); argv[0]);
return 1; 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++) { 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"); gzFile unifont = gzopen(argv[2], "rb");
@ -106,9 +123,6 @@ int main(int argc, char** argv) {
die("unifont fopen failed"); die("unifont fopen failed");
while(1) { while(1) {
unsigned short codepoint;
unsigned short *glyph = (unsigned short *) calloc(32, 1);
char buf[100]; char buf[100];
if(!gzgets(unifont, buf, sizeof(buf))){ if(!gzgets(unifont, buf, sizeof(buf))){
if(gzeof(unifont)) { 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" if( sscanf(buf, "%4hx:%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx"
"%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx\n", "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx\n",
&codepoint, &codepoint,
@ -126,6 +143,7 @@ int main(int argc, char** argv) {
&glyph[8], &glyph[9], &glyph[10], &glyph[11], &glyph[8], &glyph[9], &glyph[10], &glyph[11],
&glyph[12], &glyph[13], &glyph[14], &glyph[15]) == 17) { &glyph[12], &glyph[13], &glyph[14], &glyph[15]) == 17) {
/* read 16x16 character */ /* read 16x16 character */
isWide = true;
} else if(sscanf(buf, "%4hx:%2hx%2hx%2hx%2hx%2hx%2hx%2hx%2hx" } else if(sscanf(buf, "%4hx:%2hx%2hx%2hx%2hx%2hx%2hx%2hx%2hx"
"%2hx%2hx%2hx%2hx%2hx%2hx%2hx%2hx\n", "%2hx%2hx%2hx%2hx%2hx%2hx%2hx%2hx\n",
&codepoint, &codepoint,
@ -136,15 +154,14 @@ int main(int argc, char** argv) {
/* read 8x16 character */ /* read 8x16 character */
for(int i = 0; i < 16; i++) for(int i = 0; i < 16; i++)
glyph[i] <<= 8; glyph[i] <<= 8;
isWide = false;
} else { } else {
die("parse unifont character"); die("parse unifont character");
} }
if(codepoint >= 0x00 && codepoint < 0x80) {
font[codepoint] = glyph; font[codepoint] = glyph;
} else { properties[codepoint].exists = true;
free(glyph); properties[codepoint].isWide = isWide;
}
} }
gzclose(unifont); gzclose(unifont);
@ -153,26 +170,92 @@ int main(int argc, char** argv) {
if(!source) if(!source)
die("source fopen failed"); die("source fopen failed");
fprintf(source, "/**** This is a generated file - do not edit ****/\n\n"); const int chunk_size = 64 * 64,
fprintf(source, "static const unsigned char FontTexture[256 * 16 * 16] = {\n"); chunks = codepoint_count / chunk_size;
for(int codepoint = 0; codepoint < 0x100; codepoint++) { 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; const unsigned short *glyph = font[codepoint] != NULL ? font[codepoint] : replacement;
for(int x = 15; x >= 0; x--) { for(int x = 15; x >= 0; x--) {
for(int y = 0; y < 16; y++) { for(int y = 0; y < 16; y++) {
int pos = y * 16 + x; chunk_data[chunk_data_index++] = (glyph[y] & (1 << x)) ? 0xff : 0;
if(glyph[pos / 16] & (1 << (pos % 16))) {
fprintf(source, "255, ");
} else {
fprintf(source, " 0, ");
} }
} }
fprintf(source, "\n");
}
fprintf(source, "\n");
}
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(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"); fprintf(source, "};\n");
free(properties);
fclose(source); fclose(source);
} }