mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 13:07:44 +00:00
Merge pull request #1383 from naveen521kk/manimpango
Use ManimPango for Text rendering
This commit is contained in:
commit
a5d5e40f6a
2 changed files with 85 additions and 71 deletions
|
|
@ -1,29 +1,23 @@
|
|||
import re
|
||||
import os
|
||||
import copy
|
||||
import hashlib
|
||||
import cairo
|
||||
import os
|
||||
import re
|
||||
import typing
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
import manimpango
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import Dot
|
||||
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.customization import get_customization
|
||||
from manimlib.utils.directories import get_text_dir
|
||||
|
||||
from manimlib.utils.directories import get_downloads_dir, get_text_dir
|
||||
from manimpango import PangoUtils, TextSetting
|
||||
|
||||
TEXT_MOB_SCALE_FACTOR = 0.001048
|
||||
|
||||
|
||||
class TextSetting(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):
|
||||
CONFIG = {
|
||||
# Mobject
|
||||
|
|
@ -44,6 +38,7 @@ class Text(SVGMobject):
|
|||
"t2g": {},
|
||||
"t2s": {},
|
||||
"t2w": {},
|
||||
"disable_ligatures": True,
|
||||
}
|
||||
|
||||
def __init__(self, text, **config):
|
||||
|
|
@ -55,12 +50,11 @@ class Text(SVGMobject):
|
|||
text_without_tabs = text.replace('\t', ' ' * self.tab_width)
|
||||
self.text = text_without_tabs
|
||||
file_name = self.text2svg()
|
||||
self.remove_last_M(file_name)
|
||||
self.remove_empty_path(file_name)
|
||||
PangoUtils.remove_last_M(file_name)
|
||||
SVGMobject.__init__(self, file_name, **config)
|
||||
self.text = text
|
||||
self.apply_space_chars()
|
||||
|
||||
if self.disable_ligatures:
|
||||
self.apply_space_chars()
|
||||
if self.t2c:
|
||||
self.set_color_by_t2c()
|
||||
if self.gradient:
|
||||
|
|
@ -74,8 +68,12 @@ class Text(SVGMobject):
|
|||
|
||||
def apply_space_chars(self):
|
||||
submobs = self.submobjects.copy()
|
||||
for char_index in range(self.text.__len__()):
|
||||
if self.text[char_index] == " " or self.text[char_index] == "\t" or self.text[char_index] == "\n":
|
||||
for char_index in range(len(self.text)):
|
||||
if (
|
||||
self.text[char_index] == " "
|
||||
or self.text[char_index] == "\t"
|
||||
or self.text[char_index] == "\n"
|
||||
):
|
||||
space = Dot(radius=0, fill_opacity=0, stroke_opacity=0)
|
||||
if char_index == 0:
|
||||
space.move_to(submobs[char_index].get_center())
|
||||
|
|
@ -84,20 +82,6 @@ class Text(SVGMobject):
|
|||
submobs.insert(char_index, space)
|
||||
self.set_submobjects(submobs)
|
||||
|
||||
def remove_last_M(self, file_name):
|
||||
with open(file_name, 'r') as fpr:
|
||||
content = fpr.read()
|
||||
content = re.sub(r'Z M [^A-Za-z]*? "\/>', 'Z "/>', content)
|
||||
with open(file_name, 'w') as fpw:
|
||||
fpw.write(content)
|
||||
|
||||
def remove_empty_path(self, file_name):
|
||||
with open(file_name, 'r') as fpr:
|
||||
content = fpr.read()
|
||||
content = re.sub(r'<path .*?d=""/>', '', content)
|
||||
with open(file_name, 'w') as fpw:
|
||||
fpw.write(content)
|
||||
|
||||
def find_indexes(self, word):
|
||||
m = re.match(r'\[([0-9\-]{0,}):([0-9\-]{0,})\]', word)
|
||||
if m:
|
||||
|
|
@ -141,20 +125,6 @@ class Text(SVGMobject):
|
|||
for start, end in self.find_indexes(word):
|
||||
self[start:end].set_color_by_gradient(*gradient)
|
||||
|
||||
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 text2hash(self):
|
||||
settings = self.font + self.slant + self.weight
|
||||
settings += str(self.t2f) + str(self.t2s) + str(self.t2w)
|
||||
|
|
@ -223,27 +193,71 @@ 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)
|
||||
context.move_to(START_X, START_Y)
|
||||
|
||||
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', ' ')
|
||||
width = 600
|
||||
height = 400
|
||||
disable_liga = self.disable_ligatures
|
||||
return manimpango.text2svg(
|
||||
settings,
|
||||
size,
|
||||
lsh,
|
||||
disable_liga,
|
||||
file_name,
|
||||
START_X,
|
||||
START_Y,
|
||||
width,
|
||||
height,
|
||||
self.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]
|
||||
@contextmanager
|
||||
def register_font(font_file: typing.Union[str, Path]):
|
||||
"""Temporarily add a font file to Pango's search path.
|
||||
This searches for the font_file at various places. The order it searches it described below.
|
||||
1. Absolute path.
|
||||
2. Downloads dir.
|
||||
|
||||
return file_name
|
||||
Parameters
|
||||
----------
|
||||
font_file :
|
||||
The font file to add.
|
||||
Examples
|
||||
--------
|
||||
Use ``with register_font(...)`` to add a font file to search
|
||||
path.
|
||||
.. code-block:: python
|
||||
with register_font("path/to/font_file.ttf"):
|
||||
a = Text("Hello", font="Custom Font Name")
|
||||
Raises
|
||||
------
|
||||
FileNotFoundError:
|
||||
If the font doesn't exists.
|
||||
AttributeError:
|
||||
If this method is used on macOS.
|
||||
Notes
|
||||
-----
|
||||
This method of adding font files also works with :class:`CairoText`.
|
||||
.. important ::
|
||||
This method isn't available for macOS. Using this
|
||||
method on macOS will raise an :class:`AttributeError`.
|
||||
"""
|
||||
|
||||
input_folder = Path(get_downloads_dir()).parent.resolve()
|
||||
possible_paths = [
|
||||
Path(font_file),
|
||||
input_folder / font_file,
|
||||
]
|
||||
for path in possible_paths:
|
||||
path = path.resolve()
|
||||
if path.exists():
|
||||
file_path = path
|
||||
break
|
||||
else:
|
||||
error = f"Can't find {font_file}." f"Tried these : {possible_paths}"
|
||||
raise FileNotFoundError(error)
|
||||
|
||||
try:
|
||||
assert manimpango.register_font(str(file_path))
|
||||
yield
|
||||
finally:
|
||||
manimpango.unregister_font(str(file_path))
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ pyreadline; sys_platform == 'win32'
|
|||
validators
|
||||
ipython
|
||||
PyOpenGL
|
||||
pycairo
|
||||
manimpango>=0.2.0,<0.3.0'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue