#include "FontEditor.h" #include "bzip2/bz2wrap.h" #include "gui/interface/Textbox.h" #include "gui/interface/Engine.h" #include "gui/interface/Point.h" #include "gui/interface/Button.h" #include "gui/interface/ScrollPanel.h" #include "graphics/Graphics.h" #include "SimulationConfig.h" #include #include #include #include #include #include #include extern unsigned char *font_data; extern unsigned int *font_ptrs; extern unsigned int (*font_ranges)[2]; void FontEditor::ReadDataFile(ByteString dataFile) { std::fstream file; file.open(dataFile, std::ios_base::in | std::ios_base::binary); if(!file) throw std::runtime_error("Could not open " + dataFile); file.seekg(0, std::ios_base::end); std::vector fileData(file.tellg()); file.seekg(0); file.read(&fileData[0], fileData.size()); file.close(); std::vector fontDataBuf; std::vector fontPtrsBuf; std::vector< std::array > fontRangesBuf; if (BZ2WDecompress(fontDataBuf, fileData.data(), fileData.size()) != BZ2WDecompressOk) { throw std::runtime_error("Could not decompress font data"); } int first = -1; int last = -1; char *begin = &fontDataBuf[0]; char *ptr = &fontDataBuf[0]; char *end = &fontDataBuf[0] + fontDataBuf.size(); while (ptr != end) { if (ptr + 4 > end) { throw std::runtime_error("Could not decompress font data"); } auto codePoint = *reinterpret_cast(ptr) & 0xFFFFFFU; if (codePoint >= 0x110000U) { throw std::runtime_error("Could not decompress font data"); } auto width = *reinterpret_cast(ptr + 3); if (width > 64) { throw std::runtime_error("Could not decompress font data"); } if (ptr + 4 + width * 3 > end) { throw std::runtime_error("Could not decompress font data"); } auto cp = (int)codePoint; if (last >= cp) { throw std::runtime_error("Could not decompress font data"); } if (first != -1 && last + 1 < cp) { fontRangesBuf.push_back({ { first, last } }); first = -1; } if (first == -1) { first = cp; } last = cp; fontPtrsBuf.push_back(ptr + 3 - begin); ptr += width * 3 + 4; } if (first != -1) { fontRangesBuf.push_back({ { first, last } }); } fontRangesBuf.push_back({ { 0, 0 } }); fontData.clear(); for (auto ch : fontDataBuf) { fontData.push_back(ch); } fontPtrs.clear(); for (auto ptr : fontPtrsBuf) { fontPtrs.push_back(ptr); } fontRanges.clear(); for (auto rng : fontRangesBuf) { fontRanges.push_back({ { (unsigned int)rng[0], (unsigned int)rng[1] } }); } } void FontEditor::WriteDataFile(ByteString dataFile, std::vector const &fontData, std::vector const &fontPtrs, std::vector > const &fontRanges) { std::fstream file; file.open(dataFile, std::ios_base::out | std::ios_base::trunc | std::ios_base::binary); if(!file) throw std::runtime_error("Could not open " + dataFile); std::vector uncompressed; size_t pos = 0; for (size_t i = 0; pos < fontPtrs.size() && fontRanges[i][1]; i++) { for (String::value_type ch = fontRanges[i][0]; ch <= fontRanges[i][1]; ch++) { uncompressed.push_back((char)ch); uncompressed.push_back((char)(ch >> 8)); uncompressed.push_back((char)(ch >> 16)); auto ptr = fontPtrs[pos++]; auto width = fontData[ptr]; uncompressed.push_back(width); for (auto j = 0; j < 3 * width; ++j) { uncompressed.push_back(fontData[ptr + 1 + j]); } } } std::vector compressed; if (BZ2WCompress(compressed, uncompressed.data(), uncompressed.size()) != BZ2WCompressOk) { throw std::runtime_error("Could not compress font data"); } file.write(compressed.data(), compressed.size()); file.close(); } void FontEditor::UnpackData( std::map &fontWidths, std::map, FONT_H> > &fontPixels, std::vector const &fontData, std::vector const &fontPtrs, std::vector > const &fontRanges) { fontWidths.clear(); fontPixels.clear(); size_t pos = 0; for(size_t range = 0; fontRanges[range][1]; range++) for(String::value_type ch = fontRanges[range][0]; ch <= fontRanges[range][1]; ch++) { unsigned char const *pointer = &fontData[fontPtrs[pos]]; int width = fontWidths[ch] = *(pointer++); int pixels = 0; int data = 0; for(int j = 0; j < FONT_H; j++) for(int i = 0; i < width; i++) { if(!pixels) { data = *(pointer++); pixels = 4; } fontPixels[ch][j][i] = data & 3; data >>= 2; pixels--; } pos++; } } void FontEditor::PackData( std::map const &fontWidths, std::map, FONT_H> > const &fontPixels, std::vector &fontData, std::vector &fontPtrs, std::vector > &fontRanges) { fontData.clear(); fontPtrs.clear(); fontRanges.clear(); bool first = true; String::value_type rangeStart = 0; String::value_type prev = 0; for(std::map::const_iterator it = fontWidths.begin(); it != fontWidths.end(); it++) { String::value_type ch = it->first; if(first) { rangeStart = ch; first = false; } else if(ch != prev + 1) { fontRanges.push_back({rangeStart, prev}); rangeStart = ch; } fontPtrs.push_back(fontData.size()); fontData.push_back(it->second); int pixels = 0; int data = 0; for(int j = 0; j < FONT_H; j++) for(int i = 0; i < it->second; i++) { if(pixels == 4) { fontData.push_back(data); pixels = 0; data = 0; } data >>= 2; data |= fontPixels.at(ch)[j][i] << 6; pixels++; } if(pixels) fontData.push_back(data); prev = ch; } fontRanges.push_back({rangeStart, prev}); fontRanges.push_back({0, 0}); } class StretchLabel: public ui::Label { using Label::Label; public: int WrappedLines() const { return displayTextWrapper.WrappedLines(); } }; class StretchTextbox: public ui::Textbox { using Textbox::Textbox; public: int WrappedLines() const { return displayTextWrapper.WrappedLines(); } }; constexpr int FONT_SCALE = 16; FontEditor::FontEditor(ByteString _dataFile): ui::Window(ui::Point(0, 0), ui::Point(WINDOWW, WINDOWH)), dataFile(_dataFile), currentChar(0x80), fgR(255), fgG(255), fgB(255), bgR(0), bgG(0), bgB(0), grid(1), rulers(1) { ReadDataFile(dataFile); UnpackData(fontWidths, fontPixels, fontData, fontPtrs, fontRanges); font_data = fontData.data(); font_ptrs = fontPtrs.data(); font_ranges = (unsigned int (*)[2])fontRanges.data(); int baseline = 8 + FONT_H * FONT_SCALE + 4 + FONT_H + 4 + 1; int currentX = 1; ui::Button *prev = new ui::Button(ui::Point(currentX, baseline), ui::Point(17, 17), 0xE016); currentX += 18; prev->SetActionCallback({ [this] { PrevChar(); } }); AddComponent(prev); currentCharTextbox = new ui::Textbox(ui::Point(currentX, baseline), ui::Point(31, 17)); currentX += 32; currentCharTextbox->SetActionCallback({ [this] { unsigned int number = currentCharTextbox->GetText().ToNumber(Format::Hex(), true); if(number <= 0x10FFFF) currentChar = number; } }); UpdateCharNumber(); AddComponent(currentCharTextbox); ui::Button *next = new ui::Button(ui::Point(currentX, baseline), ui::Point(17, 17), 0xE015); currentX += 18; next->SetActionCallback({ [this] { NextChar(); } }); AddComponent(next); ui::Button *shrink = new ui::Button(ui::Point(currentX, baseline), ui::Point(17, 17), "><"); currentX += 18; shrink->SetActionCallback({ [this] { ShrinkChar(); } }); AddComponent(shrink); ui::Button *grow = new ui::Button(ui::Point(currentX, baseline), ui::Point(17, 17), "<>"); currentX += 18; grow->SetActionCallback({ [this] { GrowChar(); } }); AddComponent(grow); ui::Button *add = new ui::Button(ui::Point(currentX, baseline), ui::Point(36, 17), "Add"); currentX += 37; add->SetActionCallback({ [this] { if (fontWidths.find(currentChar) == fontWidths.end()) { savedButton->SetToggleState(false); fontWidths[currentChar] = 5; fontPixels[currentChar]; } } }); AddComponent(add); ui::Button *remove = new ui::Button(ui::Point(currentX, baseline), ui::Point(36, 17), "Remove"); currentX += 37; remove->SetActionCallback({ [this] { if (fontWidths.find(currentChar) != fontWidths.end()) { savedButton->SetToggleState(false); fontWidths.erase(currentChar); fontPixels.erase(currentChar); } } }); AddComponent(remove); 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({ [this, showGrid] { grid = showGrid->GetToggleState(); } }); AddComponent(showGrid); ui::Button *showRulers = new ui::Button(ui::Point(currentX, baseline), ui::Point(32, 17), "Rulers"); currentX += 33; showRulers->SetTogglable(true); showRulers->SetToggleState(rulers); showRulers->SetActionCallback({ [this, showRulers] { rulers = showRulers->GetToggleState(); } }); AddComponent(showRulers); baseline += 18; currentX = 1; int *refs[6] = {&fgR, &fgG, &fgB, &bgR, &bgG, &bgB}; for(int i = 0; i < 6; i++) { ui::Textbox *colorComponent = new ui::Textbox(ui::Point(currentX, baseline), ui::Point(27, 17), String::Build(*refs[i])); currentX += 28; colorComponent->SetActionCallback({ [colorComponent, refs, i] { *refs[i] = colorComponent->GetText().ToNumber(true); } }); AddComponent(colorComponent); } baseline += 18; currentX = 1; ui::Button *render = new ui::Button(ui::Point(currentX, baseline), ui::Point(50, 17), "Render"); currentX += 51; render->SetActionCallback({ [this] { Render(); } }); AddComponent(render); savedButton = new ui::Button(ui::Point(currentX, baseline), ui::Point(50, 17), "Save"); currentX += 51; savedButton->SetTogglable(true); savedButton->SetToggleState(true); savedButton->SetActionCallback({ [this] { Save(); } }); AddComponent(savedButton); baseline += 18; ui::ScrollPanel *outputPanel = new ui::ScrollPanel(ui::Point(Size.X / 2, baseline), ui::Point(Size.X / 2, Size.Y - baseline)); AddComponent(outputPanel); StretchLabel *outputPreview = new StretchLabel(ui::Point(0, 0), ui::Point(Size.X / 2, 0), ""); outputPreview->SetMultiline(true); outputPreview->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; outputPreview->Appearance.VerticalAlign = ui::Appearance::AlignTop; outputPanel->AddChild(outputPreview); ui::ScrollPanel *inputPanel = new ui::ScrollPanel(ui::Point(0, baseline), ui::Point(Size.X / 2, Size.Y - baseline)); AddComponent(inputPanel); StretchTextbox *inputPreview = new StretchTextbox(ui::Point(0, 0), ui::Point(Size.X / 2, 0)); inputPreview->SetMultiline(true); inputPreview->SetInputType(ui::Textbox::Multiline); inputPreview->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; inputPreview->Appearance.VerticalAlign = ui::Appearance::AlignTop; auto textChangedCallback = [outputPreview, outputPanel, inputPreview, inputPanel] { String str = inputPreview->GetText(); size_t at = 0; StringBuilder text; while(at < str.size()) { unsigned int ch1, ch2; if(str[at] != ' ') if(String::Split split1 = str.SplitNumber(ch1, Format::Hex(), at)) { if(str[split1.PositionAfter()] == ':') if(String::Split split2 = str.SplitNumber(ch2, Format::Hex(), split1.PositionAfter() + 1)) { for(unsigned int ch = ch1; ch <= ch2; ch++) text << String::value_type(ch); at = split2.PositionAfter(); continue; } text << String::value_type(ch1); at = split1.PositionAfter(); } else { text << str[at++]; } else at++; } outputPreview->SetText(text.Build()); outputPanel->InnerSize.Y = outputPreview->Size.Y = std::max(outputPreview->WrappedLines(), 1) * FONT_H + 2; inputPanel->InnerSize.Y = inputPreview->Size.Y = std::max(inputPreview->WrappedLines(), 1) * FONT_H + 2; }; inputPreview->SetActionCallback({ textChangedCallback }); inputPanel->AddChild(inputPreview); StringBuilder input; input << Format::Hex() << Format::Width(2); for(auto p : fontRanges) if(p[1] >= 0x20) { if(p[0] < 0x20) p[0] = 0x20; if(p[0] == p[1]) input << p[0] << "\n"; else input << p[0] << ":" << p[1] << "\n"; } inputPreview->SetText(input.Build()); textChangedCallback(); AddComponent(inputPreview); } FontEditor::FontEditor(ByteString target, ByteString source): ui::Window(ui::Point(0, 0), ui::Point(WINDOWW, WINDOWH)) { ReadDataFile(target); std::map tgtFontWidths, srcFontWidths; std::map, FONT_H> > tgtFontPixels, srcFontPixels; UnpackData(tgtFontWidths, tgtFontPixels, fontData, fontPtrs, fontRanges); ReadDataFile(source); UnpackData(srcFontWidths, srcFontPixels, fontData, fontPtrs, fontRanges); for(auto const &p : srcFontPixels) if(tgtFontPixels.count(p.first)) { bool same = tgtFontWidths[p.first] == srcFontWidths[p.first]; if(same) for(int j = 0; j < FONT_H; j++) for(int i = 0; i < tgtFontWidths[p.first]; i++) same = same && tgtFontPixels[p.first][j][i] == srcFontPixels[p.first][j][i]; if(!same) std::cout << "U+" << std::hex << (unsigned int)p.first << " is present in both files and is different!" << std::endl; } else { std::cout << "Adding U+" << std::hex << (unsigned int)p.first << " to the target" << std::endl; tgtFontWidths[p.first] = srcFontWidths[p.first]; tgtFontPixels[p.first] = p.second; } std::vector tmpFontData; std::vector tmpFontPtrs; std::vector > tmpFontRanges; PackData(tgtFontWidths, tgtFontPixels, tmpFontData, tmpFontPtrs, tmpFontRanges); WriteDataFile(target, tmpFontData, tmpFontPtrs, tmpFontRanges); } void FontEditor::OnDraw() { Graphics *g = GetGraphics(); if(fontWidths.find(currentChar) != fontWidths.end()) { int width = fontWidths[currentChar]; std::array, FONT_H> const &pixels = fontPixels[currentChar]; int areaWidth = 8 + width * FONT_SCALE + 8; g->DrawFilledRect(RectSized(Vec2{ 0, 0 }, Vec2{ areaWidth, 8 + FONT_H * FONT_SCALE + 4 + FONT_H + 4 }), RGB(bgR, bgG, bgB)); for(int j = 0; j < FONT_H; j++) for(int i = 0; i < width; i++) g->BlendFilledRect(RectSized(Vec2{ 8 + i * FONT_SCALE, 8 + j * FONT_SCALE }, Vec2{ FONT_SCALE - grid, FONT_SCALE - grid }), RGBA(fgR, fgG, fgB, pixels[j][i] * 255 / 3)); for(int j = 0; j < FONT_H; j++) for(int i = 0; i < width; i++) g->BlendPixel({ 8 + i, 8 + FONT_H * FONT_SCALE + 4 + j }, RGBA(fgR, fgG, fgB, pixels[j][i] * 255 / 3)); if(rulers) { g->DrawLine({ 0, 7 + 0 * FONT_SCALE }, { areaWidth - 1, 7 + 0 * FONT_SCALE }, 0x808080_rgb); g->DrawLine({ 0, 7 + 2 * FONT_SCALE }, { areaWidth - 1, 7 + 2 * FONT_SCALE }, 0x808080_rgb); g->DrawLine({ 0, 7 + 4 * FONT_SCALE }, { areaWidth - 1, 7 + 4 * FONT_SCALE }, 0x808080_rgb); g->DrawLine({ 0, 7 + 9 * FONT_SCALE }, { areaWidth - 1, 7 + 9 * FONT_SCALE }, 0x808080_rgb); g->DrawLine({ 0, 7 + 12 * FONT_SCALE }, { areaWidth - 1, 7 + 12 * FONT_SCALE }, 0x808080_rgb); g->DrawLine({ 7, 8 }, { 7, 7 + FONT_H * FONT_SCALE }, 0x808080_rgb); g->DrawLine({ 7 + width * FONT_SCALE, 8}, { 7 + width * FONT_SCALE, 7 + FONT_H * FONT_SCALE }, 0x808080_rgb); } } else { g->BlendText({ 8, 8 }, "No character", 0xFF0000_rgb .WithAlpha(255)); } } void FontEditor::OnMouseDown(int x, int y, unsigned button) { if(fontWidths.find(currentChar) != fontWidths.end()) { 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::Translate(std::array, FONT_H> &pixels, int dx, int dy) { std::array, FONT_H> old = pixels; for(int j = 0; j < FONT_H; j++) for(int i = 0; i < MAX_WIDTH; i++) if(i - dx >= 0 && i - dx < MAX_WIDTH && j - dy >= 0 && j - dy < FONT_H) pixels[j][i] = old[j - dy][i - dx]; else pixels[j][i] = 0; savedButton->SetToggleState(false); } void FontEditor::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) { if (IsFocused(NULL)) { switch(scan) { case SDL_SCANCODE_UP: if(shift) Translate(fontPixels[currentChar], 0, -1); break; case SDL_SCANCODE_DOWN: if(shift) Translate(fontPixels[currentChar], 0, 1); break; case SDL_SCANCODE_LEFT: if(shift) Translate(fontPixels[currentChar], -1, 0); else PrevChar(); break; case SDL_SCANCODE_RIGHT: if(shift) Translate(fontPixels[currentChar], 1, 0); else NextChar(); break; case SDL_SCANCODE_ESCAPE: case SDL_SCANCODE_Q: if(savedButton->GetToggleState()) ui::Engine::Ref().Exit(); else ui::Engine::Ref().ConfirmExit(); break; case SDL_SCANCODE_C: clipboardWidth = fontWidths[currentChar]; clipboardPixels = fontPixels[currentChar]; break; case SDL_SCANCODE_V: fontWidths[currentChar] = clipboardWidth; fontPixels[currentChar] = clipboardPixels; break; } } } void FontEditor::UpdateCharNumber() { currentCharTextbox->SetText(String::Build(Format::Hex((unsigned int)currentChar))); } void FontEditor::PrevChar() { if(currentChar > 0) currentChar--; UpdateCharNumber(); } void FontEditor::NextChar() { if(currentChar <= 0x10FFFF) 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, fontRanges); font_data = fontData.data(); font_ptrs = fontPtrs.data(); font_ranges = (unsigned int (*)[2])fontRanges.data(); } void FontEditor::Save() { std::vector tmpFontData; std::vector tmpFontPtrs; std::vector > tmpFontRanges; PackData(fontWidths, fontPixels, tmpFontData, tmpFontPtrs, tmpFontRanges); WriteDataFile(dataFile, tmpFontData, tmpFontPtrs, tmpFontRanges); savedButton->SetToggleState(true); }