From 2ac93e2f07764b58d3cca929c650158361080597 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 28 Jan 2025 13:27:01 -0800
Subject: [PATCH] Fixed SDF text layout
We're also passing through the raw SDF values now so shaders can be used to render them correctly.
---
include/SDL3_ttf/SDL_ttf.h | 5 +-
src/SDL_gpu_textengine.c | 8 +-
src/SDL_ttf.c | 190 ++++++++++++++++++-------------------
3 files changed, 101 insertions(+), 102 deletions(-)
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index ea7f43d0..e614f41e 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -601,8 +601,9 @@ extern SDL_DECLSPEC TTF_HintingFlags SDLCALL TTF_GetFontHinting(const TTF_Font *
/**
* Enable Signed Distance Field rendering for a font.
*
- * This works with the Blended APIs. SDF is a technique that
- * helps fonts look sharp even when scaling and rotating.
+ * SDF is a technique that helps fonts look sharp even when scaling and rotating, and requires special shader support for display.
+ *
+ * This works with Blended APIs, and generates the raw signed distance values in the alpha channel of the resulting texture.
*
* This updates any TTF_Text objects using this font, and clears already-generated glyphs, if any, from the cache.
*
diff --git a/src/SDL_gpu_textengine.c b/src/SDL_gpu_textengine.c
index fd7142f3..c3486fcd 100644
--- a/src/SDL_gpu_textengine.c
+++ b/src/SDL_gpu_textengine.c
@@ -381,10 +381,10 @@ static bool UpdateGPUTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture,
static bool UpdateGlyph(SDL_GPUDevice *device, AtlasGlyph *glyph, SDL_Surface *surface)
{
- if (glyph->rect.w > 0 && glyph->rect.h > 0) {
- /* FIXME: We should update the whole texture at once or at least cache the transfer buffers */
- UpdateGPUTexture(device, glyph->atlas->texture, &glyph->rect, surface->pixels, surface->pitch);
- }
+ SDL_assert(glyph->rect.w > 0 && glyph->rect.h > 0);
+
+ /* FIXME: We should update the whole texture at once or at least cache the transfer buffers */
+ UpdateGPUTexture(device, glyph->atlas->texture, &glyph->rect, surface->pixels, surface->pitch);
return true;
}
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index 0abbbf1a..95bf11cc 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -546,18 +546,10 @@ static void BG_Blended_Opaque_SDF(const TTF_Image *image, Uint32 *destination, S
Uint32 width = image->width;
Uint32 height = image->rows;
- Uint32 s;
- Uint32 d;
-
while (height--) {
/* *INDENT-OFF* */
DUFFS_LOOP4(
- d = *dst;
- s = ((Uint32)*src++) << 24;
- if (s > d) {
- *dst = s;
- }
- dst++;
+ *dst++ = ((Uint32)*src++) << 24;
, width);
/* *INDENT-ON* */
src += srcskip;
@@ -566,6 +558,8 @@ static void BG_Blended_Opaque_SDF(const TTF_Image *image, Uint32 *destination, S
}
// Blended non-opaque SDF
+// Note: This doesn't make sense when we're outputting raw SDF values.
+// We'll just copy the alpha channel as-is for now.
static void BG_Blended_SDF(const TTF_Image *image, Uint32 *destination, Sint32 srcskip, Uint32 dstskip, Uint8 fg_alpha)
{
const Uint8 *src = image->buffer;
@@ -573,20 +567,11 @@ static void BG_Blended_SDF(const TTF_Image *image, Uint32 *destination, Sint32 s
Uint32 width = image->width;
Uint32 height = image->rows;
- Uint32 s;
- Uint32 d;
-
- Uint32 tmp;
+ (void)fg_alpha;
while (height--) {
/* *INDENT-OFF* */
DUFFS_LOOP4(
- d = *dst;
- tmp = fg_alpha * (*src++);
- s = DIVIDE_BY_255(tmp) << 24;
- if (s > d) {
- *dst = s;
- }
- dst++;
+ *dst++ = ((Uint32)*src++) << 24;
, width);
/* *INDENT-ON* */
src += srcskip;
@@ -1496,27 +1481,29 @@ static bool Render_Line_TextEngine(TTF_Font *font, TTF_Direction direction, int
x = xstart + FT_FLOOR(x) + glyph->sz_left;
y = ystart + FT_FLOOR(y) - glyph->sz_top;
- // Make sure glyph is inside text area
- above_w = x + glyph_width - width;
- above_h = y + glyph_rows - height;
+ if (!glyph_font->render_sdf) {
+ // Make sure glyph is inside text area
+ above_w = x + glyph_width - width;
+ above_h = y + glyph_rows - height;
- if (x < 0) {
- int tmp = -x;
- x = 0;
- glyph_x += tmp;
- glyph_width -= tmp;
- }
- if (above_w > 0) {
- glyph_width -= above_w;
- }
- if (y < 0) {
- int tmp = -y;
- y = 0;
- glyph_y += tmp;
- glyph_rows -= tmp;
- }
- if (above_h > 0) {
- glyph_rows -= above_h;
+ if (x < 0) {
+ int tmp = -x;
+ x = 0;
+ glyph_x += tmp;
+ glyph_width -= tmp;
+ }
+ if (above_w > 0) {
+ glyph_width -= above_w;
+ }
+ if (y < 0) {
+ int tmp = -y;
+ y = 0;
+ glyph_y += tmp;
+ glyph_rows -= tmp;
+ }
+ if (above_h > 0) {
+ glyph_rows -= above_h;
+ }
}
if (glyph_width > 0 && glyph_rows > 0) {
@@ -1533,6 +1520,12 @@ static bool Render_Line_TextEngine(TTF_Font *font, TTF_Direction direction, int
op->copy.dst.y = y;
op->copy.dst.w = op->copy.src.w;
op->copy.dst.h = op->copy.src.h;
+ if (glyph_font->render_sdf) {
+ op->copy.dst.x -= DEFAULT_SDF_SPREAD;
+ op->copy.dst.y -= DEFAULT_SDF_SPREAD;
+ op->copy.dst.w -= DEFAULT_SDF_SPREAD;
+ op->copy.dst.h -= DEFAULT_SDF_SPREAD;
+ }
if (FT_HAS_SVG(glyph_font->face)) {
op->copy.flags = TTF_COPY_OPERATION_IMAGE;
}
@@ -2531,7 +2524,6 @@ static bool Load_Glyph(TTF_Font *font, c_glyph *cached, int want, int translatio
// Adjust for SDF
if (font->render_sdf) {
- // Default 'spread' property
cached->sz_width += 2 * DEFAULT_SDF_SPREAD;
cached->sz_rows += 2 * DEFAULT_SDF_SPREAD;
}
@@ -2837,30 +2829,9 @@ static bool Load_Glyph(TTF_Font *font, c_glyph *cached, int want, int translatio
*dstp++ = r;
*dstp++ = alpha;
}
+
} else {
-#if TTF_USE_SDF
- if (ft_render_mode != FT_RENDER_MODE_SDF) {
- SDL_memcpy(dstp, srcp, src->width);
- } else {
- unsigned int x;
- for (x = 0; x < src->width; x++) {
- Uint8 s = srcp[x];
- Uint8 d;
- if (s < 128) {
- d = 256 - (128 - s) * 2;
- } else {
- d = 255;
- /* some glitch ?
- if (s == 255) {
- d = 0;
- }*/
- }
- dstp[x] = d;
- }
- }
-#else
SDL_memcpy(dstp, srcp, src->width);
-#endif
}
}
}
@@ -3119,8 +3090,18 @@ SDL_Surface *TTF_GetGlyphImageForIndex(TTF_Font *font, Uint32 glyph_index)
TTF_CHECK_FONT(font, NULL);
- if (!Find_GlyphByIndex(font, glyph_index, PIXMAP, 0, 0, NULL, &image)) {
- return NULL;
+ if (font->render_sdf) {
+ if (!Find_GlyphByIndex(font, glyph_index, COLOR, 0, 0, NULL, &image)) {
+ return NULL;
+ }
+ } else {
+ if (!Find_GlyphByIndex(font, glyph_index, PIXMAP, 0, 0, NULL, &image)) {
+ return NULL;
+ }
+ }
+
+ if (image->width == 0 || image->rows == 0) {
+ return SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_ARGB8888);
}
surface = SDL_CreateSurface(image->width, image->rows, SDL_PIXELFORMAT_ARGB8888);
@@ -3584,12 +3565,13 @@ static GlyphPositions *GetCachedGlyphPositions(TTF_Font *font, const char *text,
return font->positions;
}
-static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, int *w, int *h, int *xstart, int *ystart, bool measure_width, int max_width, int *measured_width, size_t *measured_length)
+static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, int *w, int *h, int *xstart, int *ystart, bool measure_width, int max_width, int *measured_width, size_t *measured_length, bool include_spread)
{
int x = 0;
int pos_x, pos_y;
- int minx = 0, maxx = 0;
- int miny = 0, maxy = 0;
+ int minx, maxx;
+ int miny, maxy;
+ int spread_adjustment;
if (w) {
*w = 0;
@@ -3612,7 +3594,11 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, T
*measured_length = length;
}
- maxy = font->height;
+ if (font->render_sdf && !include_spread) {
+ spread_adjustment = DEFAULT_SDF_SPREAD;
+ } else {
+ spread_adjustment = 0;
+ }
GlyphPositions *positions = GetCachedGlyphPositions(font, text, length, direction, script);
if (!positions) {
@@ -3620,6 +3606,11 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, T
}
if (positions->len > 0) {
+ minx = INT_MAX;
+ maxx = INT_MIN;
+ miny = INT_MAX;
+ maxy = font->height;
+
if (positions->pos[0].offset == 0) {
// Left to right layout
for (int i = 0; i < positions->len; ++i) {
@@ -3627,13 +3618,13 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, T
c_glyph *glyph = pos->glyph;
// Compute provisional global bounding box
- pos_x = FT_FLOOR(pos->x) + glyph->sz_left;
- pos_y = FT_FLOOR(pos->y) - glyph->sz_top;
+ pos_x = FT_FLOOR(pos->x) + glyph->sz_left + spread_adjustment;
+ pos_y = FT_FLOOR(pos->y) - glyph->sz_top + spread_adjustment;
minx = SDL_min(minx, pos_x);
- maxx = SDL_max(maxx, pos_x + glyph->sz_width);
+ maxx = SDL_max(maxx, pos_x + glyph->sz_width - 2 * spread_adjustment);
miny = SDL_min(miny, pos_y);
- maxy = SDL_max(maxy, pos_y + glyph->sz_rows);
+ maxy = SDL_max(maxy, pos_y + glyph->sz_rows - 2 * spread_adjustment);
x += pos->x_advance;
#if !TTF_USE_HARFBUZZ
@@ -3666,13 +3657,13 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, T
c_glyph *glyph = pos->glyph;
// Compute provisional global bounding box
- pos_x = FT_FLOOR(pos->x) + glyph->sz_left;
- pos_y = FT_FLOOR(pos->y) - glyph->sz_top;
+ pos_x = FT_FLOOR(pos->x) + glyph->sz_left + spread_adjustment;
+ pos_y = FT_FLOOR(pos->y) - glyph->sz_top + spread_adjustment;
minx = SDL_min(minx, pos_x);
- maxx = SDL_max(maxx, pos_x + glyph->sz_width);
+ maxx = SDL_max(maxx, pos_x + glyph->sz_width - 2 * spread_adjustment);
miny = SDL_min(miny, pos_y);
- maxy = SDL_max(maxy, pos_y + glyph->sz_rows);
+ maxy = SDL_max(maxy, pos_y + glyph->sz_rows - 2 * spread_adjustment);
// Measurement mode
if (measure_width) {
@@ -3701,6 +3692,11 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, T
minx = 0;
}
}
+ } else {
+ minx = 0;
+ maxx = 0;
+ miny = 0;
+ maxy = font->height;
}
// Allows to render a string with only one space (bug 4344).
@@ -3711,8 +3707,8 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, T
if (xstart) {
*xstart = (minx < 0) ? -minx : 0;
*xstart += font->outline;
- if (font->render_sdf) {
- *xstart += DEFAULT_SDF_SPREAD; // Default 'spread' property
+ if (font->render_sdf && include_spread) {
+ *xstart += DEFAULT_SDF_SPREAD;
}
}
@@ -3720,8 +3716,8 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, T
if (ystart) {
*ystart = (miny < 0) ? -miny : 0;
*ystart += font->outline;
- if (font->render_sdf) {
- *ystart += DEFAULT_SDF_SPREAD; // Default 'spread' property
+ if (font->render_sdf && include_spread) {
+ *ystart += DEFAULT_SDF_SPREAD;
}
}
@@ -3744,7 +3740,7 @@ bool TTF_GetStringSize(TTF_Font *font, const char *text, size_t length, int *w,
if (!length && text) {
length = SDL_strlen(text);
}
- return TTF_Size_Internal(font, text, length, font->direction, font->script, w, h, NULL, NULL, NO_MEASUREMENT);
+ return TTF_Size_Internal(font, text, length, font->direction, font->script, w, h, NULL, NULL, NO_MEASUREMENT, true);
}
bool TTF_MeasureString(TTF_Font *font, const char *text, size_t length, int max_width, int *measured_width, size_t *measured_length)
@@ -3752,7 +3748,7 @@ bool TTF_MeasureString(TTF_Font *font, const char *text, size_t length, int max_
if (!length && text) {
length = SDL_strlen(text);
}
- return TTF_Size_Internal(font, text, length, font->direction, font->script, NULL, NULL, NULL, NULL, true, max_width, measured_width, measured_length);
+ return TTF_Size_Internal(font, text, length, font->direction, font->script, NULL, NULL, NULL, NULL, true, max_width, measured_width, measured_length, true);
}
static SDL_Surface* TTF_Render_Internal(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, const render_mode_t render_mode)
@@ -3785,7 +3781,7 @@ static SDL_Surface* TTF_Render_Internal(TTF_Font *font, const char *text, size_t
#endif
// Get the dimensions of the text surface
- if (!TTF_Size_Internal(font, text, length, font->direction, font->script, &width, &height, &xstart, &ystart, NO_MEASUREMENT) || !width) {
+ if (!TTF_Size_Internal(font, text, length, font->direction, font->script, &width, &height, &xstart, &ystart, NO_MEASUREMENT, true) || !width) {
SDL_SetError("Text has zero width");
goto failure;
}
@@ -3903,7 +3899,7 @@ static bool CharacterIsNewLine(Uint32 c)
return false;
}
-static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, int xoffset, int wrap_width, bool trim_whitespace, TTF_Line **lines, int *num_lines, int *w, int *h)
+static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, TTF_Direction direction, Uint32 script, int xoffset, int wrap_width, bool trim_whitespace, TTF_Line **lines, int *num_lines, int *w, int *h, bool include_spread)
{
int width, height;
int i, numLines = 0, rowHeight;
@@ -3929,7 +3925,7 @@ static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, TTF
}
// Get the dimensions of the text surface
- if (!TTF_Size_Internal(font, text, length, direction, script, &width, &height, NULL, NULL, NO_MEASUREMENT) || !width) {
+ if (!TTF_Size_Internal(font, text, length, direction, script, &width, &height, NULL, NULL, NO_MEASUREMENT, include_spread) || !width) {
return SDL_SetError("Text has zero width");
}
@@ -3984,7 +3980,7 @@ static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, TTF
max_width = SDL_max(max_width - xoffset, 1);
}
size_t max_length = 0;
- if (!TTF_Size_Internal(font, spot, left, direction, script, NULL, NULL, NULL, NULL, true, max_width, NULL, &max_length)) {
+ if (!TTF_Size_Internal(font, spot, left, direction, script, NULL, NULL, NULL, NULL, true, max_width, NULL, &max_length, include_spread)) {
SDL_SetError("Error measure text");
goto done;
}
@@ -4067,7 +4063,7 @@ static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, TTF
for (i = 0; i < numLines; i++) {
int w_tmp, h_tmp;
- if (TTF_Size_Internal(font, strLines[i].text, strLines[i].length, font->direction, font->script, &w_tmp, &h_tmp, NULL, NULL, NO_MEASUREMENT)) {
+ if (TTF_Size_Internal(font, strLines[i].text, strLines[i].length, font->direction, font->script, &w_tmp, &h_tmp, NULL, NULL, NO_MEASUREMENT, include_spread)) {
width = SDL_max(w_tmp, width);
}
}
@@ -4110,7 +4106,7 @@ static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, TTF
bool TTF_GetStringSizeWrapped(TTF_Font *font, const char *text, size_t length, int wrap_width, int *w, int *h)
{
- return GetWrappedLines(font, text, length, font->direction, font->script, 0, wrap_width, true, NULL, NULL, w, h);
+ return GetWrappedLines(font, text, length, font->direction, font->script, 0, wrap_width, true, NULL, NULL, w, h, true);
}
static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, int wrap_width, const render_mode_t render_mode)
@@ -4121,7 +4117,7 @@ static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text
int i, numLines = 0;
TTF_Line *strLines = NULL;
- if (!GetWrappedLines(font, text, length, font->direction, font->script, 0, wrap_width, true, &strLines, &numLines, &width, &height)) {
+ if (!GetWrappedLines(font, text, length, font->direction, font->script, 0, wrap_width, true, &strLines, &numLines, &width, &height, true)) {
return NULL;
}
@@ -4163,7 +4159,7 @@ static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text
int xstart, ystart, line_width, xoffset;
// Initialize xstart, ystart and compute positions
- if (!TTF_Size_Internal(font, strLines[i].text, strLines[i].length, font->direction, font->script, &line_width, NULL, &xstart, &ystart, NO_MEASUREMENT)) {
+ if (!TTF_Size_Internal(font, strLines[i].text, strLines[i].length, font->direction, font->script, &line_width, NULL, &xstart, &ystart, NO_MEASUREMENT, true)) {
goto failure;
}
@@ -4411,7 +4407,7 @@ static bool LayoutText(TTF_Text *text)
TTF_Direction direction = TTF_GetTextDirection(text);
Uint32 script = TTF_GetTextScript(text);
- if (!GetWrappedLines(font, text->text, length, direction, script, text->internal->x, wrap_width, trim_whitespace, &strLines, &numLines, &width, &height)) {
+ if (!GetWrappedLines(font, text->text, length, direction, script, text->internal->x, wrap_width, trim_whitespace, &strLines, &numLines, &width, &height, false)) {
return true;
}
height += text->internal->y;
@@ -4452,7 +4448,7 @@ static bool LayoutText(TTF_Text *text)
}
// Initialize xstart, ystart and compute positions
- if (!TTF_Size_Internal(font, strLines[i].text, strLines[i].length, direction, script, &line_width, NULL, &xstart, &ystart, NO_MEASUREMENT)) {
+ if (!TTF_Size_Internal(font, strLines[i].text, strLines[i].length, direction, script, &line_width, NULL, &xstart, &ystart, NO_MEASUREMENT, false)) {
goto done;
}
@@ -5693,9 +5689,11 @@ bool TTF_SetFontSDF(TTF_Font *font, bool enabled)
{
TTF_CHECK_FONT(font, false);
#if TTF_USE_SDF
- font->render_sdf = enabled;
- Flush_Cache(font);
- UpdateFontText(font, NULL);
+ if (font->render_sdf != enabled) {
+ font->render_sdf = enabled;
+ Flush_Cache(font);
+ UpdateFontText(font, NULL);
+ }
return true;
#else
(void)enabled;