From 33144e5146ca4b53981132558bc1c2d1900ad6ef Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 6 Oct 2024 22:36:29 -0700
Subject: [PATCH] Added substring flags and a substring for newlines
This provides more information to the application about wrap points and allows moving the edit cursor up to newlines and then past them.
---
examples/editbox.c | 28 +--
include/SDL3_ttf/SDL_ttf.h | 71 ++++++--
src/SDL_ttf.c | 345 ++++++++++++++++++++++++-------------
src/SDL_ttf.sym | 2 +
4 files changed, 295 insertions(+), 151 deletions(-)
diff --git a/examples/editbox.c b/examples/editbox.c
index b2d68970..71226660 100644
--- a/examples/editbox.c
+++ b/examples/editbox.c
@@ -294,10 +294,10 @@ static void UpdateTextInputArea(EditBox *edit)
}
SDL_Rect rect;
- rect.x = (int)SDL_floorf(window_edit_rect_min.x);
- rect.y = (int)SDL_floorf(window_edit_rect_min.y);
- rect.w = (int)SDL_floorf(window_edit_rect_max.x - window_edit_rect_min.x);
- rect.h = (int)SDL_floorf(window_edit_rect_max.y - window_edit_rect_min.y);
+ rect.x = (int)SDL_roundf(window_edit_rect_min.x);
+ rect.y = (int)SDL_roundf(window_edit_rect_min.y);
+ rect.w = (int)SDL_roundf(window_edit_rect_max.x - window_edit_rect_min.x);
+ rect.h = (int)SDL_roundf(window_edit_rect_max.y - window_edit_rect_min.y);
int cursor_offset = (int)SDL_roundf(window_cursor.x - window_edit_rect_min.x);
SDL_SetTextInputArea(edit->window, &rect, cursor_offset);
}
@@ -447,6 +447,10 @@ void EditBox_Draw(EditBox *edit)
static int GetCursorTextIndex(TTF_Font *font, int x, const TTF_SubString *substring)
{
+ if (substring->flags & TTF_SUBSTRING_LINE_END) {
+ return substring->offset;
+ }
+
bool round_down;
if (TTF_GetFontDirection(font) == TTF_DIRECTION_RTL) {
round_down = (x > (substring->rect.x + substring->rect.w / 2));
@@ -529,11 +533,11 @@ void EditBox_MoveCursorUp(EditBox *edit)
int fontHeight = TTF_GetFontHeight(edit->font);
int x, y;
if (TTF_GetFontDirection(edit->font) == TTF_DIRECTION_RTL) {
- x = substring.rect.x + substring.rect.w;
+ x = substring.rect.x + substring.rect.w - 1;
} else {
x = substring.rect.x;
}
- y = substring.rect.y - fontHeight;
+ y = substring.rect.y - fontHeight / 2;
if (TTF_GetTextSubStringForPoint(edit->text, x, y, &substring)) {
SetCursorPosition(edit, GetCursorTextIndex(edit->font, x, &substring));
}
@@ -551,11 +555,11 @@ void EditBox_MoveCursorDown(EditBox *edit)
int fontHeight = TTF_GetFontHeight(edit->font);
int x, y;
if (TTF_GetFontDirection(edit->font) == TTF_DIRECTION_RTL) {
- x = substring.rect.x + substring.rect.w;
+ x = substring.rect.x + substring.rect.w - 1;
} else {
x = substring.rect.x;
}
- y = substring.rect.y + substring.rect.h + fontHeight;
+ y = substring.rect.y + substring.rect.h + fontHeight / 2;
if (TTF_GetTextSubStringForPoint(edit->text, x, y, &substring)) {
SetCursorPosition(edit, GetCursorTextIndex(edit->font, x, &substring));
}
@@ -692,8 +696,8 @@ static bool HandleMouseDown(EditBox *edit, float x, float y)
/* Set the cursor position */
TTF_SubString substring;
- int textX = (int)SDL_roundf(x - (edit->rect.x + 4.0f));
- int textY = (int)SDL_roundf(y - (edit->rect.y + 4.0f));
+ int textX = (int)SDL_roundf(x - edit->rect.x);
+ int textY = (int)SDL_roundf(y - edit->rect.y);
if (!TTF_GetTextSubStringForPoint(edit->text, textX, textY, &substring)) {
SDL_Log("Couldn't get cursor location: %s\n", SDL_GetError());
return false;
@@ -820,9 +824,7 @@ void EditBox_Paste(EditBox *edit)
}
const char *text = SDL_GetClipboardText();
- size_t length = SDL_strlen(text);
- TTF_InsertTextString(edit->text, edit->cursor, text, length);
- SetCursorPosition(edit, (int)(edit->cursor + length));
+ EditBox_Insert(edit, text);
}
void EditBox_Insert(EditBox *edit, const char *text)
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index 88c3c680..605fc42a 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -1687,30 +1687,49 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetTextWrapping(TTF_Text *text, bool *wrap,
*/
extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSize(TTF_Text *text, int *w, int *h);
+/**
+ * Flags for TTF_SubString
+ *
+ * \since This datatype is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_SubString
+ */
+typedef Uint32 TTF_SubStringFlags;
+
+#define TTF_SUBSTRING_TEXT_START 0x00000001 /**< This substring contains the beginning of the text */
+#define TTF_SUBSTRING_LINE_START 0x00000002 /**< This substring contains the beginning of line `line_index` */
+#define TTF_SUBSTRING_LINE_END 0x00000004 /**< This substring contains the end of line `line_index` */
+#define TTF_SUBSTRING_TEXT_END 0x00000008 /**< This substring contains the end of the text */
+
/**
* The representation of a substring within text.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
+ * \sa TTF_GetNextTextSubString
+ * \sa TTF_GetPreviousTextSubString
* \sa TTF_GetTextSubString
- * \sa TTF_GetTextSubStringAtPoint
+ * \sa TTF_GetTextSubStringForLine
+ * \sa TTF_GetTextSubStringForPoint
+ * \sa TTF_GetTextSubStringsForRange
*/
typedef struct TTF_SubString
{
- int offset; /**< The byte offset from the beginning of the text */
- int length; /**< The byte length starting at the offset */
- int line_index; /**< The index of the line that contains this substring */
- int cluster_index; /**< The internal cluster index, used for quickly iterating */
- SDL_Rect rect; /**< The rectangle, relative to the top left of the text, containing the substring */
+ TTF_SubStringFlags flags; /**< The flags for this substring */
+ int offset; /**< The byte offset from the beginning of the text */
+ int length; /**< The byte length starting at the offset */
+ int line_index; /**< The index of the line that contains this substring */
+ int cluster_index; /**< The internal cluster index, used for quickly iterating */
+ SDL_Rect rect; /**< The rectangle, relative to the top left of the text, containing the substring */
} TTF_SubString;
/**
* Get the substring of a text object that surrounds a text offset.
*
- * If `offset` is less than 0, this will return a zero width substring at the
- * beginning of the text. If `offset` is greater than or equal to the length
- * of the text string, this will return a zero width substring at the end of
- * the text.
+ * If `offset` is less than 0, this will return a zero length substring at the
+ * beginning of the text with the TTF_SUBSTRING_TEXT_START flag set. If `offset` is greater than or equal to the length
+ * of the text string, this will return a zero length substring at the end of
+ * the text with the TTF_SUBSTRING_TEXT_END flag set.
*
* \param text the TTF_Text to query.
* \param offset a byte offset into the text string.
@@ -1724,10 +1743,10 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubString(TTF_Text *text, int offset
/**
* Get the substring of a text object that contains the given line.
*
- * If `line` is less than 0, this will return a zero width substring at the
- * beginning of the text. If `line` is greater than or equal to
- * `text->num_lines` this will return a zero width substring at the end of the
- * text.
+ * If `line` is less than 0, this will return a zero length substring at the
+ * beginning of the text with the TTF_SUBSTRING_TEXT_START flag set. If `line` is greater than or equal to
+ * `text->num_lines` this will return a zero length substring at the end of the
+ * text with the TTF_SUBSTRING_TEXT_END flag set.
*
* \param text the TTF_Text to query.
* \param line a zero-based line index, in the range [0 .. text->num_lines-1].
@@ -1771,6 +1790,30 @@ extern SDL_DECLSPEC TTF_SubString ** SDLCALL TTF_GetTextSubStringsForRange(TTF_T
*/
extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSubStringForPoint(TTF_Text *text, int x, int y, TTF_SubString *substring);
+/**
+ * Get the previous substring in a text object
+ *
+ * If called at the start of the text, this will return a zero length substring with the TTF_SUBSTRING_TEXT_START flag set.
+ *
+ * \param text the TTF_Text to query.
+ * \param substring the TTF_SubString to query.
+ * \param next a pointer filled in with the previous substring.
+ * \returns true on success or false on failure; call SDL_GetError() for more information.
+ */
+extern SDL_DECLSPEC bool SDLCALL TTF_GetPreviousTextSubString(TTF_Text *text, const TTF_SubString *substring, TTF_SubString *previous);
+
+/**
+ * Get the next substring in a text object
+ *
+ * If called at the end of the text, this will return a zero length substring with the TTF_SUBSTRING_TEXT_END flag set.
+ *
+ * \param text the TTF_Text to query.
+ * \param substring the TTF_SubString to query.
+ * \param next a pointer filled in with the next substring.
+ * \returns true on success or false on failure; call SDL_GetError() for more information.
+ */
+extern SDL_DECLSPEC bool SDLCALL TTF_GetNextTextSubString(TTF_Text *text, const TTF_SubString *substring, TTF_SubString *next);
+
/**
* Update the layout of a text object.
*
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index 38ac12b0..3fc57a2d 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -1920,10 +1920,6 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
font->generation = 1;
font->hdpi = TTF_DEFAULT_DPI;
font->vdpi = TTF_DEFAULT_DPI;
-#if TTF_USE_HARFBUZZ
- font->hb_direction = HB_DIRECTION_LTR;
- font->hb_script = HB_SCRIPT_UNKNOWN;
-#endif
font->text = SDL_CreateHashTable(NULL, 16, SDL_HashPointer, SDL_KeyMatchPointer, NULL, false);
if (!font->text) {
@@ -2026,9 +2022,8 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
* you will get mismatching advances and raster. */
hb_ft_font_set_load_flags(font->hb_font, FT_LOAD_DEFAULT | font->ft_load_target);
- // By default the script / direction are inherited from global variables
- font->hb_script = HB_SCRIPT_INVALID;
- font->hb_direction = HB_DIRECTION_INVALID;
+ font->hb_direction = HB_DIRECTION_LTR;
+ font->hb_script = HB_SCRIPT_UNKNOWN;
font->hb_language = hb_language_from_string("", -1);
#endif
@@ -3001,13 +2996,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
hb_buffer_guess_segment_properties(hb_buffer);
// Layout the text
- if (length > 0 && text[length - 1] == '\n') {
- // Replace newline with ZERO WIDTH SPACE
- hb_buffer_add_utf8(hb_buffer, text, (int)(length - 1), 0, -1);
- hb_buffer_add_utf8(hb_buffer, "\xE2\x80\x8B", 3, 0, -1);
- } else {
- hb_buffer_add_utf8(hb_buffer, text, (int)length, 0, -1);
- }
+ hb_buffer_add_utf8(hb_buffer, text, (int)length, 0, -1);
hb_feature_t userfeatures[1];
userfeatures[0].tag = HB_TAG('k','e','r','n');
@@ -3044,9 +3033,6 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
continue;
}
- if (c == '\n') {
- c = 0x200B; // ZERO WIDTH SPACE
- }
#endif
if (!Find_GlyphByIndex(font, idx, 0, 0, 0, 0, 0, &glyph, NULL)) {
goto failure;
@@ -3467,13 +3453,22 @@ static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, int
}
} while (left > 0);
- // Trim whitespace from the wrapped lines
- for (i = 0; i < (numLines - 1); ++i) {
+ // Trim whitespace from the wrapped lines and newlines from unwrapped lines
+ for (i = 0; i < numLines; ++i) {
TTF_Line *line = &strLines[i];
- while (line->length > 0 &&
- CharacterIsDelimiter(line->text[line->length - 1]) &&
- !CharacterIsNewLine(line->text[line->length - 1])) {
+ if (line->length == 0) {
+ continue;
+ }
+ if (CharacterIsNewLine(line->text[line->length - 1])) {
--line->length;
+ if (line->text[line->length - 1] == '\r') {
+ --line->length;
+ }
+ } else {
+ while (line->length > 0 &&
+ CharacterIsDelimiter(line->text[line->length - 1])) {
+ --line->length;
+ }
}
}
}
@@ -3717,32 +3712,74 @@ static int SDLCALL SortClusters(const void *a, const void *b)
TTF_SubString *A = (TTF_SubString *)a;
TTF_SubString *B = (TTF_SubString *)b;
+ if (A->offset == B->offset) {
+ if (A->flags & TTF_SUBSTRING_LINE_END) {
+ return -1;
+ }
+ if (B->flags & TTF_SUBSTRING_LINE_END) {
+ return 1;
+ }
+ return 0;
+ }
return (A->offset - B->offset);
}
-static bool CalculateClusterLengths(TTF_SubString *clusters, int num_clusters, size_t length, int *lines)
+static int CalculateClusterLengths(TTF_Font *font, TTF_SubString *clusters, int num_clusters, size_t length, int *lines)
{
SDL_qsort(clusters, num_clusters, sizeof(*clusters), SortClusters);
int i;
- int last_line = 0;
- for (i = 0; i < num_clusters; ++i) {
- TTF_SubString *cluster = &clusters[i];
- cluster->cluster_index = i;
+ const TTF_SubString *src = clusters;
+ TTF_SubString *last = NULL;
+ int cluster_index = 0;
+ for (i = 0; i < num_clusters; ++i, ++src) {
+ // Merge zero width clusters
+ if (last && src->offset == last->offset) {
+ if (!(last->flags & TTF_SUBSTRING_LINE_END)) {
+ last->flags |= src->flags;
+ SDL_GetRectUnion(&last->rect, &src->rect, &last->rect);
+ continue;
+ }
+ }
+
+ TTF_SubString *cluster = &clusters[cluster_index];
+ if (src != cluster) {
+ SDL_copyp(cluster, src);
+ }
+
+ if (!last || cluster->line_index != last->line_index) {
+ if (!last) {
+ cluster->flags |= TTF_SUBSTRING_TEXT_START;
+ }
+ cluster->flags |= TTF_SUBSTRING_LINE_START;
+
+ if (lines && cluster->line_index > 0) {
+ lines[cluster->line_index - 1] = cluster_index;
+ }
+ }
+
+ cluster->cluster_index = cluster_index++;
+
+ if (cluster->flags & TTF_SUBSTRING_LINE_END) {
+ if (cluster->flags & TTF_SUBSTRING_LINE_START) {
+ cluster->rect.y = cluster->line_index * font->lineskip;
+ cluster->rect.h = font->height;
+ } else {
+ SDL_copyp(&cluster->rect, &clusters[cluster->cluster_index - 1].rect);
+ cluster->rect.x += cluster->rect.w;
+ cluster->rect.w = 0;
+ }
+ }
+
if (i < (num_clusters - 1)) {
cluster->length = clusters[i + 1].offset - cluster->offset;
} else {
+ cluster->flags |= TTF_SUBSTRING_TEXT_END;
cluster->length = (int)(length - cluster->offset);
}
-
- if (lines) {
- if (cluster->line_index != last_line) {
- lines[cluster->line_index - 1] = i;
- last_line = cluster->line_index;
- }
- }
+ last = cluster;
}
- return true;
+ return cluster_index;
}
static bool LayoutText(TTF_Text *text)
@@ -3751,7 +3788,7 @@ static bool LayoutText(TTF_Text *text)
size_t length = SDL_strlen(text->text);
TTF_DrawOperation *ops = NULL;
int num_ops = 0, max_ops;
- TTF_SubString *clusters = NULL;
+ TTF_SubString *clusters = NULL, *cluster;
int num_clusters = 0;
int xstart, ystart, width = 0, height = 0;
bool result = false;
@@ -3773,8 +3810,7 @@ static bool LayoutText(TTF_Text *text)
goto done;
}
- SDL_assert(font->num_clusters > 0);
- clusters = (TTF_SubString *)SDL_malloc(font->num_clusters * sizeof(*clusters));
+ clusters = (TTF_SubString *)SDL_calloc(font->num_clusters + 1, sizeof(*clusters));
if (!clusters) {
goto done;
}
@@ -3783,6 +3819,9 @@ static bool LayoutText(TTF_Text *text)
if (!Render_Line_TextEngine(font, xstart, ystart, width, height, ops, &num_ops, clusters, &num_clusters, 0, 0)) {
goto done;
}
+ cluster = &clusters[num_clusters++];
+ cluster->flags = TTF_SUBSTRING_LINE_END;
+ cluster->offset = (int)SDL_strlen(text->text);
// Apply underline or strikethrough style, if needed
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
@@ -3793,9 +3832,7 @@ static bool LayoutText(TTF_Text *text)
Draw_Line_TextEngine(font, width, height, 0, ystart + font->strikethrough_top_row, width, font->line_thickness, ops, &num_ops);
}
- if (!CalculateClusterLengths(clusters, num_clusters, length, NULL)) {
- goto done;
- }
+ num_clusters = CalculateClusterLengths(font, clusters, num_clusters, length, NULL);
result = true;
@@ -3824,8 +3861,8 @@ static bool LayoutTextWrapped(TTF_Text *text)
TTF_Line *strLines = NULL;
TTF_DrawOperation *ops = NULL, *new_ops;
int num_ops = 0, max_ops = 0, extra_ops = 0, additional_ops;
- TTF_SubString *clusters = NULL, *new_clusters;
- int num_clusters = 0, max_clusters = 0, cluster_offset;
+ TTF_SubString *clusters = NULL, *new_clusters, *cluster;
+ int num_clusters = 0, max_clusters = 0, additional_clusters, cluster_offset;
int *lines = NULL;
bool result = false;
@@ -3855,6 +3892,18 @@ static bool LayoutTextWrapped(TTF_Text *text)
int xstart, ystart, line_width, xoffset;
if (strLines[i].length == 0) {
+ new_clusters = (TTF_SubString *)SDL_realloc(clusters, (max_clusters + 1) * sizeof(*new_clusters));
+ if (!new_clusters) {
+ goto done;
+ }
+ clusters = new_clusters;
+ max_clusters += 1;
+
+ cluster = &clusters[num_clusters++];
+ SDL_zerop(cluster);
+ cluster->flags = TTF_SUBSTRING_LINE_END;
+ cluster->offset = (int)(uintptr_t)(strLines[i].text - text->text);
+ cluster->line_index = i;
continue;
}
@@ -3887,19 +3936,24 @@ static bool LayoutTextWrapped(TTF_Text *text)
max_ops += additional_ops;
// Allocate space for the clusters on this line
- SDL_assert(font->num_clusters > 0);
- new_clusters = (TTF_SubString *)SDL_realloc(clusters, (max_clusters + font->num_clusters) * sizeof(*new_clusters));
+ additional_clusters = (font->num_clusters + 1);
+ new_clusters = (TTF_SubString *)SDL_realloc(clusters, (max_clusters + additional_clusters) * sizeof(*new_clusters));
if (!new_clusters) {
goto done;
}
+ SDL_memset(new_clusters + max_clusters, 0, additional_clusters * sizeof(*new_clusters));
clusters = new_clusters;
- max_clusters += font->num_clusters;
- cluster_offset = (int)(strLines[i].text - text->text);
+ max_clusters += additional_clusters;
+ cluster_offset = (int)(uintptr_t)(strLines[i].text - text->text);
// Create the text drawing operations
if (!Render_Line_TextEngine(font, xstart + xoffset, ystart, width, height, ops, &num_ops, clusters, &num_clusters, cluster_offset, i)) {
goto done;
}
+ cluster = &clusters[num_clusters++];
+ cluster->flags = TTF_SUBSTRING_LINE_END;
+ cluster->offset = (int)(uintptr_t)(strLines[i].text - text->text + strLines[i].length);
+ cluster->line_index = i;
// Apply underline or strikethrough style, if needed
if (TTF_HANDLE_STYLE_UNDERLINE(font)) {
@@ -3911,9 +3965,7 @@ static bool LayoutTextWrapped(TTF_Text *text)
}
}
- if (!CalculateClusterLengths(clusters, num_clusters, length, lines)) {
- goto done;
- }
+ num_clusters = CalculateClusterLengths(font, clusters, num_clusters, length, lines);
result = true;
@@ -4210,31 +4262,6 @@ bool TTF_GetTextSize(TTF_Text *text, int *w, int *h)
return true;
}
-bool TTF_GetPreviousSubString(TTF_Text *text, TTF_SubString *substring, TTF_SubString *previous)
-{
- if (previous && previous != substring) {
- SDL_zerop(previous);
- }
-
- TTF_CHECK_POINTER("text", text, false);
- TTF_CHECK_POINTER("substring", substring, false);
- TTF_CHECK_POINTER("previous", previous, false);
-
- int num_clusters = text->internal->num_clusters;
- const TTF_SubString *clusters = text->internal->clusters;
- if (substring->cluster_index < 0 || substring->cluster_index >= num_clusters) {
- return SDL_SetError("Cluster index out of range");
- }
- if (substring->offset != clusters[substring->cluster_index].offset) {
- return SDL_SetError("Stale substring");
- }
- if (substring->cluster_index == 0) {
- return SDL_SetError("No previous substring");
- }
- SDL_copyp(previous, &clusters[substring->cluster_index - 1]);
- return true;
-}
-
bool TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring)
{
if (substring) {
@@ -4253,8 +4280,11 @@ bool TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring)
return true;
}
+ int num_clusters = text->internal->num_clusters;
+ TTF_SubString *clusters = text->internal->clusters;
+
if (offset < 0) {
- SDL_copyp(substring, &text->internal->clusters[0]);
+ SDL_copyp(substring, &clusters[0]);
substring->length = 0;
substring->rect.w = 0;
return true;
@@ -4262,22 +4292,21 @@ bool TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring)
int length = (int)SDL_strlen(text->text);
if (offset >= length) {
- SDL_copyp(substring, &text->internal->clusters[text->internal->num_clusters - 1]);
- substring->offset = length;
+ SDL_copyp(substring, &clusters[num_clusters - 1]);
substring->length = 0;
- if (TTF_GetFontDirection(text->internal->font) != TTF_DIRECTION_RTL) {
- substring->rect.x += substring->rect.w;
- }
+ substring->rect.x += substring->rect.w;
substring->rect.w = 0;
return true;
}
- // Make a quick guess that works for ASCII text with no line breaks
- int num_clusters = text->internal->num_clusters;
- const TTF_SubString *clusters = text->internal->clusters;
- const TTF_SubString *cluster = NULL;
- if (offset < num_clusters && clusters[offset].offset == offset) {
- SDL_copyp(substring, &clusters[offset]);
+ // Make a quick guess that works for ASCII text
+ const TTF_SubString *cluster = &clusters[offset];
+ if (offset < num_clusters && cluster->offset == offset) {
+ if ((cluster->flags & TTF_SUBSTRING_LINE_END) && cluster->length == 0 &&
+ offset < (num_clusters - 1)) {
+ ++cluster;
+ }
+ SDL_copyp(substring, cluster);
return true;
}
@@ -4288,30 +4317,13 @@ bool TTF_GetTextSubString(TTF_Text *text, int offset, TTF_SubString *substring)
int mid = low + (high - low) / 2;
cluster = &clusters[mid];
- if (offset >= cluster->offset && (cluster->length == 0 || offset < (cluster->offset + cluster->length))) {
- // In the right ballpark, expand the substring to include related clusters
- --mid;
- while (mid >= 0) {
- cluster = &clusters[mid];
- if (offset < cluster->offset || (cluster->length != 0 && offset >= (cluster->offset + cluster->length))) {
- break;
- }
- --mid;
- }
- ++mid;
-
+ // If we're a zero length line ending, check the next cluster
+ if ((cluster->flags & TTF_SUBSTRING_LINE_END) && cluster->length == 0 &&
+ mid < (num_clusters - 1)) {
+ ++cluster;
+ }
+ if (offset >= cluster->offset && offset < (cluster->offset + cluster->length)) {
SDL_copyp(substring, &clusters[mid]);
- ++mid;
- while (mid < num_clusters) {
- cluster = &clusters[mid];
- if (offset < cluster->offset || (cluster->length != 0 && offset >= (cluster->offset + cluster->length))) {
- break;
- }
-
- SDL_GetRectUnion(&substring->rect, &cluster->rect, &substring->rect);
- substring->length = (cluster->offset - substring->offset) + cluster->length;
- ++mid;
- }
break;
}
@@ -4344,6 +4356,7 @@ bool TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substr
int num_clusters = text->internal->num_clusters;
TTF_SubString *clusters = text->internal->clusters;
+
if (line < 0) {
SDL_copyp(substring, &clusters[0]);
substring->length = 0;
@@ -4353,11 +4366,8 @@ bool TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substr
if (line >= text->num_lines) {
SDL_copyp(substring, &clusters[num_clusters - 1]);
- substring->offset = (int)SDL_strlen(text->text);
substring->length = 0;
- if (TTF_GetFontDirection(text->internal->font) != TTF_DIRECTION_RTL) {
- substring->rect.x += substring->rect.w;
- }
+ substring->rect.x += substring->rect.w;
substring->rect.w = 0;
return true;
}
@@ -4378,6 +4388,7 @@ bool TTF_GetTextSubStringForLine(TTF_Text *text, int line, TTF_SubString *substr
if (cluster->line_index != line) {
break;
}
+ substring->flags |= cluster->flags;
SDL_GetRectUnion(&substring->rect, &cluster->rect, &substring->rect);
}
return true;
@@ -4422,7 +4433,7 @@ TTF_SubString **TTF_GetTextSubStringsForRange(TTF_Text *text, int offset, int le
int offset2 = offset + length;
if (!TTF_GetTextSubString(text, offset1, &substring1) ||
!TTF_GetTextSubString(text, offset2, &substring2) ||
- !TTF_GetPreviousSubString(text, &substring2, &substring2)) {
+ !TTF_GetPreviousTextSubString(text, &substring2, &substring2)) {
return NULL;
}
@@ -4473,19 +4484,20 @@ TTF_SubString **TTF_GetTextSubStringsForRange(TTF_Text *text, int offset, int le
}
result[num_results] = NULL;
- TTF_SubString *current = substrings;
- SDL_copyp(current, &substring1);
+ TTF_SubString *substring = substrings;
+ SDL_copyp(substring, &substring1);
for (int i = substring1.cluster_index + 1; i <= substring2.cluster_index; ++i) {
TTF_SubString *cluster = &clusters[i];
- if (cluster->line_index == current->line_index) {
- SDL_GetRectUnion(¤t->rect, &cluster->rect, ¤t->rect);
+ if (cluster->line_index == substring->line_index) {
+ substring->flags |= cluster->flags;
+ SDL_GetRectUnion(&substring->rect, &cluster->rect, &substring->rect);
} else {
- current->length = (cluster->offset - current->offset);
- ++current;
- SDL_copyp(current, cluster);
+ substring->length = (cluster->offset - substring->offset);
+ ++substring;
+ SDL_copyp(substring, cluster);
}
}
- current->length = (substring2.offset - current->offset) + substring2.length;
+ substring->length = (substring2.offset - substring->offset) + substring2.length;
if (count) {
*count = num_results;
@@ -4507,24 +4519,52 @@ bool TTF_GetTextSubStringForPoint(TTF_Text *text, int x, int y, TTF_SubString *s
}
if (text->internal->num_clusters == 0) {
+ substring->rect.h = text->internal->font->height;
return true;
}
#if TTF_USE_HARFBUZZ
hb_direction_t hb_direction = text->internal->font->hb_direction;
- bool prefer_row = !(hb_direction == HB_DIRECTION_TTB || hb_direction == HB_DIRECTION_BTT);
+ bool prefer_row = (hb_direction == HB_DIRECTION_LTR || hb_direction == HB_DIRECTION_RTL);
+ bool line_ends_right = (hb_direction == HB_DIRECTION_LTR);
#else
bool prefer_row = true;
+ bool line_ends_right = true;
#endif
const TTF_SubString *closest = NULL;
int closest_dist = INT_MAX;
int wrap_cost = (text->internal->layout->wrap ? 100 : 1);
+ SDL_Point point = { x, y };
for (int i = 0; i < text->internal->num_clusters; ++i) {
const TTF_SubString *cluster = &text->internal->clusters[i];
int center_x = (cluster->rect.x + cluster->rect.w / 2);
int center_y = (cluster->rect.y + cluster->rect.h / 2);
int dist;
+ if (cluster->flags & TTF_SUBSTRING_LINE_END) {
+ if (prefer_row && (cluster->flags & TTF_SUBSTRING_LINE_END)) {
+ if ((y >= cluster->rect.y && y < (cluster->rect.y + cluster->rect.h)) &&
+ ((line_ends_right && x >= cluster->rect.x) ||
+ (!line_ends_right && x <= cluster->rect.x))) {
+ closest = cluster;
+ break;
+ }
+ }
+ } else {
+ if (prefer_row && (cluster->flags & TTF_SUBSTRING_LINE_START)) {
+ if ((y >= cluster->rect.y && y < (cluster->rect.y + cluster->rect.h)) &&
+ ((line_ends_right && x < cluster->rect.x) ||
+ (!line_ends_right && x > cluster->rect.x))) {
+ closest = cluster;
+ break;
+ }
+ }
+
+ if (SDL_PointInRect(&point, &cluster->rect)) {
+ closest = cluster;
+ break;
+ }
+ }
if (prefer_row) {
dist = SDL_abs(center_y - y) * wrap_cost + SDL_abs(center_x - x);
} else {
@@ -4542,6 +4582,63 @@ bool TTF_GetTextSubStringForPoint(TTF_Text *text, int x, int y, TTF_SubString *s
return true;
}
+bool TTF_GetPreviousTextSubString(TTF_Text *text, const TTF_SubString *substring, TTF_SubString *previous)
+{
+ if (previous && previous != substring) {
+ SDL_zerop(previous);
+ }
+
+ TTF_CHECK_POINTER("text", text, false);
+ TTF_CHECK_POINTER("substring", substring, false);
+ TTF_CHECK_POINTER("previous", previous, false);
+
+ int num_clusters = text->internal->num_clusters;
+ const TTF_SubString *clusters = text->internal->clusters;
+ if (substring->cluster_index < 0 || substring->cluster_index >= num_clusters) {
+ return SDL_SetError("Cluster index out of range");
+ }
+ if (substring->offset != clusters[substring->cluster_index].offset) {
+ return SDL_SetError("Stale substring");
+ }
+ if (substring->cluster_index == 0) {
+ SDL_copyp(previous, &clusters[0]);
+ previous->length = 0;
+ previous->rect.w = 0;
+ } else {
+ SDL_copyp(previous, &clusters[substring->cluster_index - 1]);
+ }
+ return true;
+}
+
+bool TTF_GetNextTextSubString(TTF_Text *text, const TTF_SubString *substring, TTF_SubString *next)
+{
+ if (next && next != substring) {
+ SDL_zerop(next);
+ }
+
+ TTF_CHECK_POINTER("text", text, false);
+ TTF_CHECK_POINTE
(Patch may be truncated, please check the link at the top of this post.)