mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
Multi-line and tabbed strings drawn with a scaleMultiplier != 1 overlapped or misaligned because the font code advanced newlines and tabs by the unscaled font height/tab width -- most visible in FRED's viewport labels now that they honor an adjustable font scale. Scale the newline and tab advance in both the rendering and measurement paths of each backend: the NVG path (gr_string and NVGFont::getStringSize) and the VFNT path (gr_string_old and VFNTFont::getStringSize), which also gains tab handling. The tab width is hoisted onto the shared FSFont base so both backends share it. While here, pass the FSFont wrapper to gr_string_old instead of a bag of scalars, make its font pointer const, and consolidate the get_char_width_old externs into a single declaration in font_internal.h. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
240 lines
4.9 KiB
C++
240 lines
4.9 KiB
C++
|
|
#include "graphics/software/NVGFont.h"
|
|
#include "graphics/software/font_internal.h"
|
|
#include "graphics/paths/PathRenderer.h"
|
|
|
|
#include "mod_table/mod_table.h"
|
|
#include "options/Option.h"
|
|
|
|
#include "localization/localize.h"
|
|
|
|
#include <limits>
|
|
|
|
namespace
|
|
{
|
|
const char* const TOKEN_SEPARATORS = "\n\t\r";
|
|
}
|
|
|
|
namespace font
|
|
{
|
|
size_t NVGFont::getTokenLength(const char *string, size_t maxLength)
|
|
{
|
|
Assert(string != NULL);
|
|
|
|
size_t length = 0;
|
|
|
|
if (maxLength <= 0)
|
|
return 0;
|
|
|
|
if (!Unicode_text_mode) {
|
|
// If we are not in unicode mode then we need to handle special characters
|
|
// Otherwise we just assume that the text is UTF-8 encoded
|
|
if (*string >= Lcl_special_chars || *string < 0)
|
|
return 1;
|
|
}
|
|
|
|
const char *nullPtr = strchr(string, '\0');
|
|
const char *nextToken = strpbrk(string, TOKEN_SEPARATORS);
|
|
|
|
// WOHOO! Pointer arithmetic!!!
|
|
if (nullPtr != NULL && (nextToken == NULL || nullPtr < nextToken))
|
|
{
|
|
length = nullPtr - string;
|
|
}
|
|
else if (nextToken != NULL)
|
|
{
|
|
if (nextToken == string)
|
|
{
|
|
length = 1;
|
|
}
|
|
else
|
|
{
|
|
length = nextToken - string;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
length = strlen(string);
|
|
}
|
|
|
|
if (length > maxLength)
|
|
{
|
|
length = maxLength;
|
|
}
|
|
|
|
if (!Unicode_text_mode) {
|
|
// Same as above
|
|
for (size_t i = 0; i < length; i++)
|
|
{
|
|
if (string[i] >= Lcl_special_chars || string[i] < 0)
|
|
{
|
|
// Special character needs to be handled separately
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
void NVGFont::setHandle(int handle)
|
|
{
|
|
Assertion(handle >= 0, "Invalid font handle passed!");
|
|
m_handle = handle;
|
|
}
|
|
|
|
void NVGFont::setSize(float size)
|
|
{
|
|
Assertion(size > 0.f, "Invalid size %f passed!", size);
|
|
m_size = size;
|
|
}
|
|
|
|
void NVGFont::setLetterSpacing(float spacing)
|
|
{
|
|
Assertion(spacing >= 0.0f, "Invalid letter spacing passed!");
|
|
m_letterSpacing = spacing;
|
|
}
|
|
|
|
void NVGFont::setSpecialCharacterFont(font* fontData)
|
|
{
|
|
Assertion(fontData != nullptr, "Invalid font data pointer passed!");
|
|
m_specialCharacters = fontData;
|
|
}
|
|
|
|
float NVGFont::getTextHeight() const
|
|
{
|
|
return m_lineHeight;
|
|
}
|
|
|
|
void NVGFont::getStringSize(const char *text, size_t textLen, int resize_mode, float *width, float *height, float scaleMultiplier) const
|
|
{
|
|
using namespace graphics::paths;
|
|
|
|
auto path = PathRenderer::instance();
|
|
|
|
path->saveState();
|
|
path->resetState();
|
|
|
|
float scale_factor = (canScale && !Fred_running) ? get_font_scale_factor() : 1.0f;
|
|
scale_factor *= scaleMultiplier;
|
|
|
|
path->fontFaceId(m_handle);
|
|
path->fontSize(m_size * scale_factor);
|
|
path->textLetterSpacing(m_letterSpacing);
|
|
path->textAlign(static_cast<TextAlign>(ALIGN_TOP | ALIGN_LEFT));
|
|
|
|
if (resize_mode != -1)
|
|
{
|
|
float scaleX = 1.0f;
|
|
float scaleY = 1.0f;
|
|
|
|
gr_resize_screen_posf(nullptr, nullptr, &scaleX, &scaleY, resize_mode);
|
|
|
|
path->scale(scaleX, scaleY);
|
|
}
|
|
|
|
float w = 0.0f;
|
|
float h = this->getHeight() * scale_factor;
|
|
|
|
size_t tokenLength;
|
|
|
|
const char *s = text;
|
|
bool specialChar = false;
|
|
float lineWidth = 0.0f;
|
|
|
|
while ((tokenLength = getTokenLength(s, textLen)) > 0)
|
|
{
|
|
if (tokenLength > textLen)
|
|
{
|
|
tokenLength = textLen;
|
|
}
|
|
|
|
if (tokenLength == 1)
|
|
{
|
|
// We may have encountered a special character
|
|
switch (*s)
|
|
{
|
|
case '\n':
|
|
specialChar = true;
|
|
|
|
h += this->getHeight() * scale_factor;
|
|
lineWidth = 0.0f;
|
|
break;
|
|
case '\t':
|
|
specialChar = true;
|
|
|
|
lineWidth += this->getTabWidth() * scale_factor;
|
|
break;
|
|
default:
|
|
if (!Unicode_text_mode) {
|
|
// Same as before, this code is only needed in non-unicode mode
|
|
if (*s >= Lcl_special_chars || *s < 0)
|
|
{
|
|
specialChar = true;
|
|
|
|
int charWidth;
|
|
int spacing;
|
|
|
|
if (m_specialCharacters == nullptr) {
|
|
Error(LOCATION,
|
|
"Font %s has no special characters font! This is usually caused by ignoring a font table parsing warning.",
|
|
getName().c_str());
|
|
}
|
|
|
|
get_char_width_old(m_specialCharacters, static_cast<ubyte>(*s), '\0', &charWidth, &spacing);
|
|
|
|
lineWidth += i2fl(spacing);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!specialChar)
|
|
{
|
|
lineWidth += path->textBounds(0.f, 0.f, s, s + tokenLength, nullptr);
|
|
}
|
|
|
|
w = MAX(w, lineWidth);
|
|
|
|
specialChar = false;
|
|
|
|
s = s + tokenLength;
|
|
|
|
textLen -= tokenLength;
|
|
|
|
// see above, tokenLength is at most equal to textLen so this == 0 comparision should be safe
|
|
if (textLen == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (height)
|
|
*height = h;
|
|
|
|
if (width)
|
|
*width = w;
|
|
|
|
path->restoreState();
|
|
}
|
|
void NVGFont::computeFontMetrics() {
|
|
auto path = graphics::paths::PathRenderer::instance();
|
|
|
|
path->saveState();
|
|
path->resetState();
|
|
|
|
path->fontFaceId(m_handle);
|
|
path->fontSize(m_size);
|
|
path->textLetterSpacing(m_letterSpacing);
|
|
|
|
path->textMetrics(&_ascender, &_descender, &m_lineHeight);
|
|
|
|
path->restoreState();
|
|
|
|
_height = m_lineHeight + this->offsetTop + this->offsetBottom;
|
|
|
|
checkFontMetrics();
|
|
}
|
|
}
|