Files
Goober5000andClaude Opus 4.8 f3817c80fa Scale font line and tab advance by the font scale factor
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]>
2026-09-03 01:55:03 -04:00

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();
}
}