From c0c550e920c037b3a85eb2fed92f403f4137205d Mon Sep 17 00:00:00 2001 From: mniip Date: Sun, 29 Apr 2018 18:49:40 +0300 Subject: [PATCH] Add a new font editor --- SConscript | 16 +- data/font.h | 5 + src/PowderToySDL.cpp | 9 + src/gui/font/FontEditor.cpp | 544 ++++++++++++++++++++++++++++++++++++ src/gui/font/FontEditor.h | 67 +++++ 5 files changed, 637 insertions(+), 4 deletions(-) create mode 100644 src/gui/font/FontEditor.cpp create mode 100644 src/gui/font/FontEditor.h diff --git a/SConscript b/SConscript index 03e93414b..6cbacf58a 100644 --- a/SConscript +++ b/SConscript @@ -67,6 +67,7 @@ AddSconsOption('static', False, False, "Compile statically.") AddSconsOption('opengl', False, False, "Build with OpenGL interface support.") AddSconsOption('opengl-renderer', False, False, "Build with OpenGL renderer support (turns on --opengl).") #Note: this has nothing to do with --renderer, only tells the game to render particles with opengl AddSconsOption('renderer', False, False, "Build the save renderer.") +AddSconsOption('font', False, False, "Build the font editor.") AddSconsOption('wall', False, False, "Error on all warnings.") AddSconsOption('no-warnings', False, False, "Disable all compiler warnings.") @@ -266,7 +267,7 @@ def findLibs(env, conf): else: FatalError("SDL.h not found") - if not GetOption('nolua') and not GetOption('renderer'): + if not GetOption('nolua') and not GetOption('renderer') and not GetOption('font'): #Look for Lua if platform == "FreeBSD": luaver = "lua-5.1" @@ -494,7 +495,7 @@ if GetOption('static'): #Add other flags and defines if not GetOption('nofft'): env.Append(CPPDEFINES=['GRAVFFT']) -if not GetOption('nolua') and not GetOption('renderer'): +if not GetOption('nolua') and not GetOption('renderer') and not GetOption('font'): env.Append(CPPDEFINES=['LUACONSOLE']) if GetOption('opengl') or GetOption('opengl-renderer'): @@ -507,6 +508,9 @@ if GetOption('renderer'): else: env.Append(CPPDEFINES=['USE_SDL']) +if GetOption('font'): + env.Append(CPPDEFINES=['FONTEDITOR']) + if GetOption("wall"): if msvc: env.Append(CCFLAGS=['/WX']) @@ -540,7 +544,7 @@ if GetOption('beta'): #Generate list of sources to compile sources = Glob("src/*.cpp") + Glob("src/*/*.cpp") + Glob("src/*/*/*.cpp") + Glob("generated/*.cpp") -if not GetOption('nolua') and not GetOption('renderer'): +if not GetOption('nolua') and not GetOption('renderer') and not GetOption('font'): sources += Glob("src/lua/socket/*.c") + Glob("src/lua/LuaCompat.c") if platform == "Windows": @@ -559,7 +563,11 @@ elif platform == "Darwin": if GetOption('output'): programName = GetOption('output') else: - programName = GetOption('renderer') and "render" or "powder" + programName = "powder" + if GetOption('renderer'): + programName = "render" + if GetOption('font'): + programName = "font" if "BIT" in env and env["BIT"] == 64: programName += "64" if isX86 and GetOption('no-sse'): diff --git a/data/font.h b/data/font.h index 80506b85b..b2ead7614 100644 --- a/data/font.h +++ b/data/font.h @@ -1,6 +1,7 @@ #ifndef FONT_H_CHECK #define FONT_H_CHECK #define FONT_H 12 +#ifndef FONTEDITOR #ifdef INCLUDE_FONTDATA unsigned char font_data[] = { 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -299,5 +300,9 @@ short font_ptrs[] = { extern unsigned char font_data[]; extern short font_ptrs[]; #endif +#else +extern unsigned char *font_data; +extern short *font_ptrs; +#endif #endif diff --git a/src/PowderToySDL.cpp b/src/PowderToySDL.cpp index a52d7ef34..45d3fed79 100644 --- a/src/PowderToySDL.cpp +++ b/src/PowderToySDL.cpp @@ -50,6 +50,8 @@ extern "C" { #include "gui/game/GameController.h" #include "gui/game/GameView.h" +#include "gui/font/FontEditor.h" + #include "gui/dialogues/ErrorMessage.h" #include "gui/dialogues/ConfirmPrompt.h" #include "gui/interface/Keys.h" @@ -1108,6 +1110,7 @@ int main(int argc, char * argv[]) try { #endif +#ifndef FONTEDITOR gameController = new GameController(); engine->ShowWindow(gameController->GetView()); @@ -1204,6 +1207,12 @@ int main(int argc, char * argv[]) } } +#else // FONTEDITOR + if(argc <= 1) + throw std::runtime_error("Not enough arguments"); + engine->ShowWindow(new FontEditor(argv[1])); +#endif + //initial mouse coords int sdl_x, sdl_y; SDL_GetMouseState(&sdl_x, &sdl_y); diff --git a/src/gui/font/FontEditor.cpp b/src/gui/font/FontEditor.cpp new file mode 100644 index 000000000..c66f85931 --- /dev/null +++ b/src/gui/font/FontEditor.cpp @@ -0,0 +1,544 @@ +#include +#include +#include +#include +#include +#include + +#include "FontEditor.h" + +#include "Config.h" +#include "gui/interface/Engine.h" +#include "gui/interface/Point.h" +#include "gui/interface/Button.h" +#include "gui/interface/Mouse.h" +#include "gui/interface/Keys.h" +#include "graphics/Graphics.h" + +#ifdef FONTEDITOR +unsigned char *font_data; +short *font_ptrs; + +void FontEditor::ReadHeader(std::string header) +{ + std::fstream file; + file.open(header, std::ios_base::in); + if(!file) + throw std::runtime_error("Could not open " + header); + file >> std::skipws; + + std::string word; + + while(word != "font_data[]") + file >> word; + file >> word >> word; + + size_t startFontData = file.tellg(); + + fontData.clear(); + do + { + unsigned int value; + file >> std::hex >> value; + if(!file.fail()) + { + fontData.push_back(value); + file >> word; + } + } + while(!file.fail()); + file.clear(); + + size_t endFontData = file.tellg(); + + while(word != "font_ptrs[]") + file >> word; + file >> word >> word; + + size_t startFontPtrs = file.tellg(); + + fontPtrs.clear(); + do + { + unsigned int value; + file >> std::hex >> value; + if(!file.fail()) + { + fontPtrs.push_back(value); + file >> word; + } + } + while(!file.fail()); + file.clear(); + + size_t endFontPtrs = file.tellg(); + + do + { + file >> word; + } + while(!file.fail()); + file.clear(); + size_t eof = file.tellg(); + + file.seekg(0); + beforeFontData = std::string(startFontData, 0); + file.read(&beforeFontData[0], startFontData); + + file.seekg(endFontData); + afterFontData = std::string(startFontPtrs - endFontData, 0); + file.read(&afterFontData[0], startFontPtrs - endFontData); + + file.seekg(endFontData); + afterFontData = std::string(startFontPtrs - endFontData, 0); + file.read(&afterFontData[0], startFontPtrs - endFontData); + + file.seekg(endFontPtrs); + afterFontPtrs = std::string(eof - endFontPtrs, 0); + file.read(&afterFontPtrs[0], eof - endFontPtrs); + file.close(); +} + +void FontEditor::WriteHeader(std::string header, std::vector const &fontData, std::vector const &fontPtrs) +{ + std::fstream file; + file.open(header, std::ios_base::out | std::ios_base::trunc); + if(!file) + throw std::runtime_error("Could not open " + header); + + file << std::setfill('0') << std::hex << std::uppercase; + file << beforeFontData << std::endl; + for(int ch = 0; ch < 256; ch++) + { + file << " " << "0x" << std::setw(2) << (unsigned int)fontData[fontPtrs[ch]] << ", "; + for(int i = fontPtrs[ch] + 1; i < (int)(ch == (int)fontPtrs.size() - 1 ? fontData.size() : fontPtrs[ch + 1]); i++) + file << " " << "0x" << std::setw(2) << (unsigned int)fontData[i] << ","; + file << std::endl; + } + file << afterFontData; + for(int ch = 0; ch < 256; ch++) + { + if(!(ch & 7)) + file << std::endl << " "; + else + file << " "; + file << "0x" << std::setw(4) << (unsigned int)fontPtrs[ch] << ","; + } + file << std::endl << afterFontPtrs; + file.close(); +} + +void FontEditor::UnpackData( + std::array &fontWidths, + std::array, FONT_H>, 256> &fontPixels, + std::vector const &fontData, + std::vector const &fontPtrs) +{ + for(int ch = 0; ch < 256; ch++) + { + unsigned char const *data = &fontData[fontPtrs[ch]]; + int bits = 0; + int pixels = 0; + int width = fontWidths[ch] = *(data++); + for(int j = 0; j < FONT_H; j++) + for(int i = 0; i < width; i++) + { + if(!bits) + { + pixels = *(data++); + bits = 8; + } + fontPixels[ch][j][i] = pixels & 3; + pixels >>= 2; + bits -= 2; + } + } +} + +void FontEditor::PackData( + std::array const &fontWidths, + std::array, FONT_H>, 256> const &fontPixels, + std::vector &fontData, + std::vector &fontPtrs) +{ + fontPtrs.clear(); + fontData.clear(); + for(int ch = 0; ch < 256; ch++) + { + fontPtrs.push_back(fontData.size()); + fontData.push_back(fontWidths[ch]); + + int bits = 0; + int pixels = 0; + for(int j = 0; j < FONT_H; j++) + for(int i = 0; i < fontWidths[ch]; i++) + { + if(bits == 8) + { + fontData.push_back(pixels); + bits = 0; + pixels = 0; + } + pixels >>= 2; + pixels |= fontPixels[ch][j][i] << 6; + bits += 2; + } + if(bits) + fontData.push_back(pixels); + } +} + +#define FONT_SCALE 16 +FontEditor::FontEditor(std::string _header): + ui::Window(ui::Point(0, 0), ui::Point(WINDOWW, WINDOWH)), + header(_header), + currentChar(0x80), + fgR(255), fgG(255), fgB(255), bgR(0), bgG(0), bgB(0), + grid(1), + rulers(1) +{ + ReadHeader(header); + UnpackData(fontWidths, fontPixels, fontData, fontPtrs); + font_data = fontData.data(); + font_ptrs = fontPtrs.data(); + + int baseline = 8 + FONT_H * FONT_SCALE + 4 + FONT_H + 4 + 1; + int currentX = 1; + + class PrevCharAction : public ui::ButtonAction + { + FontEditor *v; + public: + PrevCharAction(FontEditor *_v): v(_v) {} + void ActionCallback(ui::Button *) + { + v->PrevChar(); + } + }; + ui::Button *prev = new ui::Button(ui::Point(currentX, baseline), ui::Point(17, 17), "\x96"); + currentX += 18; + prev->SetActionCallback(new PrevCharAction(this)); + AddComponent(prev); + + class CharNumberAction : public ui::TextboxAction + { + FontEditor *v; + public: + CharNumberAction(FontEditor *_v): v(_v) {} + void TextChangedCallback(ui::Textbox *) + { + unsigned int number; + std::stringstream ss(v->currentCharTextbox->GetText()); + ss >> std::hex >> number; + if(number < 256) + v->currentChar = number; + } + }; + currentCharTextbox = new ui::Textbox(ui::Point(currentX, baseline), ui::Point(31, 17)); + currentX += 32; + currentCharTextbox->SetActionCallback(new CharNumberAction(this)); + UpdateCharNumber(); + AddComponent(currentCharTextbox); + + class NextCharAction : public ui::ButtonAction + { + FontEditor *v; + public: + NextCharAction(FontEditor *_v): v(_v) {} + void ActionCallback(ui::Button *) + { + v->NextChar(); + } + }; + ui::Button *next = new ui::Button(ui::Point(currentX, baseline), ui::Point(17, 17), "\x95"); + currentX += 18; + next->SetActionCallback(new NextCharAction(this)); + AddComponent(next); + + class ShrinkCharAction : public ui::ButtonAction + { + FontEditor *v; + public: + ShrinkCharAction(FontEditor *_v): v(_v) {} + void ActionCallback(ui::Button *) + { + v->ShrinkChar(); + } + }; + ui::Button *shrink = new ui::Button(ui::Point(currentX, baseline), ui::Point(17, 17), "><"); + currentX += 18; + shrink->SetActionCallback(new ShrinkCharAction(this)); + AddComponent(shrink); + + class GrowCharAction : public ui::ButtonAction + { + FontEditor *v; + public: + GrowCharAction(FontEditor *_v): v(_v) {} + void ActionCallback(ui::Button *) + { + v->GrowChar(); + } + }; + ui::Button *grow = new ui::Button(ui::Point(currentX, baseline), ui::Point(17, 17), "<>"); + currentX += 18; + grow->SetActionCallback(new GrowCharAction(this)); + AddComponent(grow); + + + class ToggleAction : public ui::ButtonAction + { + int &toggle; + public: + ToggleAction(int &_toggle): toggle(_toggle) {} + void ActionCallback(ui::Button *button) + { + toggle = button->GetToggleState(); + } + }; + ui::Button *showGrid = new ui::Button(ui::Point(currentX, baseline), ui::Point(32, 17), "Grid"); + currentX += 33; + showGrid->SetTogglable(true); + showGrid->SetToggleState(grid); + showGrid->SetActionCallback(new ToggleAction(grid)); + AddComponent(showGrid); + + ui::Button *showRulers = new ui::Button(ui::Point(currentX, baseline), ui::Point(32, 17), "Rulers"); + currentX += 33; + showRulers->SetTogglable(true); + showRulers->SetToggleState(grid); + showRulers->SetActionCallback(new ToggleAction(rulers)); + AddComponent(showRulers); + + baseline += 18; + currentX = 1; + + class ColorComponentAction : public ui::TextboxAction + { + int &color; + public: + ColorComponentAction(int &_color): color(_color) {} + void TextChangedCallback(ui::Textbox *box) + { + std::stringstream ss(box->GetText()); + ss >> color; + } + }; + int *refs[6] = {&fgR, &fgG, &fgB, &bgR, &bgG, &bgB}; + for(int i = 0; i < 6; i++) + { + std::stringstream ss; + ss << *refs[i]; + ui::Textbox *colorComponent = new ui::Textbox(ui::Point(currentX, baseline), ui::Point(27, 17), ss.str()); + currentX += 28; + colorComponent->SetActionCallback(new ColorComponentAction(*refs[i])); + AddComponent(colorComponent); + } + + baseline += 18; + currentX = 1; + + class RenderAction : public ui::ButtonAction + { + FontEditor *v; + public: + RenderAction(FontEditor *_v): v(_v) {} + void ActionCallback(ui::Button *) + { + v->Render(); + } + }; + ui::Button *render = new ui::Button(ui::Point(currentX, baseline), ui::Point(50, 17), "Render"); + currentX += 51; + render->SetActionCallback(new RenderAction(this)); + AddComponent(render); + + class SaveAction : public ui::ButtonAction + { + FontEditor *v; + public: + SaveAction(FontEditor *_v): v(_v) {} + void ActionCallback(ui::Button *) + { + v->Save(); + } + }; + savedButton = new ui::Button(ui::Point(currentX, baseline), ui::Point(50, 17), "Save"); + currentX += 51; + savedButton->SetTogglable(true); + savedButton->SetToggleState(true); + savedButton->SetActionCallback(new SaveAction(this)); + AddComponent(savedButton); + + baseline += 18; + + outputPreview = new ui::Label(ui::Point(0, baseline + (Size.Y - baseline) * 3 / 5), ui::Point(Size.X, (Size.Y - baseline) * 2 / 5), ""); + outputPreview->SetMultiline(true); + outputPreview->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; + outputPreview->Appearance.VerticalAlign = ui::Appearance::AlignTop; + AddComponent(outputPreview); + + class PreviewAction : public ui::TextboxAction + { + FontEditor *v; + public: + PreviewAction(FontEditor *_v): v(_v) {} + void TextChangedCallback(ui::Textbox *box) + { + std::stringstream ss(box->GetText()); + std::string text; + while(!ss.eof()) + { + if(ss.peek() == '\n') + { + text.push_back('\n'); + ss.get(); + } + unsigned int ch; + ss >> std::hex >> ch; + if(ss.fail()) + { + ss.clear(); + char ch = ss.get(); + if(!ss.eof()) + text.push_back(ch); + continue; + } + text.push_back((char)ch); + } + v->outputPreview->SetText(text); + } + }; + ui::Textbox *inputPreview = new ui::Textbox(ui::Point(0, baseline), ui::Point(Size.X, (Size.Y - baseline) * 3 / 5)); + inputPreview->SetMultiline(true); + inputPreview->SetInputType(ui::Textbox::Multiline); + inputPreview->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; + inputPreview->Appearance.VerticalAlign = ui::Appearance::AlignTop; + inputPreview->SetActionCallback(new PreviewAction(this)); + + std::stringstream input; + for(unsigned int ch = 0x20; ch <= 0xFF; ch++) + { + if(!(ch & 0x3F)) + input << "20 "; + input << std::hex << std::setw(2) << ch << " "; + } + inputPreview->SetText(input.str()); + PreviewAction(this).TextChangedCallback(inputPreview); + AddComponent(inputPreview); +} + +void FontEditor::OnDraw() +{ + Graphics *g = GetGraphics(); + + int areaWidth = 8 + fontWidths[currentChar] * FONT_SCALE + 8; + g->fillrect(0, 0, areaWidth, 8 + FONT_H * FONT_SCALE + 4 + FONT_H + 4, bgR, bgG, bgB, 255); + for(int j = 0; j < FONT_H; j++) + for(int i = 0; i < fontWidths[currentChar]; i++) + g->fillrect(8 + i * FONT_SCALE, 8 + j * FONT_SCALE, FONT_SCALE - grid, FONT_SCALE - grid, fgR, fgG, fgB, fontPixels[currentChar][j][i] * 255 / 3); + + for(int j = 0; j < FONT_H; j++) + for(int i = 0; i < fontWidths[currentChar]; i++) + g->blendpixel(8 + i, 8 + FONT_H * FONT_SCALE + 4 + j, fgR, fgG, fgB, fontPixels[currentChar][j][i] * 255 / 3); + + + if(rulers) + { + g->draw_line(0, 7 + 0 * FONT_SCALE , areaWidth - 1, 7 + 0 * FONT_SCALE, 128, 128, 128, 255); + g->draw_line(0, 7 + 2 * FONT_SCALE , areaWidth - 1, 7 + 2 * FONT_SCALE, 128, 128, 128, 255); + g->draw_line(0, 7 + 4 * FONT_SCALE , areaWidth - 1, 7 + 4 * FONT_SCALE, 128, 128, 128, 255); + g->draw_line(0, 7 + 9 * FONT_SCALE , areaWidth - 1, 7 + 9 * FONT_SCALE, 128, 128, 128, 255); + g->draw_line(0, 7 + 12 * FONT_SCALE , areaWidth - 1, 7 + 12 * FONT_SCALE, 128, 128, 128, 255); + + g->draw_line(7, 8, 7, 7 + FONT_H * FONT_SCALE, 128, 128, 128, 255); + g->draw_line(7 + fontWidths[currentChar] * FONT_SCALE, 8, 7 + fontWidths[currentChar] * FONT_SCALE, 7 + FONT_H * FONT_SCALE, 128, 128, 128, 255); + } +} + +void FontEditor::OnMouseDown(int x, int y, unsigned button) +{ + x = (x - 8) / FONT_SCALE; + y = (y - 8) / FONT_SCALE; + if(x >= 0 && y >= 0 && x < fontWidths[currentChar] && y < FONT_H) + { + if(button == SDL_BUTTON_LEFT) + fontPixels[currentChar][y][x] = (fontPixels[currentChar][y][x] + 1) % 4; + else + fontPixels[currentChar][y][x] = (fontPixels[currentChar][y][x] + 3) % 4; + savedButton->SetToggleState(false); + } +} + +void FontEditor::OnKeyPress(int key, Uint16 character, bool shift, bool ctrl, bool alt) +{ + if(IsFocused(NULL)) + { + switch(key) + { + case SDLK_LEFT: + PrevChar(); break; + case SDLK_RIGHT: + PrevChar(); break; + case SDLK_ESCAPE: + case 'q': + if(savedButton->GetToggleState()) + ui::Engine::Ref().Exit(); + else + ui::Engine::Ref().ConfirmExit(); + break; + } + } +} + +void FontEditor::UpdateCharNumber() +{ + std::stringstream ss; + ss << std::hex << currentChar; + currentCharTextbox->SetText(ss.str()); +} + +void FontEditor::PrevChar() +{ + if(currentChar > 0) + currentChar--; + UpdateCharNumber(); +} + +void FontEditor::NextChar() +{ + if(currentChar < 255) + currentChar++; + UpdateCharNumber(); +} + +void FontEditor::ShrinkChar() +{ + if(fontWidths[currentChar] > 0) + fontWidths[currentChar]--; + savedButton->SetToggleState(false); +} + +void FontEditor::GrowChar() +{ + if(fontWidths[currentChar] < MAX_WIDTH - 1) + fontWidths[currentChar]++; + savedButton->SetToggleState(false); +} + +void FontEditor::Render() +{ + PackData(fontWidths, fontPixels, fontData, fontPtrs); + font_data = fontData.data(); + font_ptrs = fontPtrs.data(); +} + +void FontEditor::Save() +{ + std::vector tmpFontData; + std::vector tmpFontPtrs; + PackData(fontWidths, fontPixels, tmpFontData, tmpFontPtrs); + WriteHeader(header, tmpFontData, tmpFontPtrs); + savedButton->SetToggleState(true); +} +#endif diff --git a/src/gui/font/FontEditor.h b/src/gui/font/FontEditor.h new file mode 100644 index 000000000..bc781e01c --- /dev/null +++ b/src/gui/font/FontEditor.h @@ -0,0 +1,67 @@ +#ifndef FONTEDITOR_H +#define FONTEDITOR_H + +#include +#include + +#include "font.h" + +#include "gui/interface/Window.h" +#include "gui/interface/Textbox.h" + +#define MAX_WIDTH 64 +class FontEditor: public ui::Window +{ +private: + std::string header; + std::array fontWidths; + std::array, FONT_H>, 256> fontPixels; + + std::vector fontData; + std::vector fontPtrs; + + std::string beforeFontData; + std::string afterFontData; + std::string afterFontPtrs; + + void ReadHeader(std::string header); + void WriteHeader(std::string header, std::vector const &fontData, std::vector const &fontPtrs); + static void PackData( + std::array const &fontWidths, + std::array, FONT_H>, 256> const &fontPixels, + std::vector &fontData, + std::vector &fontPtrs); + static void UnpackData( + std::array &fontWidths, + std::array, FONT_H>, 256> &fontPixels, + std::vector const &fontData, + std::vector const &fontPtrs); + + ui::Textbox *currentCharTextbox; + ui::Button *savedButton; + ui::Label *outputPreview; + + int currentChar; + int fgR, fgG, fgB; + int bgR, bgG, bgB; + + int grid; + int rulers; + + void UpdateCharNumber(); + void PrevChar(); + void NextChar(); + void ShrinkChar(); + void GrowChar(); + void Render(); + void Save(); + +public: + FontEditor(std::string header); + + void OnDraw(); + void OnMouseDown(int x, int y, unsigned button); + void OnKeyPress(int key, Uint16 character, bool shift, bool ctrl, bool alt); +}; + +#endif