diff --git a/meme_otron/img_factory.py b/meme_otron/img_factory.py index eed6264..c1156bc 100644 --- a/meme_otron/img_factory.py +++ b/meme_otron/img_factory.py @@ -98,21 +98,18 @@ def fit_text(size, text): font_size = round(text.font_size * min(size)) + 1 font = FONTS[text.font] t = "" - while (text_size is None or text_size[1] >= max_height) and font_size > 1: + while (text_size is None or text_size[0] >= max_width or text_size[1] >= max_height) and font_size > 1: font_size -= 1 font = font.font_variant(size=font_size) - words = text.text.split(" ") - t = "" - for word in words: - spacer = " " - if len(t) == 0: - spacer = "" - text_size = font.getsize_multiline(t + spacer + word, stroke_width=text.stroke_width * font_size) - if text_size[0] >= max_width: - t += "\n" + word - else: - t += spacer + word - text_size = font.getsize_multiline(t, stroke_width=text.stroke_width * font_size) + k = 0 # number of lines + while k == 0 or (t is not None and text_size[0] >= max_width): + k += 1 + t = utils.break_text(text.text, k) + if t is not None: + text_size = font.getsize_multiline(t, stroke_width=text.stroke_width * font_size) + if t is None: + # max break attained + text_size = None # restart return t, font diff --git a/meme_otron/utils.py b/meme_otron/utils.py index bdddab4..45d174e 100644 --- a/meme_otron/utils.py +++ b/meme_otron/utils.py @@ -1,4 +1,5 @@ import re +import sys import os.path as path from Levenshtein import distance @@ -147,3 +148,84 @@ def find_nearest(word, wlist, threshold=5): if found[0] > threshold: return None return found[1] + + +def safe_index(src, pattern, start=0): + """ + :param (list|str) src: + :param pattern: + :param (int) start: + """ + try: + return src.index(pattern, start) + except ValueError: + return None + + +def find_all(src, pattern): + """ + :param (str) src: + :param (str) pattern: + :rtype: list of int + """ + o = [] + i = safe_index(src, pattern) + while i is not None: + o += [i] + i = safe_index(src, pattern, i + 1) + return o + + +def replace_at(src, pattern, indexes, remove): + """ + :param (str) src: + :param (str) pattern: + :param (list of int) indexes: + :param (int) remove: + :rtype: str + """ + o = "" + last = 0 + for i in indexes: + o += src[last:i] + pattern + last = i + remove + o += src[last:] + return o + + +def break_text(src, n): + """ + :param (str) src: + :param (int) n: + :rtype: str + """ + spaces = find_all(src, " ") + if n - 1 > len(spaces): + return None + if n - 1 == len(spaces): + return replace_at(src, "\n", spaces, 1) + ideal = [k * (len(src) - 1) / n for k in range(1, n)] + indexes = best_fit(ideal, spaces) + return replace_at(src, "\n", indexes, 1) + + +def best_fit(a, b): + """ + :param (list of float) a: + :param (list of int) b: + :rtype: list of int + """ + a = a[::] + o = [] + dist = sys.maxsize + for i, value in enumerate(b): + if not len(a): + break + if dist < abs(value - a[0]): + o += [b[i - 1]] + a.pop(0) + else: + dist = abs(value - a[0]) + if len(a): + o += [b[-1]] + return o diff --git a/tests/test_utils.py b/tests/test_utils.py index 6b7cd23..6dcefed 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -80,11 +80,11 @@ class Test(TestCase): self.assertEqual(5, utils.read_key_safe(d, "test1")) self.assertEqual([1, 3, ""], utils.read_key_safe(d, "test2")) self.assertEqual("default", utils.read_key_safe(d, "test3", "default")) - self.assertEqual(None, utils.read_key_safe(d, "test3")) + self.assertIsNone(utils.read_key_safe(d, "test3")) def test_find_nearest(self): self.assertEqual("test", utils.find_nearest("tost", ["test", "example", "what"])) - self.assertEqual(None, utils.find_nearest("unknown", ["test", "example", "what"], threshold=2)) + self.assertIsNone(utils.find_nearest("unknown", ["test", "example", "what"], threshold=2)) self.assertEqual("test", utils.find_nearest("unknown", ["test", "example", "what"], threshold=200)) def test_parse_arguments(self): @@ -93,3 +93,38 @@ class Test(TestCase): self.assertEqual(["test1", "test2"], utils.parse_arguments("test1 test2")) self.assertEqual(["test1", "test 2", "test 3"], utils.parse_arguments("test1 'test 2' \"test 3\"")) self.assertEqual(["test1", "", ""], utils.parse_arguments("test1 '' \"\"")) + + def test_safe_index(self): + self.assertEqual(0, utils.safe_index("a", "a")) + self.assertEqual(0, utils.safe_index([0], 0)) + self.assertEqual(2, utils.safe_index("cbaa", "a")) + self.assertEqual(3, utils.safe_index("cbaa", "a", 3)) + self.assertEqual(1, utils.safe_index(["a", 0, 0], 0)) + self.assertEqual(2, utils.safe_index(["a", 0, 0], 0, 2)) + self.assertIsNone(utils.safe_index("a", "b")) + self.assertIsNone(utils.safe_index("a", "a", 2)) + self.assertIsNone(utils.safe_index(["a", 0, 0], 0, 3)) + + def test_find_all(self): + self.assertEqual([], utils.find_all("abc", "n")) + self.assertEqual([0], utils.find_all("abc", "a")) + self.assertEqual([0, 2], utils.find_all("aba", "a")) + + def test_replace_at(self): + self.assertEqual("abcd", utils.replace_at("abc", "d", [3], 0)) + self.assertEqual("abd", utils.replace_at("abc", "d", [2], 1)) + self.assertEqual("ddd", utils.replace_at("abc", "d", [0, 1, 2], 1)) + self.assertEqual("a nice_plac_", utils.replace_at("a nice place", "_", [6, 11], 1)) + + def test_break_text(self): + self.assertIsNone(utils.break_text("abcd", 2)) + self.assertIsNone(utils.break_text("abcd efgh", 3)) + self.assertEqual("abcd", utils.break_text("abcd", 1)) + self.assertEqual("abcd\nefgh", utils.break_text("abcd efgh", 2)) + self.assertEqual("ab cd\nef gh", utils.break_text("ab cd ef gh", 2)) + self.assertEqual("ab\ncd ef\ngh", utils.break_text("ab cd ef gh", 3)) + + def test_best_fit(self): + self.assertEqual([5, 9, 15], utils.best_fit([5.2, 14.3, 15.2], [3, 5, 9, 15, 18])) + self.assertEqual([5, 9, 15, 18], utils.best_fit([5.2, 14.3, 14.5, 15.2], [3, 5, 9, 15, 18])) + self.assertEqual([5, 9, 15, 18], utils.best_fit([5.2, 14.3, 14.5, 15.2], [3, 5, 9, 15, 18, 20]))