zetaPRIME 5954dc5768 add OSK preview scrolling, background, fix DrawLayerProxy,
make TextConfig.Print string ref const, make form priority float,
ScrollField::ScrollIntoView, minor BitmapFont fix
2017-03-16 20:35:04 -04:00

228 lines
7.1 KiB
C++

#include "BitmapFont.h"
#include <cstdlib>
#include <cmath>
#include <fastmath.h>
#include "starlight/_incLib/json.hpp"
using nlohmann::json;
using starlight::Vector2;
using starlight::VRect;
using starlight::Color;
using starlight::gfx::BitmapFont;
//using starlight::gfx::BitmapFont::CharInfo;
BitmapFont::BitmapFont(json& j) {
// populate global info
fontSize = fabsf(j["info"]["size"]);
lineHeight = j["common"]["lineHeight"];
baseY = j["common"]["base"];
padX = j["info"]["padding"][3];
padY = j["info"]["padding"][0];
// assemble charinfo structs
for (auto& c : json::iterator_wrapper(j["chars"])) {
auto& cc = c.value();
auto& def = chars.insert({static_cast<char>(std::stoi(c.key())), CharInfo()}).first->second; // well that's kind of a pain just to get back the instantiated value
def.imgX = cc["x"];
def.imgY = cc["y"];
def.width = cc["width"];
def.height = cc["height"];
def.offX = cc["xoffset"];
def.offY = cc["yoffset"];
def.advX = cc["xadvance"];
}
// fetch kerning info
for (auto& kl : json::iterator_wrapper(j["kernings"])) {
char cl = std::stoi(kl.key());
for (auto& kr : json::iterator_wrapper(kl.value())) {
char cr = std::stoi(kr.key());
kernings[KerningKey(cl, cr)] = kr.value();
}
}
}
float BitmapFont::GetKerning(char cl, char cr) {
auto f = kernings.find(KerningKey(cl, cr));
if (f == kernings.end()) return 0;
return f->second;
}
BitmapFont::CharInfo& BitmapFont::Char(char c) {
auto f = chars.find(c);
if (f != chars.end()) return f->second;
static CharInfo cdefault = []() -> CharInfo {
CharInfo def;
def.imgX = def.imgY = def.width = def.height = def.offX = def.offY = def.advX = 0.0f;
return def;
}();
return cdefault;
}
Vector2 BitmapFont::MeasureTo(const std::string& msg, bool total, unsigned int end, float maxWidth) {
if (msg == "") return Vector2::zero;
if (total) {
if (end == 0) return Vector2(0, lineHeight);
Vector2 measure = Vector2::zero;
ForChar(msg, [&, this](auto& s){
if (s.i > end) return true;
if (s.iline > 0) return false;
measure.x = std::max(measure.x, s.lineWidth);
measure.y += lineHeight;
return false;
}, maxWidth);
return measure;
}
Vector2 measure;
bool found = false;
unsigned int et = end - 1;
if (end == 0) et = 0;
ForChar(msg, [&, this, total, end, et](auto& s){
measure = Vector2(s.lineAcc + s.cc->advX, lineHeight * s.lineNum);
if (s.i >= et) {
found = true;
if (s.i == s.lineEnd) measure = Vector2(0, lineHeight * (s.lineNum + 1)); // linebreak == next line
if (end == 0) measure = Vector2(0, lineHeight * s.lineNum);
return true;
}
return false;
}, maxWidth);
if (!found) { // catch newline-at-end
measure.x = 0;
measure.y += lineHeight;
}
return measure;
}
unsigned int BitmapFont::PointToIndex(const std::string& msg, Vector2 pt, float maxWidth) {
//pt -= Vector2(padX, 0*padY);
if (pt.y < 0) return 0;
unsigned int tl = std::floor(pt.y / lineHeight);
unsigned int idx;
ForChar(msg, [&, this, pt, tl](auto& s){
if (s.lineNum < tl) return false; // skip
if (s.lineNum > tl) { // huh.
return true;
}
if (s.i == s.lineEnd) {
if (s.i == s.lineStart) { // if blank line...
idx = s.i;
return true;
}
return false; // skip newlines on non-blank lines
}
if ((s.iline == 0 || pt.x >= s.lineAcc) && pt.x < s.lineAcc + s.cc->advX) {
idx = s.i;
if (pt.x > s.lineAcc + s.cc->advX * 0.5f) idx++;
return true;
}
idx = s.i+1;
return false;
}, maxWidth);
return idx;
}
void BitmapFont::ForChar(const std::string& msg, std::function<bool(CharLoopState&)> func, float maxWidth) {
struct LineStat {
unsigned int start;
unsigned int end;
float width;
};
unsigned int len = msg.length();
float space = Char(' ').advX;
std::vector<LineStat> lines;
{
LineStat cl = {0, 0, 0};
float ww = 0;
unsigned int ws = 0;
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 = (i >= len) ? '\n' : 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++;
}
}