forked from Mirror/libstarlight
make TextConfig.Print string ref const, make form priority float, ScrollField::ScrollIntoView, minor BitmapFont fix
228 lines
7.1 KiB
C++
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++;
|
|
}
|
|
|
|
}
|