From bca1adc6eb7388b067a478bbd17ee9d7642e4743 Mon Sep 17 00:00:00 2001 From: zetaPRIME Date: Wed, 15 Mar 2017 10:42:24 -0400 Subject: [PATCH] some flailing at text... then a complete text-rendering rewrite! lambda-loop shininess! --- libstarlight/source/starlight/dialog/OSK.cpp | 15 +- .../starlight/dialog/osk/InputHandler.cpp | 13 +- .../starlight/dialog/osk/InputHandler.h | 4 +- .../source/starlight/gfx/BitmapFont.cpp | 137 ++++++++++++++---- .../source/starlight/gfx/BitmapFont.h | 14 ++ libstarlight/source/starlight/gfx/FontBMF.cpp | 39 ++++- libstarlight/todo.txt | 5 + 7 files changed, 184 insertions(+), 43 deletions(-) diff --git a/libstarlight/source/starlight/dialog/OSK.cpp b/libstarlight/source/starlight/dialog/OSK.cpp index 99b9ca7..eb022a8 100644 --- a/libstarlight/source/starlight/dialog/OSK.cpp +++ b/libstarlight/source/starlight/dialog/OSK.cpp @@ -31,6 +31,13 @@ using starlight::ui::Form; using starlight::dialog::OSK; +namespace { + inline starlight::TextConfig& PreviewTC() { + static auto tc = ThemeManager::GetMetric("/dialogs/OSK/preview"); + return tc; + } +} + OSK::OSK(osk::InputHandler* handler) : Form(true), handler(handler) { priority = 1000; // probably don't want all that much displaying above the keyboard handler->parent = this; @@ -117,7 +124,7 @@ void OSK::Update(bool focused) { preview->Refresh(); } - static auto tc = ThemeManager::GetMetric("/dialogs/OSK/preview"); + auto& tc = PreviewTC(); if (InputManager::Pressed(Keys::DPadUp)) { Vector2 pt = tc.font->GetCursorPosition(preview->rect, handler->GetPreviewText(), handler->GetCursor()); string msr = "|"; @@ -128,7 +135,7 @@ void OSK::Update(bool focused) { if (InputManager::Pressed(Keys::DPadDown)) { Vector2 pt = tc.font->GetCursorPosition(preview->rect, handler->GetPreviewText(), handler->GetCursor()); string msr = "|"; - pt.y += tc.font->Measure(msr).y; + pt.y += tc.font->Measure(msr).y * 1.5f; handler->SetCursor(tc.font->GetCursorFromPoint(preview->rect, handler->GetPreviewText(), pt)); preview->Refresh(); } @@ -152,7 +159,7 @@ void OSK::OnKey() { void OSK::DrawPreview(DrawLayerProxy& layer) { if (true || handler->showPreview) { - static auto tc = ThemeManager::GetMetric("/dialogs/OSK/preview"); + auto& tc = PreviewTC(); tc.Print(layer.rect, handler->GetPreviewText(), Vector2::zero); Vector2 cp = tc.font->GetCursorPosition(layer.rect, handler->GetPreviewText(), handler->GetCursor()); @@ -163,7 +170,7 @@ void OSK::DrawPreview(DrawLayerProxy& layer) { void OSK::OnPreviewTap(DrawLayerProxy& layer) { Vector2 tpos = InputManager::TouchPos() - layer.ScreenRect().pos; - static auto tc = ThemeManager::GetMetric("/dialogs/OSK/preview"); + auto& tc = PreviewTC(); handler->SetCursor(tc.font->GetCursorFromPoint(layer.rect, handler->GetPreviewText(), tpos + layer.rect.pos)); preview->Refresh(); } diff --git a/libstarlight/source/starlight/dialog/osk/InputHandler.cpp b/libstarlight/source/starlight/dialog/osk/InputHandler.cpp index d7dc062..fa7f09c 100644 --- a/libstarlight/source/starlight/dialog/osk/InputHandler.cpp +++ b/libstarlight/source/starlight/dialog/osk/InputHandler.cpp @@ -1,5 +1,7 @@ #include "InputHandler.h" +#include + #include "starlight/ui/Form.h" using std::string; @@ -13,7 +15,7 @@ using starlight::dialog::osk::InputHandlerBuffered; std::string& InputHandlerDirectEdit::GetPreviewText() { return *pText; } unsigned int InputHandlerDirectEdit::GetCursor() { return cursor; } -void InputHandlerDirectEdit::SetCursor(unsigned int index) { cursor = index; if (cursor < minIndex) cursor = minIndex; auto len = pText->length(); if (cursor > len) cursor = len; } +void InputHandlerDirectEdit::SetCursor(unsigned int index) { cursor = std::max(minIndex, std::min(index, pText->length())); } void InputHandlerDirectEdit::InputSymbol(const string& sym) { //pText->append(sym); @@ -45,15 +47,16 @@ void InputHandlerDirectEdit::Done() { std::string& InputHandlerBuffered::GetPreviewText() { return buffer; } -unsigned int InputHandlerBuffered::GetCursor() { return buffer.length(); } -void InputHandlerBuffered::SetCursor(unsigned int index) { } +unsigned int InputHandlerBuffered::GetCursor() { return cursor; } +void InputHandlerBuffered::SetCursor(unsigned int index) { cursor = std::max(0U, std::min(index, buffer.length())); } void InputHandlerBuffered::InputSymbol(const string& sym) { - buffer.append(sym); + buffer.insert(cursor, sym); + cursor += sym.length(); } void InputHandlerBuffered::Backspace() { - buffer.pop_back(); + if (cursor > 0) buffer.erase(--cursor, 1); } void InputHandlerBuffered::Enter() { diff --git a/libstarlight/source/starlight/dialog/osk/InputHandler.h b/libstarlight/source/starlight/dialog/osk/InputHandler.h index 95d8c84..6f9a16a 100644 --- a/libstarlight/source/starlight/dialog/osk/InputHandler.h +++ b/libstarlight/source/starlight/dialog/osk/InputHandler.h @@ -65,10 +65,12 @@ namespace starlight { public: bool multiLine = false; + unsigned int cursor = 0; + std::function eOnFinalize = { }; InputHandlerBuffered(const std::string& text = "", bool multiLine = false, std::function onFinalize = {}) - : buffer(text), multiLine(multiLine), eOnFinalize(onFinalize) { this->showPreview = true; } + : buffer(text), multiLine(multiLine), cursor(text.length()), eOnFinalize(onFinalize) { this->showPreview = true; } ~InputHandlerBuffered() override { } std::string& GetPreviewText() override; diff --git a/libstarlight/source/starlight/gfx/BitmapFont.cpp b/libstarlight/source/starlight/gfx/BitmapFont.cpp index 52593c6..dc455de 100644 --- a/libstarlight/source/starlight/gfx/BitmapFont.cpp +++ b/libstarlight/source/starlight/gfx/BitmapFont.cpp @@ -101,25 +101,22 @@ Vector2 BitmapFont::MeasureTo(std::string& msg, bool total, unsigned int end, fl for (unsigned int i = 0; i < len; i++) { char& c = msg[i]; if (c == ' ' || c == '\n') { - bool alb = false; // already linebreak + bool lb = (c == '\n' || pen.x + wordlen > maxWidth); oPen = pen; - if (pen.x > longest) longest = pen.x; - if (pen.x + wordlen > maxWidth) { - pen.x = 0; pen.y += lineHeight; - alb = true; - } - pen.x += wordlen; - pen.x += space; - if (c == '\n' && !alb) { - if (pen.x > longest) longest = pen.x; - pen.x = 0; pen.y += lineHeight; + if (lb) { + if (c == '\n') pen.x += space + wordlen; // previous word + longest = std::max(pen.x - space, longest); + pen.x = (c == ' ') ? (space + wordlen) : 0; + pen.y += lineHeight; + } else { + pen.x += space + wordlen; } if (!total && i >= end) { - //if (c == ' ') pen.x -= space; // I think this needs to be undone too? - return Vector2(oPen.x + plen, oPen.y); // return cursor position + return Vector2(pen.x - (wordlen - plen) - space, pen.y); } + wordlen = plen = 0; } else { float adv = Char(c).advX; @@ -127,23 +124,21 @@ Vector2 BitmapFont::MeasureTo(std::string& msg, bool total, unsigned int end, fl if (i < end) plen += adv; } } + longest = std::max(pen.x - space, longest); - { // oh right, this check kind of needs to happen at the end too - if (pen.x > longest) longest = pen.x; - if (pen.x + wordlen > maxWidth) { - pen.x = 0; pen.y += lineHeight; - } - pen.x += wordlen; - - if (!total) { - //if (c == ' ') pen.x -= space; // I think this needs to be undone too? - return Vector2(pen.x - (wordlen - plen), pen.y); // return cursor position - } + if (pen.x + space + wordlen > maxWidth) { + pen.x = wordlen; + pen.y += lineHeight; + } else { + pen.x += wordlen + space; + } + longest = std::max(pen.x - space, longest); // do I need two of these? + + if (!total) { + return Vector2(pen.x - (wordlen - plen) - space, pen.y); // return cursor position } - //if (msg.back() != '\n') pen.y += lineHeight; // trim trailing newline (todo: make this recursive...?) - pen.y += lineHeight; - return Vector2(longest, pen.y); // total size + return Vector2(longest, pen.y + lineHeight); // total size } unsigned int BitmapFont::PointToIndex(std::string& msg, Vector2 pt, float maxWidth) { @@ -199,7 +194,93 @@ unsigned int BitmapFont::PointToIndex(std::string& msg, Vector2 pt, float maxWid return len; } - +void BitmapFont::ForChar(const std::string& msg, std::function func, float maxWidth) { + struct LineStat { + unsigned int start; + unsigned int end; + float width; + }; + + float space = Char(' ').advX; + + std::vector lines; + + { + LineStat cl = {0, 0, 0}; + float ww = 0; + unsigned int ws = 0; + unsigned int len = msg.length(); + for (unsigned int i = 0; i <= len; i++) { + char c = (i == len) ? '\n' : msg[i]; + char pc = (i == 0) ? '\n' : msg[i-1]; + if (c == ' ' || c == '\n') { + // handle previously-accumulated word + if (cl.start != ws && cl.width + ww > maxWidth) { // don't bother wrapping the first word on a line... + // omit breaking space from the end of the line + if (ws > 0 && msg[ws-1] == ' ') cl.width -= space; + // wrap + lines.push_back(cl); + cl = {ws, i, ww}; // start at start of word, end at < this char, have width of word + ww = 0; + ws = i+1; + } else { + // add to length + cl.width += ww; + cl.end = i; // < this char + ww = 0; + ws = i+1; + } + + if (c == ' ') { + // space should be present in spacing when it *is not* the line-terminator (that is, the space before a wrapped word) + // how the fuck do I do that!? + // probably a retroactive decrement on wrap event... + if (true) { // if not... this stuff ^ + cl.width += space; + } + } else { // newline + // I guess there's no circumstance where a newline *shouldn't* be honored? + cl.end = i; + lines.push_back(cl); + cl = {i+1, i+1, 0}; + } + } else { // non-word-ender! add to accumulated word width + ww += Char(c).advX + GetKerning(pc, c); + } + } + // don't need to manually do anything after due to the extra step + //lines.push_back(cl); + } + + CharLoopState state; + state.numLines = lines.size(); + state.lineNum = 0; + for (auto& cl : lines) { + state.lineStart = cl.start; + state.lineEnd = cl.end; + state.lineWidth = cl.width; + state.lineAcc = 0; + state.c = '\n'; + + for (unsigned int i = cl.start; i < cl.end; i++) { + char pc = state.c; + state.c = msg[i]; + state.cc = &(Char(state.c)); + + state.i = i; + state.iline = i - cl.start; + + state.lineAcc += GetKerning(pc, state.c); // I think this goes before? + + if (func(state)) return; // return true to break + + state.lineAcc += state.cc->advX; + } + + state.lineNum++; + } + +} diff --git a/libstarlight/source/starlight/gfx/BitmapFont.h b/libstarlight/source/starlight/gfx/BitmapFont.h index 015e03a..ae125a2 100644 --- a/libstarlight/source/starlight/gfx/BitmapFont.h +++ b/libstarlight/source/starlight/gfx/BitmapFont.h @@ -2,6 +2,7 @@ #include "starlight/_global.h" #include +#include #include #include @@ -29,6 +30,17 @@ namespace starlight { float advX; }; + struct CharLoopState { + unsigned int lineStart, lineEnd; + float lineWidth; + unsigned int numLines; + unsigned int lineNum; + char c; + CharInfo* cc; + float lineAcc; + unsigned int i, iline; + }; + float fontSize; float lineHeight; float baseY; @@ -52,6 +64,8 @@ namespace starlight { Vector2 MeasureTo(std::string& msg, bool total = true, unsigned int end = 4294967295, float maxWidth = 65536*64); unsigned int PointToIndex(std::string& msg, Vector2 pt, float maxWidth = 65536*64); + void ForChar(const std::string& msg, std::function func, float maxWidth = 65536*64); + static inline constexpr unsigned int KerningKey(char cl, char cr) { return (static_cast(cl) | (static_cast(cr) << 8)); } diff --git a/libstarlight/source/starlight/gfx/FontBMF.cpp b/libstarlight/source/starlight/gfx/FontBMF.cpp index 02b38f4..761f8ec 100644 --- a/libstarlight/source/starlight/gfx/FontBMF.cpp +++ b/libstarlight/source/starlight/gfx/FontBMF.cpp @@ -28,7 +28,21 @@ void FontBMF::Print(Vector2 position, std::string& text, float scale, Color colo if (text == "") return; if (GFXManager::PrepareForDrawing()) { DisplayList dl = DisplayList(); - PrintOp(position, text, scale, color, justification, borderColor, 2147483647, static_cast(nullptr), &dl); + //PrintOp(position, text, scale, color, justification, borderColor, 2147483647, static_cast(nullptr), &dl); + { + auto qn = dl.GetLastNode(true); + Vector2 uvScale = Vector2::one / font->txMain->txSize; + Vector2 ppen = Vector2(-font->padX, -font->padY /*- (font->lineHeight - font->baseY)*/); + font->ForChar(text, [&, this, ppen, justification, qn, scale, uvScale](auto& s){ + if (s.c == ' ') return false; // skip spaces + Vector2 pen = (ppen + Vector2(s.lineAcc - s.lineWidth * justification.x, font->lineHeight * ((float)s.lineNum - (float)s.numLines * justification.y))) * scale; + auto& ci = *s.cc; + VRect crect(ci.imgX, ci.imgY, ci.width, ci.height); + qn->Add(VRect(pen, crect.size * scale), crect * uvScale); + + return false; + }); + } if (borderColor && borderColor.get() != Color::transparent && font->txBorder) { font->txBorder->Bind(borderColor.get()); dl.Run(position); @@ -42,15 +56,30 @@ void FontBMF::Print(VRect rect, std::string& text, float scale, Color color, Vec if (text == "") return; if (GFXManager::PrepareForDrawing()) { if (borderColor && borderColor.get() != Color::transparent) rect = rect.Expand(-1, -1); - Vector2 pos = rect.pos + rect.size * justification; + Vector2 position = rect.pos + rect.size * justification; DisplayList dl = DisplayList(); - PrintOp(pos, text, scale, color, justification, borderColor, rect.size.x, static_cast(nullptr), &dl); + //PrintOp(pos, text, scale, color, justification, borderColor, rect.size.x, static_cast(nullptr), &dl); + { + auto qn = dl.GetLastNode(true); + Vector2 uvScale = Vector2::one / font->txMain->txSize; + Vector2 ppen = Vector2(-font->padX, -font->padY /*- (font->lineHeight - font->baseY)*/); + font->ForChar(text, [&, this, ppen, justification, qn, scale, uvScale](auto& s){ + if (s.c == ' ') return false; // skip spaces + Vector2 pen = (ppen + Vector2(s.lineAcc - s.lineWidth * justification.x, font->lineHeight * ((float)s.lineNum - (float)s.numLines * justification.y))) * scale; + auto& ci = *s.cc; + VRect crect(ci.imgX, ci.imgY, ci.width, ci.height); + qn->Add(VRect(pen, crect.size * scale), crect * uvScale); + + return false; + }, rect.size.x); + } + if (borderColor && borderColor.get() != Color::transparent && font->txBorder) { font->txBorder->Bind(borderColor.get()); - dl.Run(pos); + dl.Run(position); } font->txMain->Bind(color); - dl.Run(pos); + dl.Run(position); } } diff --git a/libstarlight/todo.txt b/libstarlight/todo.txt index aa1419b..dfc9719 100644 --- a/libstarlight/todo.txt +++ b/libstarlight/todo.txt @@ -9,6 +9,11 @@ roadmap to first release, in no particular order { - preview where applicable fix desync between cursor position, tap-cursor and display when a single word overflows a line (cursor doesn't wrap until the next word!?) + fix font glyph padding to eliminate slight "crosstalk" in bordered variants + ... reimplement as a per-character lambda-"loop" { + status object with stuff + precalculate line stats, then loop through each + } polish! InputManager::OpenKeyboard }