Code() in file Code_mobject.py to display code with color highlighted added Paragraph() and "exact_spaces" parameter to Text() (#1036)

This commit is contained in:
NavpreetDevpuri 2020-05-14 10:49:09 +05:30 committed by GitHub
parent 7f1a15b6ba
commit cf656e9c21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 573 additions and 17 deletions

View file

@ -275,3 +275,107 @@ them to manim.play(), e.g.
>>> c = Circle()
>>> manim.play(ShowCreation(c))
"""
code_languages_list = {"abap": "abap", "as": "as", "as3": "as3", "ada": "ada", "antlr": "antlr",
"antlr_as": "antlr-as",
"antlr_csharp": "antlr-csharp", "antlr_cpp": "antlr-cpp", "antlr_java": "antlr-java",
"antlr_objc": "antlr-objc", "antlr_perl": "antlr-perl", "antlr_python": "antlr-python",
"antlr_ruby": "antlr-ruby", "apacheconf": "apacheconf", "applescript": "applescript",
"aspectj": "aspectj",
"aspx_cs": "aspx-cs", "aspx_vb": "aspx-vb", "asy": "asy", "ahk": "ahk", "autoit": "autoit",
"awk": "awk",
"basemake": "basemake", "bash": "bash", "console": "console", "bat": "bat",
"bbcode": "bbcode",
"befunge": "befunge", "blitzmax": "blitzmax", "boo": "boo", "brainfuck": "brainfuck",
"bro": "bro",
"bugs": "bugs", "c": "c", "csharp": "csharp", "cpp": "cpp", "c_objdump": "c-objdump",
"ca65": "ca65",
"cbmbas": "cbmbas", "ceylon": "ceylon", "cfengine3": "cfengine3", "cfs": "cfs",
"cheetah": "cheetah",
"clojure": "clojure", "cmake": "cmake", "cobol": "cobol", "cobolfree": "cobolfree",
"coffee_script": "coffee-script", "cfm": "cfm", "common_lisp": "common-lisp", "coq": "coq",
"cpp_objdump": "cpp-objdump", "croc": "croc", "css": "css", "css_django": "css+django",
"css_genshitext": "css+genshitext", "css_lasso": "css+lasso", "css_mako": "css+mako",
"css_myghty": "css+myghty", "css_php": "css+php", "css_erb": "css+erb",
"css_smarty": "css+smarty",
"cuda": "cuda", "cython": "cython", "d": "d", "d_objdump": "d-objdump", "dpatch": "dpatch",
"dart": "dart",
"control": "control", "sourceslist": "sourceslist", "delphi": "delphi", "dg": "dg",
"diff": "diff",
"django": "django", "dtd": "dtd", "duel": "duel", "dylan": "dylan",
"dylan_console": "dylan-console",
"dylan_lid": "dylan-lid", "ec": "ec", "ecl": "ecl", "elixir": "elixir", "iex": "iex",
"ragel_em": "ragel-em",
"erb": "erb", "erlang": "erlang", "erl": "erl", "evoque": "evoque", "factor": "factor",
"fancy": "fancy",
"fan": "fan", "felix": "felix", "fortran": "fortran", "Clipper": "Clipper",
"fsharp": "fsharp", "gas": "gas",
"genshi": "genshi", "genshitext": "genshitext", "pot": "pot", "Cucumber": "Cucumber",
"glsl": "glsl",
"gnuplot": "gnuplot", "go": "go", "gooddata_cl": "gooddata-cl", "gosu": "gosu", "gst": "gst",
"groff": "groff",
"groovy": "groovy", "haml": "haml", "haskell": "haskell", "hx": "hx", "html": "html",
"html_cheetah": "html+cheetah", "html_django": "html+django", "html_evoque": "html+evoque",
"html_genshi": "html+genshi", "html_lasso": "html+lasso", "html_mako": "html+mako",
"html_myghty": "html+myghty", "html_php": "html+php", "html_smarty": "html+smarty",
"html_velocity": "html+velocity", "http": "http", "haxeml": "haxeml", "hybris": "hybris",
"idl": "idl",
"ini": "ini", "io": "io", "ioke": "ioke", "irc": "irc", "jade": "jade", "jags": "jags",
"java": "java",
"jsp": "jsp", "js": "js", "js_cheetah": "js+cheetah", "js_django": "js+django",
"js_genshitext": "js+genshitext", "js_lasso": "js+lasso", "js_mako": "js+mako",
"js_myghty": "js+myghty",
"js_php": "js+php", "js_erb": "js+erb", "js_smarty": "js+smarty", "json": "json",
"julia": "julia",
"jlcon": "jlcon", "kconfig": "kconfig", "koka": "koka", "kotlin": "kotlin", "lasso": "lasso",
"lighty": "lighty", "lhs": "lhs", "live_script": "live-script", "llvm": "llvm",
"logos": "logos",
"logtalk": "logtalk", "lua": "lua", "make": "make", "mako": "mako", "maql": "maql",
"mason": "mason",
"matlab": "matlab", "matlabsession": "matlabsession", "minid": "minid",
"modelica": "modelica",
"modula2": "modula2", "trac_wiki": "trac-wiki", "monkey": "monkey", "moocode": "moocode",
"moon": "moon",
"mscgen": "mscgen", "mupad": "mupad", "mxml": "mxml", "myghty": "myghty", "mysql": "mysql",
"nasm": "nasm",
"nemerle": "nemerle", "newlisp": "newlisp", "newspeak": "newspeak", "nginx": "nginx",
"nimrod": "nimrod",
"nsis": "nsis", "numpy": "numpy", "objdump": "objdump", "objective_c": "objective-c",
"objective_c_+": "objective-c++", "objective_j": "objective-j", "ocaml": "ocaml",
"octave": "octave",
"ooc": "ooc", "opa": "opa", "openedge": "openedge", "perl": "perl", "php": "php",
"plpgsql": "plpgsql",
"psql": "psql", "postgresql": "postgresql", "postscript": "postscript", "pov": "pov",
"powershell": "powershell", "prolog": "prolog", "properties": "properties",
"protobuf": "protobuf",
"puppet": "puppet", "pypylog": "pypylog", "python": "python", "python3": "python3",
"py3tb": "py3tb",
"pycon": "pycon", "pytb": "pytb", "qml": "qml", "racket": "racket", "ragel": "ragel",
"ragel_c": "ragel-c",
"ragel_cpp": "ragel-cpp", "ragel_d": "ragel-d", "ragel_java": "ragel-java",
"ragel_objc": "ragel-objc",
"ragel_ruby": "ragel-ruby", "raw": "raw", "rconsole": "rconsole", "rd": "rd",
"rebol": "rebol",
"redcode": "redcode", "registry": "registry", "rst": "rst", "rhtml": "rhtml",
"RobotFramework": "RobotFramework", "spec": "spec", "rb": "rb", "rbcon": "rbcon",
"rust": "rust",
"splus": "splus", "sass": "sass", "scala": "scala", "ssp": "ssp", "scaml": "scaml",
"scheme": "scheme",
"scilab": "scilab", "scss": "scss", "shell_session": "shell-session", "smali": "smali",
"smalltalk": "smalltalk", "smarty": "smarty", "snobol": "snobol", "sp": "sp", "sql": "sql",
"sqlite3": "sqlite3", "squidconf": "squidconf", "stan": "stan", "sml": "sml",
"systemverilog": "systemverilog",
"tcl": "tcl", "tcsh": "tcsh", "tea": "tea", "tex": "tex", "text": "text",
"treetop": "treetop", "ts": "ts",
"urbiscript": "urbiscript", "vala": "vala", "vb.net": "vb.net", "velocity": "velocity",
"verilog": "verilog",
"vgl": "vgl", "vhdl": "vhdl", "vim": "vim", "xml": "xml", "xml_cheetah": "xml+cheetah",
"xml_django": "xml+django", "xml_evoque": "xml+evoque", "xml_lasso": "xml+lasso",
"xml_mako": "xml+mako",
"xml_myghty": "xml+myghty", "xml_php": "xml+php", "xml_erb": "xml+erb",
"xml_smarty": "xml+smarty",
"xml_velocity": "xml+velocity", "xquery": "xquery", "xslt": "xslt", "xtend": "xtend",
"yaml": "yaml"}
code_styles_list = {0: "autumn", 1: "borland", 2: "bw", 3: "colorful", 4: "default", 5: "emacs",
6: "friendly", 7: "fruity", 8: "manni", 9: "monokai", 10: "murphy", 11: "native",
12: "pastie", 13: "perldoc", 14: "rrt", 15: "tango", 16: "trac", 17: "vim", 18: "vs"}

View file

@ -50,6 +50,7 @@ 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.svg.code_mobject import *
from manimlib.mobject.three_d_utils import *
from manimlib.mobject.three_dimensions import *
from manimlib.mobject.types.image_mobject import *

View file

@ -0,0 +1,310 @@
import html
from manimlib.constants import *
from manimlib.container.container import Container
from manimlib.mobject.geometry import Rectangle, Dot, RoundedRectangle
from manimlib.mobject.shape_matchers import SurroundingRectangle
from manimlib.mobject.svg.text_mobject import Paragraph
from manimlib.mobject.types.vectorized_mobject import VGroup
import re
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
'''
1) Code is VGroup() with three things
1.1) Code[0] is Code.background_mobject
which can be a
1.1.1) Rectangle() if background == "rectangle"
1.1.2) VGroup() of Rectangle() and Dot() for three buttons if background == "window"
1.2) Code[1] is Code.line_numbers Which is a Paragraph() object, this mean you can use
Code.line_numbers[0] or Code[1][0] to access first line number
1.3) Code[2] is Code.code
1.3.1) Which is a Paragraph() with color highlighted, this mean you can use
Code.code[1] or Code[2][1]
line number 1
Code.code[1][0] or Code.code[1][0]
first character of line number 1
Code.code[1][0:5] or Code.code[1][0:5]
first five characters of line number 1
'''
class Code(VGroup):
CONFIG = {
"tab_width": 3,
"line_spacing": 0.1,
"scale_factor": 0.5,
"run_time": 1,
"font": 'Monospac821 BT',
'stroke_width': 0,
'margin': 0.3,
'indentation_char': " ",
"background": "rectangle", # or window
"corner_radius": 0.2,
'insert_line_no': True,
'line_no_from': 1,
"line_no_buff": 0.4,
'style': 'vim',
'language': 'cpp',
'generate_html_file': False
}
def __init__(self, file_name=None, **kwargs):
Container.__init__(self, **kwargs)
self.file_name = file_name or self.file_name
self.ensure_valid_file()
self.style = self.style.lower()
self.gen_html_string()
strati = self.html_string.find("background:")
self.background_color = self.html_string[strati + 12:strati + 19]
self.gen_code_json()
self.code = self.gen_colored_lines()
if self.insert_line_no:
self.line_numbers = self.gen_line_numbers()
self.line_numbers.next_to(self.code, direction=LEFT, buff=self.line_no_buff)
if self.background == "rectangle":
if self.insert_line_no:
forground = VGroup(self.code, self.line_numbers)
else:
forground = self.code
self.background_mobject = SurroundingRectangle(forground, buff=self.margin,
color=self.background_color,
fill_color=self.background_color,
stroke_width=0,
fill_opacity=1, )
self.background_mobject.round_corners(self.corner_radius)
else:
if self.insert_line_no:
forground = VGroup(self.code, self.line_numbers)
else:
forground = self.code
height = forground.get_height() + 0.1 * 3 + 2 * self.margin
width = forground.get_width() + 0.1 * 3 + 2 * self.margin
rrect = RoundedRectangle(corner_radius=self.corner_radius, height=height, width=width,
stroke_width=0,
color=self.background_color, fill_opacity=1)
red_button = Dot(radius=0.1, stroke_width=0, color='#ff5f56')
red_button.shift(LEFT * 0.1 * 3)
yellow_button = Dot(radius=0.1, stroke_width=0, color='#ffbd2e')
green_button = Dot(radius=0.1, stroke_width=0, color='#27c93f')
green_button.shift(RIGHT * 0.1 * 3)
buttons = VGroup(red_button, yellow_button, green_button)
buttons.shift(
UP * (height / 2 - 0.1 * 2 - 0.05) + LEFT * (width / 2 - 0.1 * 5 - self.corner_radius / 2 - 0.05))
self.background_mobject = VGroup(rrect, buttons)
x = (height - forground.get_height()) / 2 - 0.1 * 3
self.background_mobject.shift(forground.get_center())
self.background_mobject.shift(UP * x)
if self.insert_line_no:
VGroup.__init__(self, self.background_mobject, self.line_numbers, *self.code, **kwargs)
else:
VGroup.__init__(self, self.background_mobject, Dot(fill_opacity=0, stroke_opacity=0), *self.code, **kwargs)
self.move_to(np.array([0, 0, 0]))
def apply_points_function_about_point(self, func, about_point=None, about_edge=None):
if about_point is None:
if about_edge is None:
about_edge = self.get_corner(UP + LEFT)
about_point = self.get_critical_point(about_edge)
for mob in self.family_members_with_points():
mob.points -= about_point
mob.points = func(mob.points)
mob.points += about_point
return self
def ensure_valid_file(self):
if self.file_name is None:
raise Exception("Must specify file for Code")
possible_paths = [
os.path.join(os.path.join("assets", "codes"), self.file_name),
self.file_name,
]
for path in possible_paths:
if os.path.exists(path):
self.file_path = path
return
raise IOError("No file matching %s in codes directory" %
self.file_name)
def gen_line_numbers(self):
line_numbers_array = []
for line_no in range(0, self.code_json.__len__()):
number = str(self.line_no_from + line_no)
line_numbers_array.append(number)
line_numbers = Paragraph(*[i for i in line_numbers_array], line_spacing=self.line_spacing,
alignment="right", font=self.font, stroke_width=self.stroke_width).scale(self.scale_factor)
return line_numbers
def gen_colored_lines(self):
lines_text = []
for line_no in range(0, self.code_json.__len__()):
line_str = ""
for word_index in range(self.code_json[line_no].__len__()):
line_str = line_str + self.code_json[line_no][word_index][0]
lines_text.append(self.tab_spaces[line_no] * "\t" + line_str)
code = Paragraph(*[i for i in lines_text], line_spacing=self.line_spacing, tab_width=self.tab_width,
alignment="left", font=self.font, stroke_width=self.stroke_width).scale(self.scale_factor)
for line_no in range(code.__len__()):
line = code[line_no]
line_char_index = self.tab_spaces[line_no]
for word_index in range(self.code_json[line_no].__len__()):
line[line_char_index:line_char_index + self.code_json[line_no][word_index][0].__len__()].set_color(
self.code_json[line_no][word_index][1])
line_char_index += self.code_json[line_no][word_index][0].__len__()
return code
def gen_html_string(self):
file = open(self.file_path, "r")
code_str = file.read()
file.close()
self.html_string = hilite_me(code_str, self.language, {}, self.style, self.insert_line_no,
"border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;")
if self.generate_html_file:
os.makedirs(os.path.join("assets", "codes", "generated_html_files"), exist_ok=True)
file = open(os.path.join("assets", "codes", "generated_html_files", self.file_name + ".html"), "w")
file.write(self.html_string)
file.close()
def gen_code_json(self):
if self.background_color == "#111111" or \
self.background_color == "#272822" or \
self.background_color == "#202020" or \
self.background_color == "#000000":
self.default_color = "#ffffff"
else:
self.default_color = "#000000"
for i in range(3, -1, -1):
self.html_string = self.html_string.replace("</" + " " * i, "</")
for i in range(10, -1, -1):
self.html_string = self.html_string.replace("</span>" + " " * i, " " * i + "</span>")
self.html_string = self.html_string.replace("background-color:", "background:")
if self.insert_line_no:
start_point = self.html_string.find("</td><td><pre")
start_point = start_point + 9
else:
start_point = self.html_string.find("<pre")
self.html_string = self.html_string[start_point:]
# print(self.html_string)
lines = self.html_string.split("\n")
lines = lines[0:lines.__len__() - 2]
start_point = lines[0].find(">")
lines[0] = lines[0][start_point + 1:]
# print(lines)
self.code_json = []
self.tab_spaces = []
code_json_line_index = -1
for line_index in range(0, lines.__len__()):
if lines[line_index].__len__() == 0:
continue
# print(lines[line_index])
self.code_json.append([])
code_json_line_index = code_json_line_index + 1
if lines[line_index].startswith(self.indentation_char):
start_point = lines[line_index].find("<")
starting_string = lines[line_index][:start_point]
indentation_char_count = lines[line_index][:start_point].count(self.indentation_char)
if starting_string.__len__() != indentation_char_count * self.indentation_char.__len__():
lines[line_index] = "\t" * indentation_char_count + starting_string[starting_string.rfind(
self.indentation_char) + self.indentation_char.__len__():] + \
lines[line_index][start_point:]
else:
lines[line_index] = "\t" * indentation_char_count + lines[line_index][start_point:]
indentation_char_count = 0
while lines[line_index][indentation_char_count] == '\t':
indentation_char_count = indentation_char_count + 1
self.tab_spaces.append(indentation_char_count)
# print(lines[line_index])
lines[line_index] = self.correct_non_span(lines[line_index])
# print(lines[line_index])
words = lines[line_index].split("<span")
for word_index in range(1, words.__len__()):
color_index = words[word_index].find("color:")
if color_index == -1:
color = self.default_color
else:
starti = words[word_index][color_index:].find("#")
color = words[word_index][color_index + starti:color_index + starti + 7]
start_point = words[word_index].find(">")
end_point = words[word_index].find("</span>")
text = words[word_index][start_point + 1:end_point]
text = html.unescape(text)
if text != "":
# print(text, "'" + color + "'")
self.code_json[code_json_line_index].append([text, color])
# print(self.code_json)
def correct_non_span(self, line_str):
words = line_str.split("</span>")
line_str = ""
for i in range(0, words.__len__()):
if i != words.__len__() - 1:
j = words[i].find("<span")
else:
j = words[i].__len__()
temp = ""
starti = -1
for k in range(0, j):
if words[i][k] == "\t" and starti == -1:
continue
else:
if starti == -1: starti = k
temp = temp + words[i][k]
if temp != "":
if i != words.__len__() - 1:
temp = '<span style="color:' + self.default_color + '">' + words[i][starti:j] + "</span>"
else:
temp = '<span style="color:' + self.default_color + '">' + words[i][starti:j]
temp = temp + words[i][j:]
words[i] = temp
if words[i] != "":
line_str = line_str + words[i] + "</span>"
return line_str
def hilite_me(code, lexer, options, style, linenos, divstyles):
lexer = lexer or 'python'
style = style or 'colorful'
defstyles = 'overflow:auto;width:auto;'
formatter = HtmlFormatter(style=style,
linenos=False,
noclasses=True,
cssclass='',
cssstyles=defstyles + divstyles,
prestyles='margin: 0')
html = highlight(code, get_lexer_by_name(lexer, **options), formatter)
if linenos:
html = insert_line_numbers(html)
html = "<!-- HTML generated using hilite.me -->" + html
return html
def get_default_style():
return 'border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;'
def insert_line_numbers(html):
match = re.search('(<pre[^>]*>)(.*)(</pre>)', html, re.DOTALL)
if not match: return html
pre_open = match.group(1)
pre = match.group(2)
pre_close = match.group(3)
html = html.replace(pre_close, '</pre></td></tr></table>')
numbers = range(1, pre.count('\n') + 1)
format = '%' + str(len(str(numbers[-1]))) + 'i'
lines = '\n'.join(format % i for i in numbers)
html = html.replace(pre_open, '<table><tr><td>' + pre_open + lines + '</pre></td><td>' + pre_open)
return html

View file

@ -5,8 +5,10 @@ import hashlib
import cairo
import manimlib.constants as consts
from manimlib.constants import *
from manimlib.mobject.geometry import Dot
from manimlib.container.container import Container
from manimlib.mobject.geometry import Dot, Rectangle
from manimlib.mobject.svg.svg_mobject import SVGMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.config_ops import digest_config
@ -51,14 +53,17 @@ class Text(SVGMobject):
def __init__(self, text, **config):
self.full2short(config)
digest_config(self, config)
text_without_tabs = text
if text.find('\t') != -1:
text = text.replace('\t', ' '*self.tab_width)
self.text = text
text_without_tabs = text.replace('\t', ' '*self.tab_width)
self.text = text_without_tabs
self.lsh = self.size if self.lsh == -1 else self.lsh
file_name = self.text2svg()
self.remove_last_M(file_name)
SVGMobject.__init__(self, file_name, **config)
self.apply_front_and_end_spaces()
self.text = text
self.apply_space_chars()
nppc = self.n_points_per_cubic_curve
@ -86,20 +91,76 @@ class Text(SVGMobject):
if self.height is None and self.width is None:
self.scale(TEXT_MOB_SCALE_FACTOR)
def apply_space_chars(self):
indexes = self.find_indexes(' ') + self.find_indexes('\n')
indexes = sorted(indexes, key=lambda i: i[0])
if len(self.text) == len(indexes):
space = Dot(fill_opacity=0, stroke_opacity=0)
self.submobjects = [space.copy() for _ in range(len(indexes))]
return
for start, _ in indexes:
space = Dot(fill_opacity=0, stroke_opacity=0)
if start == 0:
space.move_to(self.submobjects[0].get_center())
def get_space_width(self):
size = self.size * 10
dir_name = consts.TEXT_DIR
file_name = os.path.join(dir_name, "space") + '.svg'
surface = cairo.SVGSurface(file_name, 600, 400)
context = cairo.Context(surface)
context.set_font_size(size)
context.move_to(START_X, START_Y)
context.select_font_face(self.font, self.str2slant(self.slant), self.str2weight(self.weight))
context.move_to(START_X, START_Y)
context.show_text("_")
surface.finish()
svg_with_space = SVGMobject(file_name, height=self.height,
width=self.width,
stroke_width=self.stroke_width,
should_center=self.should_center,
unpack_groups=self.unpack_groups, )
space_width = svg_with_space.get_width()
return space_width
def apply_front_and_end_spaces(self):
space_width = self.get_space_width()
max_height = self.get_height()
front_spaces_count = 0
i = -1
for i in range(self.text.__len__()):
if self.text[i] == " ":
front_spaces_count += 1
continue
else:
space.move_to(self.submobjects[start-1].get_center())
self.submobjects.insert(start, space)
break
first_visible_char_index = i
if first_visible_char_index != 0:
space = Rectangle(width=space_width * front_spaces_count, height=max_height, fill_opacity=0,
stroke_opacity=0,
stroke_width=0)
text_width = self.get_width()
space.move_to(np.array([-text_width / 2, max_height / 2, 0]))
self.next_to(space, direction=RIGHT, buff=0)
self.submobjects.insert(0, space)
i = -1
last_spaces_count = 0
for i in range(self.text.__len__() - 1, -1, -1):
if self.text[i] == " ":
last_spaces_count += 1
continue
else:
break
last_visible_char_index = i
if last_visible_char_index != self.text.__len__() - 1:
space = Rectangle(width=space_width * last_spaces_count, height=max_height, fill_opacity=0,
stroke_opacity=0,
stroke_width=0)
text_width = self.get_width()
space.move_to(np.array([-text_width / 2, max_height / 2, 0]))
self.next_to(space, direction=LEFT, buff=0)
self.submobjects.append(space)
self.move_to(np.array([0,0,0]))
def apply_space_chars(self):
char_index = 0
while char_index < self.text.__len__() - 1:
char_index += 1
if self.text[char_index] == " " or self.text[char_index] == "\t" or self.text[char_index] == "\n":
space = Dot(fill_opacity=0, stroke_opacity=0)
space.move_to(self.submobjects[char_index - 1].get_center())
self.submobjects.insert(char_index, space)
def remove_last_M(self, file_name):
with open(file_name, 'r') as fpr:
@ -226,7 +287,8 @@ class Text(SVGMobject):
lsh = self.lsh * 10
if self.font == '':
print(NOT_SETTING_FONT_MSG)
if NOT_SETTING_FONT_MSG != '':
print(NOT_SETTING_FONT_MSG)
dir_name = consts.TEXT_DIR
hash_name = self.text2hash()
@ -257,3 +319,81 @@ class Text(SVGMobject):
offset_x += context.text_extents(text)[4]
return file_name
class TextWithFixHeight(Text):
def __init__(self, text, **kwargs):
Text.__init__(self, text, **kwargs)
max_height = Text("(gyt{[/QW", **kwargs).get_height()
rectangle = Rectangle(width=0, height=max_height, fill_opacity=0,
stroke_opacity=0,
stroke_width=0)
self.submobjects.append(rectangle)
class Paragraph(VGroup):
CONFIG = {
"line_spacing": 0.1,
"alignment": "center",
}
def __init__(self, *text, **config):
Container.__init__(self, **config)
self.lines_list = list(text)
self.lines = []
self.lines.append([])
for line_no in range(self.lines_list.__len__()):
if "\n" in self.lines_list[line_no]:
self.lines_list[line_no:line_no + 1] = self.lines_list[line_no].split("\n")
for line_no in range(self.lines_list.__len__()):
self.lines[0].append(TextWithFixHeight(self.lines_list[line_no], **config))
self.char_height = TextWithFixHeight("(", **config).get_height()
self.lines.append([])
self.lines[1].extend([self.alignment for _ in range(self.lines_list.__len__())])
self.lines[0][0].move_to(np.array([0, 0, 0]))
self.align_lines()
VGroup.__init__(self, *[self.lines[0][i] for i in range(self.lines[0].__len__())], **config)
self.move_to(np.array([0, 0, 0]))
def set_all_lines_alignment(self, alignment):
self.lines[1] = [alignment for _ in range(self.lines_list.__len__())]
for line_no in range(0, self.lines[0].__len__()):
self.change_alignment_for_a_line(alignment, line_no)
return self
def set_alignment(self, alignment, line_no):
self.change_alignment_for_a_line(alignment, line_no)
return self
def change_alignment_for_a_line(self, alignment, line_no):
self.lines[1][line_no] = alignment
if self.lines[1][line_no] == "center":
self[line_no].move_to(self.get_top() +
np.array([0, -self.char_height / 2, 0]) +
np.array([0, - line_no * (self.char_height + self.line_spacing), 0]))
elif self.lines[1][line_no] == "right":
self[line_no].move_to(self.get_top() +
np.array([0, -self.char_height / 2, 0]) +
np.array([self.get_width() / 2 - self.lines[0][line_no].get_width() / 2,
- line_no * (self.char_height + self.line_spacing), 0])
)
elif self.lines[1][line_no] == "left":
self[line_no].move_to(self.get_top() +
np.array([0, -self.char_height / 2, 0]) +
np.array([- self.get_width() / 2 + self.lines[0][line_no].get_width() / 2,
- line_no * (self.char_height + self.line_spacing), 0])
)
def align_lines(self):
for line_no in range(0, self.lines[0].__len__()):
if self.lines[1][line_no] == "center":
self.lines[0][line_no].move_to(
np.array([0, 0, 0]) + np.array([0, - line_no * (self.char_height + self.line_spacing), 0]))
elif self.lines[1][line_no] == "left":
self.lines[0][line_no].move_to(np.array([0, 0, 0]) +
np.array([self.lines[0][line_no].get_width() / 2,
- line_no * (self.char_height + self.line_spacing), 0])
)
elif self.lines[1][line_no] == "right":
self.lines[0][line_no].move_to(np.array([0, 0, 0]) +
np.array([- self.lines[0][line_no].get_width() / 2,
- line_no * (self.char_height + self.line_spacing), 0])
)

View file

@ -8,4 +8,5 @@ tqdm
opencv-python
pycairo
pydub
pygments
pyreadline; sys_platform == 'win32'