From: "Christoph M. Becker" <cmbecker69@gmx.de>
Date: Sat, 2 May 2020 23:44:52 +0200
Subject: Fix #615: gdImageStringFT() fails for empty strings as of libgd
 2.3.0

We change the return type of `textLayout()` to `ssize_t`, and signal
failure by returning `-1`, so that laying out an empty string is no
longer handled as failure.  We make sure that no overflow occurs,
assuming that all `int` values can be fully represented as `ssize_t`.
---
 src/gdft.c                           | 18 +++++++++---------
 tests/gdimagestringft/.gitignore     |  2 ++
 tests/gdimagestringft/CMakeLists.txt |  1 +
 tests/gdimagestringft/Makemodule.am  |  1 +
 tests/gdimagestringft/bug00615.c     | 25 +++++++++++++++++++++++++
 5 files changed, 38 insertions(+), 9 deletions(-)
 create mode 100644 tests/gdimagestringft/.gitignore
 create mode 100644 tests/gdimagestringft/bug00615.c

diff --git a/src/gdft.c b/src/gdft.c
index b483b38..186eeff 100644
--- a/src/gdft.c
+++ b/src/gdft.c
@@ -441,7 +441,7 @@ typedef struct {
 	uint32_t cluster;
 } glyphInfo;
 
-static size_t
+static ssize_t
 textLayout(uint32_t *text, int len,
 		FT_Face face, gdFTStringExtraPtr strex,
 		glyphInfo **glyph_info)
@@ -459,19 +459,19 @@ textLayout(uint32_t *text, int len,
 		!raqm_set_par_direction (rq, RAQM_DIRECTION_DEFAULT) ||
 		!raqm_layout (rq)) {
 		raqm_destroy (rq);
-		return 0;
+		return -1;
 	}
 
 	glyphs = raqm_get_glyphs (rq, &count);
 	if (!glyphs) {
 		raqm_destroy (rq);
-		return 0;
+		return -1;
 	}
 
 	info = (glyphInfo*) gdMalloc (sizeof (glyphInfo) * count);
 	if (!info) {
 		raqm_destroy (rq);
-		return 0;
+		return -1;
 	}
 
 	for (i = 0; i < count; i++) {
@@ -489,7 +489,7 @@ textLayout(uint32_t *text, int len,
 	FT_Error err;
 	info = (glyphInfo*) gdMalloc (sizeof (glyphInfo) * len);
 	if (!info) {
-		return 0;
+		return -1;
 	}
 	for (count = 0; count < len; count++) {
 		/* Convert character code to glyph index */
@@ -508,7 +508,7 @@ textLayout(uint32_t *text, int len,
 		err = FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT);
 		if (err) {
 			gdFree (info);
-			return 0;
+			return -1;
 		}
 		info[count].index = glyph_index;
 		info[count].x_offset = 0;
@@ -527,7 +527,7 @@ textLayout(uint32_t *text, int len,
 #endif
 
 	*glyph_info = info;
-	return count;
+	return count <= SSIZE_MAX ? count : -1;
 }
 
 /********************************************************************/
@@ -1108,7 +1108,7 @@ BGD_DECLARE(char *) gdImageStringFTEx (gdImage * im, int *brect, int fg, const c
 	char *tmpstr = 0;
 	uint32_t *text;
 	glyphInfo *info = NULL;
-	size_t count;
+	ssize_t count;
 	int render = (im && (im->trueColor || (fg <= 255 && fg >= -255)));
 	FT_BitmapGlyph bm;
 	/* 2.0.13: Bob Ostermann: don't force autohint, that's just for testing
@@ -1409,7 +1409,7 @@ BGD_DECLARE(char *) gdImageStringFTEx (gdImage * im, int *brect, int fg, const c
 
 	count = textLayout (text , i, face, strex, &info);
 
-	if (!count) {
+	if (count < 0) {
 		gdFree (text);
 		gdFree (tmpstr);
 		gdCacheDelete (tc_cache);
diff --git a/tests/gdimagestringft/.gitignore b/tests/gdimagestringft/.gitignore
new file mode 100644
index 0000000..efe5e61
--- /dev/null
+++ b/tests/gdimagestringft/.gitignore
@@ -0,0 +1,2 @@
+/bug00615
+/gdimagestringft_bbox
diff --git a/tests/gdimagestringft/CMakeLists.txt b/tests/gdimagestringft/CMakeLists.txt
index f46b900..42868a2 100644
--- a/tests/gdimagestringft/CMakeLists.txt
+++ b/tests/gdimagestringft/CMakeLists.txt
@@ -1,5 +1,6 @@
 IF(FREETYPE_FOUND)
 LIST(APPEND TESTS_FILES
+	bug00615
 	gdimagestringft_bbox
 )
 ENDIF(FREETYPE_FOUND)
diff --git a/tests/gdimagestringft/Makemodule.am b/tests/gdimagestringft/Makemodule.am
index 0dfe26f..a62081f 100644
--- a/tests/gdimagestringft/Makemodule.am
+++ b/tests/gdimagestringft/Makemodule.am
@@ -1,5 +1,6 @@
 if HAVE_LIBFREETYPE
 libgd_test_programs += \
+	gdimagestringft/bug00615 \
 	gdimagestringft/gdimagestringft_bbox
 endif
 
diff --git a/tests/gdimagestringft/bug00615.c b/tests/gdimagestringft/bug00615.c
new file mode 100644
index 0000000..93b93ad
--- /dev/null
+++ b/tests/gdimagestringft/bug00615.c
@@ -0,0 +1,25 @@
+/**
+ * Test that rendering an empty string does not fail
+ *
+ * Rendering an empty string with gdImageStringFT() is not supposed to fail;
+ * it is just a no-op.
+ * 
+ * See <https://github.com/libgd/libgd/issues/615>
+ */
+
+#include "gd.h"
+#include "gdtest.h"
+
+int main()
+{
+    gdImagePtr im = gdImageCreate(100, 100);
+
+    int rect[8];
+    int fg = gdImageColorAllocate(im, 255, 255, 255);
+    char *path = gdTestFilePath("freetype/DejaVuSans.ttf");
+    char *res = gdImageStringFT(im, rect, fg, path, 12, 0, 10, 10, "");
+
+    gdTestAssert(res == NULL);
+
+    return gdNumFailures();
+}
