From 057a92f92c408e3ce7e44a847fed79b918aaf623 Mon Sep 17 00:00:00 2001 From: xy-23 Date: Mon, 5 Aug 2019 22:53:15 +0800 Subject: [PATCH 1/5] new Text mobject --- manimlib/constants.py | 20 +- manimlib/imports.py | 1 + manimlib/mobject/svg/svg_mobject.py | 33 ++- manimlib/mobject/svg/text_mobject.py | 324 +++++++++++++++++++++++++++ 4 files changed, 370 insertions(+), 8 deletions(-) create mode 100644 manimlib/mobject/svg/text_mobject.py diff --git a/manimlib/constants.py b/manimlib/constants.py index 7cd99e26..d4f3ac06 100644 --- a/manimlib/constants.py +++ b/manimlib/constants.py @@ -5,6 +5,7 @@ MEDIA_DIR = "" VIDEO_DIR = "" VIDEO_OUTPUT_DIR = "" TEX_DIR = "" +TEXT_DIR = "" def initialize_directories(config): @@ -12,6 +13,7 @@ def initialize_directories(config): global VIDEO_DIR global VIDEO_OUTPUT_DIR global TEX_DIR + global TEXT_DIR video_path_specified = config["video_dir"] or config["video_output_dir"] @@ -36,7 +38,8 @@ def initialize_directories(config): "directory were both passed" ) - TEX_DIR = config["tex_dir"] or os.path.join(MEDIA_DIR, "Tex") + TEX_DIR = config["tex_dir"] or os.path.join(MEDIA_DIR, "texs") + TEXT_DIR = os.path.join(MEDIA_DIR, "texts") if not video_path_specified: VIDEO_DIR = os.path.join(MEDIA_DIR, "videos") VIDEO_OUTPUT_DIR = os.path.join(MEDIA_DIR, "videos") @@ -45,10 +48,23 @@ def initialize_directories(config): else: VIDEO_DIR = config["video_dir"] - for folder in [VIDEO_DIR, VIDEO_OUTPUT_DIR, TEX_DIR]: + for folder in [VIDEO_DIR, VIDEO_OUTPUT_DIR, TEX_DIR, TEXT_DIR]: if folder != "" and not os.path.exists(folder): os.makedirs(folder) +DEFAULT_FONT = '' +DEFAULT_LSH = 1 +DEFAULT_SIZE = 1 +NOT_SETTING_FONT_MSG=''' +Warning: +You haven't set DEFAULT_FONT. +If you are not using English, this may cause text rendering problem. +You can change the DEFAULT_FONT in manimlib\\constans.py or Text('your text', font='your font'). +''' +NORMAL = 'NORMAL' +ITALIC = 'ITALIC' +OBLIQUE = 'OBLIQUE' +BOLD = 'BOLD' TEX_USE_CTEX = False TEX_TEXT_TO_REPLACE = "YourTextHere" diff --git a/manimlib/imports.py b/manimlib/imports.py index eb089282..b6fd097b 100644 --- a/manimlib/imports.py +++ b/manimlib/imports.py @@ -49,6 +49,7 @@ from manimlib.mobject.svg.brace import * from manimlib.mobject.svg.drawings import * from manimlib.mobject.svg.svg_mobject import * from manimlib.mobject.svg.tex_mobject import * +from manimlib.mobject.svg.text_mobject import * from manimlib.mobject.three_d_utils import * from manimlib.mobject.three_dimensions import * from manimlib.mobject.types.image_mobject import * diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index a5dc6bc3..219134f9 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -81,7 +81,7 @@ class SVGMobject(VMobject): self.update_ref_to_element(element) elif element.tagName == 'style': pass # TODO, handle style - elif element.tagName in ['g', 'svg']: + elif element.tagName in ['g', 'svg', 'symbol']: result += it.chain(*[ self.get_mobjects_from(child) for child in element.childNodes @@ -284,12 +284,33 @@ class SVGMobject(VMobject): pass # TODO, ... + def flatten(self, input_list): + output_list = [] + while True: + if input_list == []: + break + for index, i in enumerate(input_list): + if type(i)== list: + input_list = i + input_list[index+1:] + break + else: + output_list.append(i) + input_list.pop(index) + break + return output_list + + def get_all_childNodes_have_id(self, element): + all_childNodes_have_id = [] + if not isinstance(element, minidom.Element): + return + if element.hasAttribute('id'): + return element + for e in element.childNodes: + all_childNodes_have_id.append(self.get_all_childNodes_have_id(e)) + return self.flatten([e for e in all_childNodes_have_id if e]) + def update_ref_to_element(self, defs): - new_refs = dict([ - (element.getAttribute('id'), element) - for element in defs.childNodes - if isinstance(element, minidom.Element) and element.hasAttribute('id') - ]) + new_refs = dict([(e.getAttribute('id'), e) for e in self.get_all_childNodes_have_id(defs)]) self.ref_to_element.update(new_refs) def move_into_position(self): diff --git a/manimlib/mobject/svg/text_mobject.py b/manimlib/mobject/svg/text_mobject.py new file mode 100644 index 00000000..31c443c0 --- /dev/null +++ b/manimlib/mobject/svg/text_mobject.py @@ -0,0 +1,324 @@ +import cairo +import copy +import hashlib +import re +import os +from manimlib.constants import * +from manimlib.mobject.svg.svg_mobject import SVGMobject +import manimlib.constants as consts + + +class TextStyle(object): + def __init__(self, start, end, font, slant, weight, line_num=-1): + self.start = start + self.end = end + self.font = font + self.slant = slant + self.weight = weight + self.line_num = line_num + + +class Text(SVGMobject): + ''' + Params: + ------- + text :: + a str, the space(' ') in front or back and '\\n' and '\\t' will be ignored + + Params(optional): + ----------------- + color :: + color defined in constants.py or a str like '#FFFFFF', default is WHITE + + font :: + a str, the name of font like 'Source Han Sans', default is DEFAULT_FONT, defined in constants.py + + lsh (line_spacing_height) :: + a number, better larger than 0.1(due to anti-aliasing), irrelevant with MUnit, default is DEFAULT_LSH + + size :: + a number, better larger than 0.1(due to anti-aliasing), irrelevant with MUnit, default is DEFAULT_SIZE + + slant :: + NORMAL or ITALIC, default is NORMAL, defined in constants.py(a str actually) + + weight :: + NORMAL or BOLD, default is NORMAL, defined in constants.py(a str actually) + + fill_color :: + the same as color + + fill_opacity :: + a float, default is 1 + + stroke_color :: + the same as color + + storke_opacity :: + a float + + t2c (text2color) :: + a dict like {'text':color} or Accurate mode + + t2f (text2font) :: + a dict like {'text':font} or Accurate mode + + t2s (text2slant) :: + a dict like {'text':slant} or Accurate mode + + t2w (text2weight) :: + a dict like {'text':weight} or Accurate mode + + Functions : + ----------- + set_color(mobject function) :: + param color, this will set the color of the whole text + + set_text_color :: + param t2c, the same as the t2c mentioned above(require a dict!) + + Accurate mode: + -------------- + This will help you to choose a specific text just like slicing, e.g. :: + text = Text('ooo', t2c={'[:1]':RED, '[1:2]':GREEN, '[2:]':BLUE}) + + btw, you can use '[[:]]' to represent the text '[:]' + ''' + CONFIG = { + "color": WHITE, + "fill_opacity": 1, + "height": None, + } + + def __init__(self, text, **kwargs): + self.text = text + + kwargs = self.full2short(**kwargs) + file_name = self.text2svg(text, **kwargs) + SVGMobject.__init__(self, file_name=file_name, **kwargs) + if kwargs.__contains__('t2c'): + self.text2color(text, **kwargs) + #anti-aliasing + self.scale(0.1) + + def full2short(self, **kwargs): + if kwargs.__contains__('line_spacing_height'): + kwargs['lsh'] = kwargs.pop('line_spacing_height') + if kwargs.__contains__('text2color'): + kwargs['t2c'] = kwargs.pop('text2color') + if kwargs.__contains__('text2font'): + kwargs['t2f'] = kwargs.pop('text2font') + if kwargs.__contains__('text2slant'): + kwargs['t2s'] = kwargs.pop('text2slant') + if kwargs.__contains__('text2weight'): + kwargs['t2w'] = kwargs.pop('text2weight') + return kwargs + + def find_indexes(self, text, word): + indexes = [] + if re.match(r'\[\[[0-9\-]{0,}:[0-9\-]{0,}\]\]', word): + word = word[1:-1] + index = text.find(word) + while index != -1: + indexes.append((index, index+len(word))) + index = text.find(word, index+len(word)) + return indexes + + def find_strat_and_end(self, text, word): + m = re.match(r'\[([0-9\-]{0,}):([0-9\-]{0,})\]', word) + start = int(m.group(1)) if m.group(1) != '' else 0 + end = int(m.group(2)) if m.group(2) != '' else len(text) + return (start, end) + + def is_slicing(self, word): + m = re.match(r'\[[0-9\-]{0,}:[0-9\-]{0,}\]', word) + return True if m else False + + def get_t2c_indexes(self, t2c): + text = self.text + length = len(text) + t2c_indexes = [] + for word, color in list(t2c.items()): + # accurate mode + if self.is_slicing(word): + start, end = self.find_strat_and_end(text, word) + start = length + start if start < 0 else start + end = length + end if end < 0 else end + t2c_indexes.append((start, end, color)) + continue + for start, end in self.find_indexes(text, word): + t2c_indexes.append((start, end, color)) + return sorted(t2c_indexes, key=lambda i: i[1]) + + def getfsw(self, **kwargs): + font = kwargs['font'] if kwargs.__contains__('font') else DEFAULT_FONT + slant = kwargs['slant'] if kwargs.__contains__('slant') else NORMAL + weight = kwargs['weight'] if kwargs.__contains__('weight') else NORMAL + return (font, slant, weight) + + def getxywh(self, text, font, slant, weight, size): + dir_name = consts.TEXT_DIR + file_name = os.path.join(dir_name, 'temp')+'.svg' + + temp_surface = cairo.SVGSurface(file_name, 1, 1) + temp_context = cairo.Context(temp_surface) + temp_context.set_font_size(size) + if font != '': + fs = self.str2slant(slant) + fw = self.str2weight(weight) + temp_context.select_font_face(font, fs, fw) + x, y, w, h, dx, dy = temp_context.text_extents(text) + return (x, y, w, h) + + def get_space_w(self, font, size): + x1, y1, w1, h1 = self.getxywh('a', font, NORMAL, NORMAL, size) + x2, y2, w2, h2, = self.getxywh('aa', font, NORMAL, NORMAL, size) + return w2 - w1*2 + + def has_multi_line(self, text): + return True if re.search(r'\n', text) else False + + def set_text_color(self, t2c): + self.text2color(self.text, t2c=t2c) + + def str2slant(self, string): + if string == NORMAL: + return cairo.FontSlant.NORMAL + if string == ITALIC: + return cairo.FontSlant.ITALIC + if string == OBLIQUE: + return cairo.FontSlant.OBLIQUE + + def str2weight(self, string): + if string == NORMAL: + return cairo.FontWeight.NORMAL + if string == BOLD: + return cairo.FontWeight.BOLD + + def text2color(self, text, **kwargs): + for word, color in list(kwargs['t2c'].items()): + # accurate mode + if self.is_slicing(word): + start, end = self.find_strat_and_end(text, word) + self[start:end].set_color(color) + continue + for start, end in self.find_indexes(text, word): + self[start:end].set_color(color) + + def text2hash(self, text, **kwargs): + ignores = [ + 'color', 't2c', + 'fill_color', 'fill_opacity', + 'stroke_color', 'storke_opacity' + ] + for ignore in ignores: + if kwargs.__contains__(ignore): + kwargs.pop(ignore) + + id_str = text+str(kwargs) + hasher = hashlib.sha256() + hasher.update(id_str.encode()) + return hasher.hexdigest()[:16] + + def text2styles(self, text, **kwargs): + styles = [] + f0, s0, w0 = self.getfsw(**kwargs) + + if kwargs.__contains__('t2f'): + for word, font in list(kwargs['t2f'].items()): + if self.is_slicing(word): + start, end = self.find_strat_and_end(text, word) + styles.append(TextStyle(start, end, font, s0, w0)) + for start, end in self.find_indexes(text, word): + styles.append(TextStyle(start, end, font, s0, w0)) + + if kwargs.__contains__('t2s'): + for word, slant in list(kwargs['t2s'].items()): + if self.is_slicing(word): + start, end = self.find_strat_and_end(text, word) + styles.append(TextStyle(start, end, f0, slant, w0)) + for start, end in self.find_indexes(text, word): + styles.append(TextStyle(start, end, f0, slant, w0)) + + if kwargs.__contains__('t2w'): + for word, weight in list(kwargs['t2w'].items()): + if self.is_slicing(word): + start, end = self.find_strat_and_end(text, word) + styles.append(TextStyle(start, end, f0, s0, weight)) + for start, end in self.find_indexes(text, word): + styles.append(TextStyle(start, end, f0, s0, weight)) + + #Set All text styles(default font slant weight) + styles = sorted(styles, key=lambda s: s.start) + temp_styles = styles.copy() + start = 0 + for style in styles: + if style.start != start: + temp_styles.append(TextStyle(start, style.start, f0, s0, w0)) + start = style.end + if start != len(text): + temp_styles.append(TextStyle(start, len(text), f0, s0, w0)) + styles = sorted(temp_styles, key=lambda s: s.start) + + if self.has_multi_line(text): + line_num = 0 + for start, end in self.find_indexes(text, '\n'): + for style in styles: + if style.line_num == -1: + style.line_num = line_num + if start < style.end: + line_num += 1 + new_style = copy.copy(style) + style.end = end + new_style.start = end + new_style.line_num = line_num + styles.append(new_style) + styles = sorted(styles, key=lambda s: s.start) + break + + return styles + + def text2svg(self, text, **kwargs): + font, slant, weight = self.getfsw(**kwargs) + size = kwargs['size'] if kwargs.__contains__('size') else DEFAULT_SIZE + lsh = kwargs['lsh'] if kwargs.__contains__('lsh') else DEFAULT_LSH + #anti-aliasing + size *= 10 + lsh *= 10 + + if font == '': + print(NOT_SETTING_FONT_MSG) + + dir_name = consts.TEXT_DIR + hash_name = self.text2hash(text, **kwargs) + file_name = os.path.join(dir_name, hash_name)+'.svg' + if os.path.exists(file_name): + return file_name + + text_surface = cairo.SVGSurface(file_name, 300, 200) + text_context = cairo.Context(text_surface) + text_context.set_font_size(size) + + styles = self.text2styles(text, **kwargs) + last_width = 0 + last_ln = 0 + for style in styles: + temp_text = text[style.start:style.end] + sf = style.font + ss = style.slant + sw = style.weight + sln = style.line_num if style.line_num != -1 else 0 + x1, y1, w1, h1 = self.getxywh(temp_text, sf, ss, sw, size) + csf = self.str2slant(ss) + csw = self.str2weight(sw) + + text_context.select_font_face(sf, csf, csw) + if sln != last_ln: + last_width = 0 + last_ln = sln + text_context.move_to(last_width-x1, lsh*sln) + text_context.show_text(temp_text) + + last_width += w1 + self.get_space_w(sf, size) + + return file_name From fd3721e0502fdec72e57a983717b4062a96378c4 Mon Sep 17 00:00:00 2001 From: xy-23 Date: Tue, 6 Aug 2019 21:05:37 +0800 Subject: [PATCH 2/5] new Text mobject --- manimlib/mobject/svg/text_mobject.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/manimlib/mobject/svg/text_mobject.py b/manimlib/mobject/svg/text_mobject.py index 31c443c0..e46626ba 100644 --- a/manimlib/mobject/svg/text_mobject.py +++ b/manimlib/mobject/svg/text_mobject.py @@ -23,7 +23,7 @@ class Text(SVGMobject): Params: ------- text :: - a str, the space(' ') in front or back and '\\n' and '\\t' will be ignored + a str, the space(' ') in front or back and '\\t' will be ignored(when there is only one line) Params(optional): ----------------- @@ -53,10 +53,7 @@ class Text(SVGMobject): stroke_color :: the same as color - - storke_opacity :: - a float - + t2c (text2color) :: a dict like {'text':color} or Accurate mode @@ -209,7 +206,7 @@ class Text(SVGMobject): ignores = [ 'color', 't2c', 'fill_color', 'fill_opacity', - 'stroke_color', 'storke_opacity' + 'stroke_color' ] for ignore in ignores: if kwargs.__contains__(ignore): From e9fa188d42c135ffd12935a5adb90f23f7c49296 Mon Sep 17 00:00:00 2001 From: xy-23 Date: Mon, 12 Aug 2019 09:35:05 +0800 Subject: [PATCH 3/5] New Text Mobject --- manimlib/constants.py | 15 +- manimlib/mobject/svg/text_mobject.py | 391 ++++++++++----------------- 2 files changed, 149 insertions(+), 257 deletions(-) diff --git a/manimlib/constants.py b/manimlib/constants.py index d4f3ac06..57ec438c 100644 --- a/manimlib/constants.py +++ b/manimlib/constants.py @@ -52,15 +52,20 @@ def initialize_directories(config): if folder != "" and not os.path.exists(folder): os.makedirs(folder) -DEFAULT_FONT = '' -DEFAULT_LSH = 1 -DEFAULT_SIZE = 1 NOT_SETTING_FONT_MSG=''' Warning: -You haven't set DEFAULT_FONT. +You haven't set font. If you are not using English, this may cause text rendering problem. -You can change the DEFAULT_FONT in manimlib\\constans.py or Text('your text', font='your font'). +You set font like: +text = Text('your text', font='your font') +or: +class MyText(Text): + CONFIG = { + 'font': 'My Font' + } ''' +START_X = 30 +START_Y = 20 NORMAL = 'NORMAL' ITALIC = 'ITALIC' OBLIQUE = 'OBLIQUE' diff --git a/manimlib/mobject/svg/text_mobject.py b/manimlib/mobject/svg/text_mobject.py index e46626ba..bc569cec 100644 --- a/manimlib/mobject/svg/text_mobject.py +++ b/manimlib/mobject/svg/text_mobject.py @@ -3,12 +3,13 @@ import copy import hashlib import re import os +import manimlib.constants as consts from manimlib.constants import * from manimlib.mobject.svg.svg_mobject import SVGMobject -import manimlib.constants as consts +from manimlib.utils.config_ops import digest_config -class TextStyle(object): +class TextSetting(object): def __init__(self, start, end, font, slant, weight, line_num=-1): self.start = start self.end = end @@ -19,165 +20,86 @@ class TextStyle(object): class Text(SVGMobject): - ''' - Params: - ------- - text :: - a str, the space(' ') in front or back and '\\t' will be ignored(when there is only one line) - - Params(optional): - ----------------- - color :: - color defined in constants.py or a str like '#FFFFFF', default is WHITE - - font :: - a str, the name of font like 'Source Han Sans', default is DEFAULT_FONT, defined in constants.py - - lsh (line_spacing_height) :: - a number, better larger than 0.1(due to anti-aliasing), irrelevant with MUnit, default is DEFAULT_LSH - - size :: - a number, better larger than 0.1(due to anti-aliasing), irrelevant with MUnit, default is DEFAULT_SIZE - - slant :: - NORMAL or ITALIC, default is NORMAL, defined in constants.py(a str actually) - - weight :: - NORMAL or BOLD, default is NORMAL, defined in constants.py(a str actually) - - fill_color :: - the same as color - - fill_opacity :: - a float, default is 1 - - stroke_color :: - the same as color - - t2c (text2color) :: - a dict like {'text':color} or Accurate mode - - t2f (text2font) :: - a dict like {'text':font} or Accurate mode - - t2s (text2slant) :: - a dict like {'text':slant} or Accurate mode - - t2w (text2weight) :: - a dict like {'text':weight} or Accurate mode - - Functions : - ----------- - set_color(mobject function) :: - param color, this will set the color of the whole text - - set_text_color :: - param t2c, the same as the t2c mentioned above(require a dict!) - - Accurate mode: - -------------- - This will help you to choose a specific text just like slicing, e.g. :: - text = Text('ooo', t2c={'[:1]':RED, '[1:2]':GREEN, '[2:]':BLUE}) - - btw, you can use '[[:]]' to represent the text '[:]' - ''' CONFIG = { - "color": WHITE, - "fill_opacity": 1, - "height": None, + #Mobject + 'color': WHITE, + 'height': None, + #Text + 'font': '', + 'gradient': None, + 'lsh': -1, + 'size': 1, + 'slant': NORMAL, + 'weight': NORMAL, + 't2c': {}, + 't2f': {}, + 't2g': {}, + 't2s': {}, + 't2w': {}, } - def __init__(self, text, **kwargs): + def __init__(self, text, **config): self.text = text + self.full2short(config) + digest_config(self, config) + self.lsh = self.size if self.lsh == -1 else self.lsh + + file_name = self.text2svg() + SVGMobject.__init__(self, file_name, **config) - kwargs = self.full2short(**kwargs) - file_name = self.text2svg(text, **kwargs) - SVGMobject.__init__(self, file_name=file_name, **kwargs) - if kwargs.__contains__('t2c'): - self.text2color(text, **kwargs) + if self.t2c: + self.set_color_by_t2c() + if self.gradient: + self.set_color_by_gradient(*self.gradient) + if self.t2g: + self.set_color_by_t2g() + #anti-aliasing - self.scale(0.1) + self.scale(0.1) + + def find_indexes(self, word): + m = re.match(r'\[([0-9\-]{0,}):([0-9\-]{0,})\]', word) + if m: + start = int(m.group(1)) if m.group(1) != '' else 0 + end = int(m.group(2)) if m.group(2) != '' else len(self.text) + start = len(self.text) + start if start < 0 else start + end = len(self.text) + end if end < 0 else end + return [(start, end)] - def full2short(self, **kwargs): - if kwargs.__contains__('line_spacing_height'): - kwargs['lsh'] = kwargs.pop('line_spacing_height') - if kwargs.__contains__('text2color'): - kwargs['t2c'] = kwargs.pop('text2color') - if kwargs.__contains__('text2font'): - kwargs['t2f'] = kwargs.pop('text2font') - if kwargs.__contains__('text2slant'): - kwargs['t2s'] = kwargs.pop('text2slant') - if kwargs.__contains__('text2weight'): - kwargs['t2w'] = kwargs.pop('text2weight') - return kwargs - - def find_indexes(self, text, word): indexes = [] - if re.match(r'\[\[[0-9\-]{0,}:[0-9\-]{0,}\]\]', word): - word = word[1:-1] - index = text.find(word) + index = self.text.find(word) while index != -1: - indexes.append((index, index+len(word))) - index = text.find(word, index+len(word)) + indexes.append((index, index + len(word))) + index = self.text.find(word, index + len(word)) return indexes - def find_strat_and_end(self, text, word): - m = re.match(r'\[([0-9\-]{0,}):([0-9\-]{0,})\]', word) - start = int(m.group(1)) if m.group(1) != '' else 0 - end = int(m.group(2)) if m.group(2) != '' else len(text) - return (start, end) + def full2short(self, config): + for kwargs in [config, self.CONFIG]: + if kwargs.__contains__('line_spacing_height'): + kwargs['lsh'] = kwargs.pop('line_spacing_height') + if kwargs.__contains__('text2color'): + kwargs['t2c'] = kwargs.pop('text2color') + if kwargs.__contains__('text2font'): + kwargs['t2f'] = kwargs.pop('text2font') + if kwargs.__contains__('text2gradient'): + kwargs['t2g'] = kwargs.pop('text2gradient') + if kwargs.__contains__('text2slant'): + kwargs['t2s'] = kwargs.pop('text2slant') + if kwargs.__contains__('text2weight'): + kwargs['t2w'] = kwargs.pop('text2weight') - def is_slicing(self, word): - m = re.match(r'\[[0-9\-]{0,}:[0-9\-]{0,}\]', word) - return True if m else False - - def get_t2c_indexes(self, t2c): - text = self.text - length = len(text) - t2c_indexes = [] + def set_color_by_t2c(self, t2c=None): + t2c = t2c if t2c else self.t2c for word, color in list(t2c.items()): - # accurate mode - if self.is_slicing(word): - start, end = self.find_strat_and_end(text, word) - start = length + start if start < 0 else start - end = length + end if end < 0 else end - t2c_indexes.append((start, end, color)) - continue - for start, end in self.find_indexes(text, word): - t2c_indexes.append((start, end, color)) - return sorted(t2c_indexes, key=lambda i: i[1]) + for start, end in self.find_indexes(word): + self[start:end].set_color(color) - def getfsw(self, **kwargs): - font = kwargs['font'] if kwargs.__contains__('font') else DEFAULT_FONT - slant = kwargs['slant'] if kwargs.__contains__('slant') else NORMAL - weight = kwargs['weight'] if kwargs.__contains__('weight') else NORMAL - return (font, slant, weight) + def set_color_by_t2g(self, t2g=None): + t2g = t2g if t2g else self.t2g + for word, gradient in list(t2g.items()): + for start, end in self.find_indexes(word): + self[start:end].set_color_by_gradient(*gradient) - def getxywh(self, text, font, slant, weight, size): - dir_name = consts.TEXT_DIR - file_name = os.path.join(dir_name, 'temp')+'.svg' - - temp_surface = cairo.SVGSurface(file_name, 1, 1) - temp_context = cairo.Context(temp_surface) - temp_context.set_font_size(size) - if font != '': - fs = self.str2slant(slant) - fw = self.str2weight(weight) - temp_context.select_font_face(font, fs, fw) - x, y, w, h, dx, dy = temp_context.text_extents(text) - return (x, y, w, h) - - def get_space_w(self, font, size): - x1, y1, w1, h1 = self.getxywh('a', font, NORMAL, NORMAL, size) - x2, y2, w2, h2, = self.getxywh('aa', font, NORMAL, NORMAL, size) - return w2 - w1*2 - - def has_multi_line(self, text): - return True if re.search(r'\n', text) else False - - def set_text_color(self, t2c): - self.text2color(self.text, t2c=t2c) - def str2slant(self, string): if string == NORMAL: return cairo.FontSlant.NORMAL @@ -192,130 +114,95 @@ class Text(SVGMobject): if string == BOLD: return cairo.FontWeight.BOLD - def text2color(self, text, **kwargs): - for word, color in list(kwargs['t2c'].items()): - # accurate mode - if self.is_slicing(word): - start, end = self.find_strat_and_end(text, word) - self[start:end].set_color(color) - continue - for start, end in self.find_indexes(text, word): - self[start:end].set_color(color) - - def text2hash(self, text, **kwargs): - ignores = [ - 'color', 't2c', - 'fill_color', 'fill_opacity', - 'stroke_color' - ] - for ignore in ignores: - if kwargs.__contains__(ignore): - kwargs.pop(ignore) - - id_str = text+str(kwargs) + def text2hash(self): + settings = self.font + self.slant + self.weight + settings += str(self.t2f) + str(self.t2s) + str(self.t2w) + settings += str(self.lsh) + str(self.size) + id_str = self.text+settings hasher = hashlib.sha256() hasher.update(id_str.encode()) return hasher.hexdigest()[:16] - def text2styles(self, text, **kwargs): - styles = [] - f0, s0, w0 = self.getfsw(**kwargs) - - if kwargs.__contains__('t2f'): - for word, font in list(kwargs['t2f'].items()): - if self.is_slicing(word): - start, end = self.find_strat_and_end(text, word) - styles.append(TextStyle(start, end, font, s0, w0)) - for start, end in self.find_indexes(text, word): - styles.append(TextStyle(start, end, font, s0, w0)) - - if kwargs.__contains__('t2s'): - for word, slant in list(kwargs['t2s'].items()): - if self.is_slicing(word): - start, end = self.find_strat_and_end(text, word) - styles.append(TextStyle(start, end, f0, slant, w0)) - for start, end in self.find_indexes(text, word): - styles.append(TextStyle(start, end, f0, slant, w0)) - - if kwargs.__contains__('t2w'): - for word, weight in list(kwargs['t2w'].items()): - if self.is_slicing(word): - start, end = self.find_strat_and_end(text, word) - styles.append(TextStyle(start, end, f0, s0, weight)) - for start, end in self.find_indexes(text, word): - styles.append(TextStyle(start, end, f0, s0, weight)) - - #Set All text styles(default font slant weight) - styles = sorted(styles, key=lambda s: s.start) - temp_styles = styles.copy() + def text2settings(self): + settings = [] + t2x = [self.t2f, self.t2s, self.t2w] + for i in range(len(t2x)): + fsw = [self.font, self.slant, self.weight] + if t2x[i]: + for word, x in list(t2x[i].items()): + for start, end in self.find_indexes(word): + fsw[i] = x + settings.append(TextSetting(start, end, *fsw)) + + #Set All text settings(default font slant weight) + fsw = [self.font, self.slant, self.weight] + settings.sort(key = lambda setting: setting.start) + temp_settings = settings.copy() start = 0 - for style in styles: - if style.start != start: - temp_styles.append(TextStyle(start, style.start, f0, s0, w0)) - start = style.end - if start != len(text): - temp_styles.append(TextStyle(start, len(text), f0, s0, w0)) - styles = sorted(temp_styles, key=lambda s: s.start) - - if self.has_multi_line(text): + for setting in settings: + if setting.start != start: + temp_settings.append(TextSetting(start, setting.start, *fsw)) + start = setting.end + if start != len(self.text): + temp_settings.append(TextSetting(start, len(self.text), *fsw)) + settings = sorted(temp_settings, key = lambda setting: setting.start) + + if re.search(r'\n', self.text): line_num = 0 - for start, end in self.find_indexes(text, '\n'): - for style in styles: - if style.line_num == -1: - style.line_num = line_num - if start < style.end: + for start, end in self.find_indexes('\n'): + for setting in settings: + if setting.line_num == -1: + setting.line_num = line_num + if start < setting.end: line_num += 1 - new_style = copy.copy(style) - style.end = end - new_style.start = end - new_style.line_num = line_num - styles.append(new_style) - styles = sorted(styles, key=lambda s: s.start) + new_setting = copy.copy(setting) + setting.end = end + new_setting.start = end + new_setting.line_num = line_num + settings.append(new_setting) + settings.sort(key = lambda setting: setting.start) break + + for setting in settings: + if setting.line_num == -1: + setting.line_num = 0 - return styles + return settings - def text2svg(self, text, **kwargs): - font, slant, weight = self.getfsw(**kwargs) - size = kwargs['size'] if kwargs.__contains__('size') else DEFAULT_SIZE - lsh = kwargs['lsh'] if kwargs.__contains__('lsh') else DEFAULT_LSH + def text2svg(self): #anti-aliasing - size *= 10 - lsh *= 10 + size = self.size * 10 + lsh = self.lsh * 10 - if font == '': + if self.font == '': print(NOT_SETTING_FONT_MSG) dir_name = consts.TEXT_DIR - hash_name = self.text2hash(text, **kwargs) + hash_name = self.text2hash() file_name = os.path.join(dir_name, hash_name)+'.svg' if os.path.exists(file_name): return file_name + + surface = cairo.SVGSurface(file_name, 600, 400) + context = cairo.Context(surface) + context.set_font_size(size) + context.move_to(START_X, START_Y) - text_surface = cairo.SVGSurface(file_name, 300, 200) - text_context = cairo.Context(text_surface) - text_context.set_font_size(size) - - styles = self.text2styles(text, **kwargs) - last_width = 0 - last_ln = 0 - for style in styles: - temp_text = text[style.start:style.end] - sf = style.font - ss = style.slant - sw = style.weight - sln = style.line_num if style.line_num != -1 else 0 - x1, y1, w1, h1 = self.getxywh(temp_text, sf, ss, sw, size) - csf = self.str2slant(ss) - csw = self.str2weight(sw) + settings = self.text2settings() + offset_x = 0 + last_line_num = 0 + for setting in settings: + font = setting.font + slant = self.str2slant(setting.slant) + weight = self.str2weight(setting.weight) + text = self.text[setting.start:setting.end].replace('\n', ' ') - text_context.select_font_face(sf, csf, csw) - if sln != last_ln: - last_width = 0 - last_ln = sln - text_context.move_to(last_width-x1, lsh*sln) - text_context.show_text(temp_text) + context.select_font_face(font, slant, weight) + if setting.line_num != last_line_num: + offset_x = 0 + last_line_num = setting.line_num + context.move_to(START_X+offset_x, START_Y + lsh*setting.line_num) + context.show_text(text) + offset_x += context.text_extents(text)[4] - last_width += w1 + self.get_space_w(sf, size) - - return file_name + return file_name \ No newline at end of file From 0983fa11bec99ceea9d02002b84d85a8d3fa051f Mon Sep 17 00:00:00 2001 From: xy-23 Date: Mon, 12 Aug 2019 14:40:42 +0800 Subject: [PATCH 4/5] Update some Text Mobject changes --- manimlib/mobject/svg/text_mobject.py | 42 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/manimlib/mobject/svg/text_mobject.py b/manimlib/mobject/svg/text_mobject.py index bc569cec..2be9b758 100644 --- a/manimlib/mobject/svg/text_mobject.py +++ b/manimlib/mobject/svg/text_mobject.py @@ -1,8 +1,8 @@ -import cairo -import copy -import hashlib import re import os +import copy +import hashlib +import cairo import manimlib.constants as consts from manimlib.constants import * from manimlib.mobject.svg.svg_mobject import SVGMobject @@ -21,10 +21,10 @@ class TextSetting(object): class Text(SVGMobject): CONFIG = { - #Mobject - 'color': WHITE, + # Mobject + 'color': consts.WHITE, 'height': None, - #Text + # Text 'font': '', 'gradient': None, 'lsh': -1, @@ -46,7 +46,7 @@ class Text(SVGMobject): file_name = self.text2svg() SVGMobject.__init__(self, file_name, **config) - + if self.t2c: self.set_color_by_t2c() if self.gradient: @@ -54,9 +54,9 @@ class Text(SVGMobject): if self.t2g: self.set_color_by_t2g() - #anti-aliasing - self.scale(0.1) - + # anti-aliasing + self.scale(0.1) + def find_indexes(self, word): m = re.match(r'\[([0-9\-]{0,}):([0-9\-]{0,})\]', word) if m: @@ -133,10 +133,10 @@ class Text(SVGMobject): for start, end in self.find_indexes(word): fsw[i] = x settings.append(TextSetting(start, end, *fsw)) - - #Set All text settings(default font slant weight) + + # Set All text settings(default font slant weight) fsw = [self.font, self.slant, self.weight] - settings.sort(key = lambda setting: setting.start) + settings.sort(key=lambda setting: setting.start) temp_settings = settings.copy() start = 0 for setting in settings: @@ -145,8 +145,8 @@ class Text(SVGMobject): start = setting.end if start != len(self.text): temp_settings.append(TextSetting(start, len(self.text), *fsw)) - settings = sorted(temp_settings, key = lambda setting: setting.start) - + settings = sorted(temp_settings, key=lambda setting: setting.start) + if re.search(r'\n', self.text): line_num = 0 for start, end in self.find_indexes('\n'): @@ -160,9 +160,9 @@ class Text(SVGMobject): new_setting.start = end new_setting.line_num = line_num settings.append(new_setting) - settings.sort(key = lambda setting: setting.start) + settings.sort(key=lambda setting: setting.start) break - + for setting in settings: if setting.line_num == -1: setting.line_num = 0 @@ -170,7 +170,7 @@ class Text(SVGMobject): return settings def text2svg(self): - #anti-aliasing + # anti-aliasing size = self.size * 10 lsh = self.lsh * 10 @@ -182,7 +182,7 @@ class Text(SVGMobject): file_name = os.path.join(dir_name, hash_name)+'.svg' if os.path.exists(file_name): return file_name - + surface = cairo.SVGSurface(file_name, 600, 400) context = cairo.Context(surface) context.set_font_size(size) @@ -201,8 +201,8 @@ class Text(SVGMobject): if setting.line_num != last_line_num: offset_x = 0 last_line_num = setting.line_num - context.move_to(START_X+offset_x, START_Y + lsh*setting.line_num) + context.move_to(START_X + offset_x, START_Y + lsh*setting.line_num) context.show_text(text) offset_x += context.text_extents(text)[4] - return file_name \ No newline at end of file + return file_name From 9b3e60f5ce20b8decc051f215f8dc8995ff0716f Mon Sep 17 00:00:00 2001 From: xy-23 Date: Fri, 16 Aug 2019 15:53:36 +0800 Subject: [PATCH 5/5] Update some Text Mobject changes --- manimlib/constants.py | 2 +- manimlib/mobject/svg/svg_mobject.py | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/manimlib/constants.py b/manimlib/constants.py index 57ec438c..82d2298d 100644 --- a/manimlib/constants.py +++ b/manimlib/constants.py @@ -38,7 +38,7 @@ def initialize_directories(config): "directory were both passed" ) - TEX_DIR = config["tex_dir"] or os.path.join(MEDIA_DIR, "texs") + TEX_DIR = config["tex_dir"] or os.path.join(MEDIA_DIR, "Tex") TEXT_DIR = os.path.join(MEDIA_DIR, "texts") if not video_path_specified: VIDEO_DIR = os.path.join(MEDIA_DIR, "videos") diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 219134f9..4798003a 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -286,17 +286,11 @@ class SVGMobject(VMobject): def flatten(self, input_list): output_list = [] - while True: - if input_list == []: - break - for index, i in enumerate(input_list): - if type(i)== list: - input_list = i + input_list[index+1:] - break - else: - output_list.append(i) - input_list.pop(index) - break + for i in input_list: + if isinstance(i, list): + output_list.extend(self.flatten(i)) + else: + output_list.append(i) return output_list def get_all_childNodes_have_id(self, element):