Skip to content

Commit

Permalink
Implement correct visual text width calculation for displaying text
Browse files Browse the repository at this point in the history
Resolves #128
  • Loading branch information
IrneRacoonovich committed Sep 5, 2024
1 parent 22168f8 commit acf955e
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 49 deletions.
92 changes: 50 additions & 42 deletions lib/libtesla/include/tesla.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,15 @@ namespace tsl {
s32 x, y, w, h;
};

struct Glyph {
stbtt_fontinfo* currFont;
float currFontSize;
int bounds[4];
int xAdvance;
u8* glyphBmp;
int width, height;
};

/**
* @brief Manages the Tesla layer and draws raw data to the screen
*/
Expand Down Expand Up @@ -787,6 +796,41 @@ namespace tsl {
this->fillScreen({ 0x00, 0x00, 0x00, 0x00 });
}

Glyph* getGlyph(u32 character, float fontSize, bool monospace)
{
static std::unordered_map<u64, Glyph> s_glyphCache;
Glyph* glyph = nullptr;
u64 key = (static_cast<u64>(character) << 32) | static_cast<u64>(monospace) << 31 | static_cast<u64>(std::bit_cast<u32>(fontSize));

auto it = s_glyphCache.find(key);
if (it == s_glyphCache.end()) {
/* Cache glyph */
glyph = &s_glyphCache.emplace(key, Glyph()).first->second;

if (stbtt_FindGlyphIndex(&this->m_extFont, character))
glyph->currFont = &this->m_extFont;
else if (this->m_hasLocalFont && stbtt_FindGlyphIndex(&this->m_stdFont, character) == 0)
glyph->currFont = &this->m_localFont;
else
glyph->currFont = &this->m_stdFont;

glyph->currFontSize = stbtt_ScaleForPixelHeight(glyph->currFont, fontSize);

stbtt_GetCodepointBitmapBoxSubpixel(glyph->currFont, character, glyph->currFontSize, glyph->currFontSize,
0, 0, &glyph->bounds[0], &glyph->bounds[1], &glyph->bounds[2], &glyph->bounds[3]);

int yAdvance = 0;
stbtt_GetCodepointHMetrics(glyph->currFont, monospace ? 'W' : character, &glyph->xAdvance, &yAdvance);

glyph->glyphBmp = stbtt_GetCodepointBitmap(glyph->currFont, glyph->currFontSize, glyph->currFontSize, character, &glyph->width, &glyph->height, nullptr, nullptr);
} else {
/* Use cached glyph */
glyph = &it->second;
}

return glyph;
}

/**
* @brief Draws a string
*
Expand All @@ -803,15 +847,6 @@ namespace tsl {
s32 currX = x;
s32 currY = y;

struct Glyph {
stbtt_fontinfo *currFont;
float currFontSize;
int bounds[4];
int xAdvance;
u8 *glyphBmp;
int width, height;
};

static std::unordered_map<u64, Glyph> s_glyphCache;

do {
Expand All @@ -835,35 +870,7 @@ namespace tsl {
continue;
}

u64 key = (static_cast<u64>(currCharacter) << 32) | static_cast<u64>(monospace) << 31 | static_cast<u64>(std::bit_cast<u32>(fontSize));

Glyph *glyph = nullptr;

auto it = s_glyphCache.find(key);
if (it == s_glyphCache.end()) {
/* Cache glyph */
glyph = &s_glyphCache.emplace(key, Glyph()).first->second;

if (stbtt_FindGlyphIndex(&this->m_extFont, currCharacter))
glyph->currFont = &this->m_extFont;
else if(this->m_hasLocalFont && stbtt_FindGlyphIndex(&this->m_stdFont, currCharacter)==0)
glyph->currFont = &this->m_localFont;
else
glyph->currFont = &this->m_stdFont;

glyph->currFontSize = stbtt_ScaleForPixelHeight(glyph->currFont, fontSize);

stbtt_GetCodepointBitmapBoxSubpixel(glyph->currFont, currCharacter, glyph->currFontSize, glyph->currFontSize,
0, 0, &glyph->bounds[0], &glyph->bounds[1], &glyph->bounds[2], &glyph->bounds[3]);

int yAdvance = 0;
stbtt_GetCodepointHMetrics(glyph->currFont, monospace ? 'W' : currCharacter, &glyph->xAdvance, &yAdvance);

glyph->glyphBmp = stbtt_GetCodepointBitmap(glyph->currFont, glyph->currFontSize, glyph->currFontSize, currCharacter, &glyph->width, &glyph->height, nullptr, nullptr);
} else {
/* Use cached glyph */
glyph = &it->second;
}
Glyph* glyph = this->getGlyph(currCharacter, fontSize, monospace);

if (glyph->glyphBmp != nullptr && !std::iswspace(currCharacter) && fontSize > 0 && color.a != 0x0) {

Expand Down Expand Up @@ -940,20 +947,21 @@ namespace tsl {
return string;
}

private:
Renderer() {}

/**
* @brief Gets the renderer instance
*
* @return Renderer
*/
static Renderer& get() {
static Renderer& get()
{
static Renderer renderer;

return renderer;
}

private:
Renderer() { }

/**
* @brief Sets the opacity of the layer
*
Expand Down
31 changes: 24 additions & 7 deletions source/text_funcs.hpp
Original file line number Diff line number Diff line change
@@ -1,53 +1,70 @@
#pragma once

#include "tesla.hpp"

#include <fstream>
#include <iostream>
#include <string>
#include <utility>

size_t lineWidth(const std::string& line, const int fontSize = 19, const bool monospace = false)
{
size_t width = 0;
for (const auto character : line) {
auto glyph = tsl::gfx::Renderer::get().getGlyph(character, fontSize, monospace);
width += static_cast<size_t>(glyph->xAdvance * glyph->currFontSize);
}
return width;
}

std::pair<std::string, int> readTextFromFile(const std::string& filePath)
{
// log("Entered readTextFromFile");

std::string lines;
std::stringstream lines;
std::string currentLine;
std::ifstream file(filePath);
std::vector<std::string> words;
int lineCount = 0;
size_t maxRowLength = 35;
const size_t maxRowWidth = 363; // magic number. width of a tesla List drawing area
const size_t whitespaceWidth = lineWidth(" ");

std::string line;
while (std::getline(file, line)) {
if (line == "\r" || line.empty()) {
lines += "\n"; // Preserve empty lines
lines << std::endl; // Preserve empty lines
lineCount++;
continue;
}

std::istringstream lineStream(line);
std::string word;
std::string currentLine;
size_t currentLineWidth = 0;

while (lineStream >> word) {
if (currentLine.empty()) {
currentLine = word;
} else if (currentLine.length() + 1 + word.length() <= maxRowLength) {
currentLineWidth = lineWidth(currentLine);
} else if (const auto wordWidth = lineWidth(word); currentLineWidth + whitespaceWidth + wordWidth <= maxRowWidth) {
currentLine += " " + word;
currentLineWidth = currentLineWidth + whitespaceWidth + wordWidth;
} else {
lines += currentLine + "\n";
lines << currentLine << std::endl;
currentLine = word;
currentLineWidth = lineWidth(currentLine);
lineCount++;
}
}

if (!currentLine.empty()) {
lines += currentLine + "\n";
lines << currentLine << std::endl;
lineCount++;
}
}

file.close();
return std::make_pair(lines, lineCount);
return std::make_pair(lines.str(), lineCount);
}

bool write_to_file(const std::string& file_path, const std::string& line)
Expand Down

0 comments on commit acf955e

Please sign in to comment.