mirror of
https://github.com/3b1b/manim.git
synced 2025-09-19 04:41:56 +00:00
Merge branch 'master' of github.com:3b1b/manim into video-work
This commit is contained in:
commit
88f2ae6d0d
25 changed files with 1645 additions and 739 deletions
|
@ -12,7 +12,7 @@
|
|||
|
||||
Manim is an engine for precise programmatic animations, designed for creating explanatory math videos.
|
||||
|
||||
Note, there are two versions of manim. This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos). In 2020 a group of developers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. See [this page](https://docs.manim.community/en/stable/installation/versions.html?highlight=OpenGL#which-version-to-use) for more details.
|
||||
Note, there are two versions of manim. This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos). In 2020 a group of developers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. See [this page](https://docs.manim.community/en/stable/faq/installation.html#different-versions) for more details.
|
||||
|
||||
## Installation
|
||||
> **WARNING:** These instructions are for ManimGL _only_. Trying to use these instructions to install [ManimCommunity/manim](https://github.com/ManimCommunity/manim) or instructions there to install this version will cause problems. You should first decide which version you wish to install, then only follow the instructions for your desired version.
|
||||
|
|
|
@ -1,6 +1,97 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
Unreleased
|
||||
----------
|
||||
|
||||
Breaking Changes
|
||||
^^^^^^^^^^^^^^^^
|
||||
- Added ``InteractiveScene`` (`#1794 <https://github.com/3b1b/manim/pull/1794>`__)
|
||||
|
||||
Fixed bugs
|
||||
^^^^^^^^^^
|
||||
- Fixed ``ImageMobject`` by overriding ``set_color`` method (`#1791 <https://github.com/3b1b/manim/pull/1791>`__)
|
||||
- Fixed bug with trying to close window during embed (`#1796 <https://github.com/3b1b/manim/commit/e0f5686d667152582f052021cd62bd2ef8c6b470>`__)
|
||||
- Fixed animating ``Mobject.restore`` bug (`#1796 <https://github.com/3b1b/manim/commit/62289045cc8e102121cfe4d7739f3c89102046fb>`__)
|
||||
- Fixed ``InteractiveScene.refresh_selection_highlight`` (`#1802 <https://github.com/3b1b/manim/commit/205116b8cec964b5619416f6e8acf0d8ac7df828>`__)
|
||||
- Fixed ``VMobject.match_style`` (`#1821 <https://github.com/3b1b/manim/commit/0060a4860c9d6b073a60cd839269c213446bba7b>`__)
|
||||
|
||||
New Features
|
||||
^^^^^^^^^^^^
|
||||
- Added specific euler angle getters (`#1794 <https://github.com/3b1b/manim/commit/df2d465140e25fee265f602608aebbbaa2898c7e>`__)
|
||||
- Added start angle option to ``Circle`` (`#1794 <https://github.com/3b1b/manim/commit/217c1d7bb02f23a61722bf7275c40802be808563>`__)
|
||||
- Added ``Mobject.is_touching`` (`#1794 <https://github.com/3b1b/manim/commit/c1716895c0d9f36e23487322a18963991100bb95>`__)
|
||||
- Added ``Mobject.get_highlight`` (`#1794 <https://github.com/3b1b/manim/commit/29816fa74c7aa6ca060b63ab4165c89987e58d8b>`__)
|
||||
- Allowed for saving and loading mobjects from file (`#1794 <https://github.com/3b1b/manim/commit/50f5d20cc379947d7253d841c060dd7c55fa7787>`__)
|
||||
- Added ``Mobject.get_all_corners`` (`#1794 <https://github.com/3b1b/manim/commit/f636199d9a5d1e87ab861bcb6aebae6c9d96a133>`__)
|
||||
- Added ``Scene.id_to_mobject`` and ``Scene.ids_to_group`` (`#1794 <https://github.com/3b1b/manim/commit/cb768c26a0bc63e02c3035b4af31ba5cbc2e9dda>`__)
|
||||
- Added ``Scene.save_mobject`` and ``Scene.load_mobject`` to allow for saving and loading mobjects from file at the Scene level (`#1794 <https://github.com/3b1b/manim/commit/777b6d37783f8592df8a8abc3d62af972bc5a0c6>`__)
|
||||
- Added ``InteractiveScene`` (`#1794 <https://github.com/3b1b/manim/commit/c3afc84bfeb3a76ea8ede4ec4d9f36df0d4d9a28>`__)
|
||||
- Added ``VHighlight`` (`#1794 <https://github.com/3b1b/manim/commit/9d5e2b32fa9215219d11a601829126cea40410d1>`__)
|
||||
- Allowed for sweeping selection (`#1796 <https://github.com/3b1b/manim/commit/4caa03332367631d2fff15afd7e56b15fe8701ee>`__)
|
||||
- Allowed stretched-resizing (`#1796 <https://github.com/3b1b/manim/commit/b4b72d1b68d0993b96a6af76c4bb6816f77f0f12>`__)
|
||||
- Added cursor location label (`#1796 <https://github.com/3b1b/manim/commit/b9751e9d06068f27a327b419c52fd3c9d68db2e6>`__)
|
||||
- Added ``Mobject.deserialize`` (`#1796 <https://github.com/3b1b/manim/commit/4d8698a0e88333f6481c08d1b84b6e44f9dc4543>`__)
|
||||
- Added undo and redo stacks for scene (`#1796 <https://github.com/3b1b/manim/commit/cf466006faa00fc12dc22f5732dc21ccedaa5a63>`__)
|
||||
- Added ``Mobject.looks_identical`` (`#1802 <https://github.com/3b1b/manim/commit/c3c5717dde543b172b928b516d80a29bbd12651f>`__)
|
||||
- Added equality for ``ShaderWrapper`` (`#1802 <https://github.com/3b1b/manim/commit/3ae0a4e81b7790194bcf27142a1deb29fa548b9d>`__)
|
||||
- Added ``Mobject.get_ancestors`` (`#1802 <https://github.com/3b1b/manim/commit/db884b0a67fcee1ad7009f1869c475015fa886c7>`__)
|
||||
- Added smarter default radius to ``Polygon.round_corners`` (`#1802 <https://github.com/3b1b/manim/commit/4c1210b3ab1bf66b161f3d00cb859d36068c2fbb>`__)
|
||||
- Added checkpoints to ``Scene`` (`#1821 <https://github.com/3b1b/manim/commit/1b589e336f8151f2914ff00e8956baea8a95abc5>`__)
|
||||
- Added ``crosshair`` to ``InteractiveScene`` (`#1821 <https://github.com/3b1b/manim/commit/33ffd4863aaa7ecf950b7044181a8e8e3c643698>`__)
|
||||
- Added ``SceneState`` (`#1821 <https://github.com/3b1b/manim/commit/75e1cff5792065aa1c7fb3eb02e6ee0fa0e8e18d>`__)
|
||||
- Added ``time_span`` option to ``Animation`` (`#1821 <https://github.com/3b1b/manim/commit/a6fcfa3b4053b7f68f7b029eae87dbd207d97ad2>`__)
|
||||
- Added ``Mobject.arrange_to_fit_dim`` (`#1821 <https://github.com/3b1b/manim/commit/a87d3b5f59a64ce5a89ce6e17310bdbf62166157>`__)
|
||||
- Added ``DecimalNumber.get_tex`` (`#1821 <https://github.com/3b1b/manim/commit/48689c8c7bc0029bf5c1b540c11f647e857d419b>`__)
|
||||
|
||||
Refactor
|
||||
^^^^^^^^
|
||||
- Updated parent updater status when adding updaters (`#1794 <https://github.com/3b1b/manim/commit/3b847da9eaad7391e779c5dbce63ad9257d8c773>`__)
|
||||
- Added case for zero vectors on ``angle_between_vectors`` (`#1794 <https://github.com/3b1b/manim/commit/e8ac25903e19cbb2b2c2037c988baafce4ddcbbc>`__)
|
||||
- Refactored ``Mobject.clear_updaters`` (`#1794 <https://github.com/3b1b/manim/commit/95f56f5e80106443d705c68fa220850ec38daee0>`__)
|
||||
- Changed the way changing-vs-static mobjects are tracked (more details see `#1794 <https://github.com/3b1b/manim/commit/50565fcd7a43ed13dc532f17515208edf97f64d0>`__)
|
||||
- Refactored ``Mobject.is_point_touching`` (`#1794 <https://github.com/3b1b/manim/commit/135f68de35712be266a1a85261d6d44234fc0056>`__)
|
||||
- Refactored ``Mobject.make_movable`` and ``Mobject.set_animating_status`` to recurse over family (`#1794 <https://github.com/3b1b/manim/commit/48390375037f745c9cb82b03d1cb3a1de6c530f3>`__)
|
||||
- Refactored ``AnimationGroup`` (`#1794 <https://github.com/3b1b/manim/commit/fdeab8ca953b46a902b531febcf132739ca194d4>`__)
|
||||
- Refactored ``Scene.save_state`` and ``Scene.restore`` (`#1794 <https://github.com/3b1b/manim/commit/97400a5cf26f33ed507ddeeb9b9a7f1a558d4f17>`__)
|
||||
- Added ``MANIM_COLORS`` (`#1794 <https://github.com/3b1b/manim/commit/5a34ca1fba8b4724eda0caa11b271d74e49f468c>`__)
|
||||
- Changed default transparent background codec to be prores (`#1794 <https://github.com/3b1b/manim/commit/eae7dbbe6eaf4344374713052aae694e69b62c28>`__)
|
||||
- Simplified ``Mobject.copy`` (`#1794 <https://github.com/3b1b/manim/commit/1b009a4b035244bd6a0b48bc4dc945fd3b4236ef>`__)
|
||||
- Refactored ``StringMobject`` and relevant classes (`#1795 <https://github.com/3b1b/manim/pull/1795>`__)
|
||||
- Updates to copying based on pickle serializing (`#1796 <https://github.com/3b1b/manim/commit/fe3e10acd29a3dd6f8b485c0e36ead819f2d937b>`)
|
||||
- Removed ``refresh_shader_wrapper_id`` from ``Mobject.become`` (`#1796 <https://github.com/3b1b/manim/commit/1b2460f02a694314897437b9b8755443ed290cc1>`__)
|
||||
- Refactored ``Scene.embed`` to play nicely with gui interactions (`#1796 <https://github.com/3b1b/manim/commit/c96bdc243e57c17bb75bf12d73ab5bf119cf1464>`__)
|
||||
- Made ``BlankScene`` inherit from ``InteractiveScene`` (`#1796 <https://github.com/3b1b/manim/commit/2737d9a736885a594dd101ffe07bb82e00069333>`__)
|
||||
- Updated behavior of -e flag to take in (optional) strings as inputs (`#1796 <https://github.com/3b1b/manim/commit/bb7fa2c8aa68d7c7992517cfde3c7d0e804e13e8>`__)
|
||||
- Refactor -e flag (`#1796 <https://github.com/3b1b/manim/commit/71c14969dffc8762a43f9646a0c3dc024a51b8df>`__)
|
||||
- Reverted to original copying scheme (`#1796 <https://github.com/3b1b/manim/commit/59506b89cc73fff3b3736245dd72e61dcebf9a2c>`__)
|
||||
- Renamed ``Mobject.is_movable`` to ``Mobject.interaction_allowed`` (`#1796 <https://github.com/3b1b/manim/commit/3961005fd708333a3e77856d10e78451faa04075>`__)
|
||||
- Refreshed static mobjects on undo's and redo's (`#1796 <https://github.com/3b1b/manim/commit/04bca6cafbb1482b8f25cfb34ce83316d8a095c9>`__)
|
||||
- Factored out event handling (`#1796 <https://github.com/3b1b/manim/commit/754316bf586be5a59839f8bac6fb9fcc47da0efb>`__)
|
||||
- Removed ``Mobject.interaction_allowed``, in favor of using ``_is_animating`` for multiple purposes (`#1796 <https://github.com/3b1b/manim/commit/f70e91348c8241bcb96470e7881dd92d9d3386d3>`__)
|
||||
- Moved Command + z and Command + shift + z behavior to Scene (`#1797 <https://github.com/3b1b/manim/commit/0fd8491c515ad23ca308099abe0f39fc38e2dd0e>`__)
|
||||
- Slight copy refactor (`#1797 <https://github.com/3b1b/manim/commit/902c2c002d6ca03c8080b2bd02ca36f2b8a748b6>`__)
|
||||
- When scene saves state, have it only copy mobjects which have changed (`#1802 <https://github.com/3b1b/manim/commit/bd2dce08300e5b110c6668bd6763f3918fcdc65e>`__)
|
||||
- Cleaned up ``Scene.remove`` function (`#1802 <https://github.com/3b1b/manim/commit/6310e2fb6414b01b3fe4be1d4d98525e34356b5e>`__)
|
||||
- Speed-ups to ``Mobject.copy`` (`#1802 <https://github.com/3b1b/manim/commit/e49e4b8373c13c7a888193aaf61955470acbe5d6>`__)
|
||||
- Slight speed-up to ``InteractiveScene.gather_selection`` (`#1802 <https://github.com/3b1b/manim/commit/f2b4245c134da577a2854732ec0331768d93ffbe>`__)
|
||||
- Only leave wait notes in presenter mode (`#1802 <https://github.com/3b1b/manim/commit/42d1f48c60d11caa043d5458e64bfceb31ea203f>`__)
|
||||
- Refactored ``remove_list_redundancies`` and ``list_update`` (`#1821 <https://github.com/3b1b/manim/commit/b920e7be7b85bc0bb0577e2f71c4320bb97b42d4>`__)
|
||||
- Match updaters in ``Mobject.become`` (`#1821 <https://github.com/3b1b/manim/commit/0e45b41fea5f22d136f62f4af2e0d892e61a12ce>`__)
|
||||
- Don't show animation progress bar by default (`#1821 <https://github.com/3b1b/manim/commit/52259af5df619d3f44fbaff4c43402b93d01be2f>`__)
|
||||
- Handle quitting during scene more gracefully (`#1821 <https://github.com/3b1b/manim/commit/e83ad785caaa1a1456e07b23f207469d335bbc0d>`__)
|
||||
- Made ``selection_highlight`` refresh with an updater (`#1821 <https://github.com/3b1b/manim/commit/ac08963feff24a1dd2e57f604b44ea0a18ab01f3>`__)
|
||||
- Refactored ``anims_from_play_args`` to ``prepare_animations`` which deprecating old style ``self.play(mob.method, ...)`` (`#1821 <https://github.com/3b1b/manim/commit/feab79c260498fd7757a304e24c617a4e51ba1df>`__)
|
||||
- Made presenter mode hold before first play call (`#1821 <https://github.com/3b1b/manim/commit/a9a151d4eff80cc37b9db0fe7117727aac45ba09>`__)
|
||||
- Update frame on all play calls when skipping animations, so as to provide a rapid preview during scene loading (`#1821 <https://github.com/3b1b/manim/commit/41b811a5e7c03f528d41555217106e62b287ca3b>`__)
|
||||
- Renamed frame_rate to fps (`#1821 <https://github.com/3b1b/manim/commit/6decb0c32aec21c09007f9a2b91aaa8e642ca848>`__)
|
||||
- Let default text alignment be decided in default_config (`#1821 <https://github.com/3b1b/manim/commit/83b4aa6b88b6c3defb19f204189681f5afbb219e>`__)
|
||||
|
||||
Dependencies
|
||||
^^^^^^^^^^^^
|
||||
- Added dependency on ``pyperclip`` (`#1794 <https://github.com/3b1b/manim/commit/e579f4c955844fba415b976c313f64d1bb0376d0>`__)
|
||||
|
||||
|
||||
v1.6.1
|
||||
------
|
||||
|
||||
|
|
|
@ -98,6 +98,9 @@ Import line that need to execute when entering interactive mode directly.
|
|||
- ``font``
|
||||
Default font of Text
|
||||
|
||||
- ``text_alignment``
|
||||
Default text alignment for LaTeX
|
||||
|
||||
- ``background_color``
|
||||
Default background color
|
||||
|
||||
|
@ -113,6 +116,11 @@ means left(L) / middle(O) / right(R)).
|
|||
|
||||
The number of the monitor you want the preview window to pop up on. (default is 0)
|
||||
|
||||
``full_screen``
|
||||
---------------
|
||||
|
||||
Whether open the window in full screen. (default is false)
|
||||
|
||||
``break_into_partial_movies``
|
||||
-----------------------------
|
||||
|
||||
|
@ -123,22 +131,27 @@ to form the full scene.
|
|||
Sometimes video-editing is made easier when working with the broken up scene, which
|
||||
effectively has cuts at all the places you might want.
|
||||
|
||||
``camera_qualities``
|
||||
--------------------
|
||||
``camera_resolutions``
|
||||
----------------------
|
||||
|
||||
Export quality
|
||||
Export resolutions
|
||||
|
||||
- ``low``
|
||||
Low quality (default is 480p15)
|
||||
Low resolutions (default is 480p)
|
||||
|
||||
- ``medium``
|
||||
Medium quality (default is 720p30)
|
||||
Medium resolutions (default is 720p)
|
||||
|
||||
- ``high``
|
||||
High quality (default is 1080p30)
|
||||
High resolutions (default is 1080p)
|
||||
|
||||
- ``ultra_high``
|
||||
Ultra high quality (default is 4K60)
|
||||
Ultra high resolutions (default is 4K)
|
||||
|
||||
- ``default_quality``
|
||||
Default quality (one of the above four)
|
||||
- ``default_resolutions``
|
||||
Default resolutions (one of the above four, default is high)
|
||||
|
||||
``fps``
|
||||
-------
|
||||
|
||||
Export frame rate. (default is 30)
|
|
@ -32,10 +32,11 @@ Some useful flags
|
|||
All supported flags
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
========================================================== ====== =================================================================================================================================================================================================
|
||||
========================================================== ====== =====================================================================================================================================================================================================
|
||||
flag abbr function
|
||||
========================================================== ====== =================================================================================================================================================================================================
|
||||
========================================================== ====== =====================================================================================================================================================================================================
|
||||
``--help`` ``-h`` Show the help message and exit
|
||||
``--version`` ``-v`` Display the version of manimgl
|
||||
``--write_file`` ``-w`` Render the scene as a movie file
|
||||
``--skip_animations`` ``-s`` Skip to the last frame
|
||||
``--low_quality`` ``-l`` Render at a low quality (for faster rendering)
|
||||
|
@ -45,7 +46,7 @@ flag abbr function
|
|||
``--full_screen`` ``-f`` Show window in full screen
|
||||
``--presenter_mode`` ``-p`` Scene will stay paused during wait calls until space bar or right arrow is hit, like a slide show
|
||||
``--save_pngs`` ``-g`` Save each frame as a png
|
||||
``--save_as_gif`` ``-i`` Save the video as gif
|
||||
``--gif`` ``-i`` Save the video as gif
|
||||
``--transparent`` ``-t`` Render to a movie file with an alpha channel
|
||||
``--quiet`` ``-q``
|
||||
``--write_all`` ``-a`` Write all the scenes from a file
|
||||
|
@ -54,14 +55,15 @@ flag abbr function
|
|||
``--config`` Guide for automatic configuration
|
||||
``--file_name FILE_NAME`` Name for the movie or image file
|
||||
``--start_at_animation_number START_AT_ANIMATION_NUMBER`` ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passing two comma separated values, e.g. "3,6", it will end the rendering at the second value.
|
||||
``--embed LINENO`` ``-e`` Takes a line number as an argument, and results in the scene being called as if the line ``self.embed()`` was inserted into the scene code at that line number
|
||||
``--embed [EMBED]`` ``-e`` Creates a new file where the line ``self.embed`` is inserted into the Scenes construct method. If a string is passed in, the line will be inserted below the last line of code including that string.
|
||||
``--resolution RESOLUTION`` ``-r`` Resolution, passed as "WxH", e.g. "1920x1080"
|
||||
``--fps FPS`` Frame rate, as an integer
|
||||
``--fps FPS`` Frame rate, as an integer
|
||||
``--color COLOR`` ``-c`` Background color
|
||||
``--leave_progress_bars`` Leave progress bars displayed in terminal
|
||||
``--video_dir VIDEO_DIR`` Directory to write video
|
||||
``--config_file CONFIG_FILE`` Path to the custom configuration file
|
||||
========================================================== ====== =================================================================================================================================================================================================
|
||||
``--log-level LOG_LEVEL`` Level of messages to Display, can be DEBUG / INFO / WARNING / ERROR / CRITICAL
|
||||
========================================================== ====== =====================================================================================================================================================================================================
|
||||
|
||||
custom_config
|
||||
--------------
|
||||
|
|
|
@ -12,6 +12,7 @@ from manimlib.animation.fading import FadeOut
|
|||
from manimlib.animation.fading import FadeIn
|
||||
from manimlib.animation.movement import Homotopy
|
||||
from manimlib.animation.transform import Transform
|
||||
from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
|
||||
from manimlib.constants import ORIGIN, RIGHT, UP
|
||||
from manimlib.constants import SMALL_BUFF
|
||||
from manimlib.constants import TAU
|
||||
|
|
|
@ -176,9 +176,9 @@ class MoveToTarget(Transform):
|
|||
|
||||
|
||||
class _MethodAnimation(MoveToTarget):
|
||||
def __init__(self, mobject: Mobject, methods: Callable):
|
||||
def __init__(self, mobject: Mobject, methods: list[Callable], **kwargs):
|
||||
self.methods = methods
|
||||
super().__init__(mobject)
|
||||
super().__init__(mobject, **kwargs)
|
||||
|
||||
|
||||
class ApplyMethod(Transform):
|
||||
|
|
|
@ -167,81 +167,95 @@ class TransformMatchingStrings(AnimationGroup):
|
|||
digest_config(self, kwargs)
|
||||
assert isinstance(source, StringMobject)
|
||||
assert isinstance(target, StringMobject)
|
||||
anims = []
|
||||
source_indices = list(range(len(source.labels)))
|
||||
target_indices = list(range(len(target.labels)))
|
||||
|
||||
def get_filtered_indices_lists(indices_lists, rest_indices):
|
||||
def get_matched_indices_lists(*part_items_list):
|
||||
part_items_list_len = len(part_items_list)
|
||||
indexed_part_items = sorted(it.chain(*[
|
||||
[
|
||||
(substr, items_index, indices_list)
|
||||
for substr, indices_list in part_items
|
||||
]
|
||||
for items_index, part_items in enumerate(part_items_list)
|
||||
]))
|
||||
grouped_part_items = [
|
||||
(substr, [
|
||||
[indices_lists for _, _, indices_lists in grouper_2]
|
||||
for _, grouper_2 in it.groupby(
|
||||
grouper_1, key=lambda t: t[1]
|
||||
)
|
||||
])
|
||||
for substr, grouper_1 in it.groupby(
|
||||
indexed_part_items, key=lambda t: t[0]
|
||||
)
|
||||
]
|
||||
return [
|
||||
tuple(indices_lists_list)
|
||||
for _, indices_lists_list in sorted(filter(
|
||||
lambda t: t[0] and len(t[1]) == part_items_list_len,
|
||||
grouped_part_items
|
||||
), key=lambda t: len(t[0]), reverse=True)
|
||||
]
|
||||
|
||||
def get_filtered_indices_lists(indices_lists, used_indices):
|
||||
result = []
|
||||
used = []
|
||||
for indices_list in indices_lists:
|
||||
if not indices_list:
|
||||
continue
|
||||
if not all(index in rest_indices for index in indices_list):
|
||||
if not all(
|
||||
index not in used_indices and index not in used
|
||||
for index in indices_list
|
||||
):
|
||||
continue
|
||||
result.append(indices_list)
|
||||
for index in indices_list:
|
||||
rest_indices.remove(index)
|
||||
return result
|
||||
used.extend(indices_list)
|
||||
return result, used
|
||||
|
||||
def add_anims(anim_class, indices_lists_pairs):
|
||||
for source_indices_lists, target_indices_lists in indices_lists_pairs:
|
||||
source_indices_lists = get_filtered_indices_lists(
|
||||
source_indices_lists, source_indices
|
||||
)
|
||||
target_indices_lists = get_filtered_indices_lists(
|
||||
target_indices_lists, target_indices
|
||||
)
|
||||
if not source_indices_lists or not target_indices_lists:
|
||||
continue
|
||||
anims.append(anim_class(
|
||||
source.build_parts_from_indices_lists(source_indices_lists),
|
||||
target.build_parts_from_indices_lists(target_indices_lists),
|
||||
**kwargs
|
||||
))
|
||||
|
||||
def get_substr_to_indices_lists_map(part_items):
|
||||
result = {}
|
||||
for substr, indices_list in part_items:
|
||||
if substr not in result:
|
||||
result[substr] = []
|
||||
result[substr].append(indices_list)
|
||||
return result
|
||||
|
||||
def add_anims_from(anim_class, func):
|
||||
source_substr_map = get_substr_to_indices_lists_map(func(source))
|
||||
target_substr_map = get_substr_to_indices_lists_map(func(target))
|
||||
common_substrings = sorted([
|
||||
s for s in source_substr_map if s and s in target_substr_map
|
||||
], key=len, reverse=True)
|
||||
add_anims(
|
||||
anim_class,
|
||||
[
|
||||
(source_substr_map[substr], target_substr_map[substr])
|
||||
for substr in common_substrings
|
||||
]
|
||||
)
|
||||
|
||||
add_anims(
|
||||
ReplacementTransform,
|
||||
[
|
||||
anim_class_items = [
|
||||
(ReplacementTransform, [
|
||||
(
|
||||
source.get_submob_indices_lists_by_selector(k),
|
||||
target.get_submob_indices_lists_by_selector(v)
|
||||
)
|
||||
for k, v in self.key_map.items()
|
||||
]
|
||||
)
|
||||
add_anims_from(
|
||||
FadeTransformPieces,
|
||||
StringMobject.get_specified_part_items
|
||||
)
|
||||
add_anims_from(
|
||||
FadeTransformPieces,
|
||||
StringMobject.get_group_part_items
|
||||
)
|
||||
]),
|
||||
(FadeTransformPieces, get_matched_indices_lists(
|
||||
source.get_specified_part_items(),
|
||||
target.get_specified_part_items()
|
||||
)),
|
||||
(FadeTransformPieces, get_matched_indices_lists(
|
||||
source.get_group_part_items(),
|
||||
target.get_group_part_items()
|
||||
))
|
||||
]
|
||||
|
||||
rest_source = VGroup(*[source[index] for index in source_indices])
|
||||
rest_target = VGroup(*[target[index] for index in target_indices])
|
||||
anims = []
|
||||
source_used_indices = []
|
||||
target_used_indices = []
|
||||
for anim_class, pairs in anim_class_items:
|
||||
for source_indices_lists, target_indices_lists in pairs:
|
||||
source_filtered, source_used = get_filtered_indices_lists(
|
||||
source_indices_lists, source_used_indices
|
||||
)
|
||||
target_filtered, target_used = get_filtered_indices_lists(
|
||||
target_indices_lists, target_used_indices
|
||||
)
|
||||
if not source_filtered or not target_filtered:
|
||||
continue
|
||||
anims.append(anim_class(
|
||||
source.build_parts_from_indices_lists(source_filtered),
|
||||
target.build_parts_from_indices_lists(target_filtered),
|
||||
**kwargs
|
||||
))
|
||||
source_used_indices.extend(source_used)
|
||||
target_used_indices.extend(target_used)
|
||||
|
||||
rest_source = VGroup(*[
|
||||
submob for index, submob in enumerate(source.submobjects)
|
||||
if index not in source_used_indices
|
||||
])
|
||||
rest_target = VGroup(*[
|
||||
submob for index, submob in enumerate(target.submobjects)
|
||||
if index not in target_used_indices
|
||||
])
|
||||
if self.transform_mismatches:
|
||||
anims.append(
|
||||
ReplacementTransform(rest_source, rest_target, **kwargs)
|
||||
|
|
|
@ -16,17 +16,9 @@ directories:
|
|||
# return whatever is at to the TMPDIR environment variable. If you want to
|
||||
# specify them elsewhere,
|
||||
temporary_storage: ""
|
||||
tex:
|
||||
executable: "latex"
|
||||
template_file: "tex_template.tex"
|
||||
intermediate_filetype: "dvi"
|
||||
text_to_replace: "[tex_expression]"
|
||||
# For ctex, use the following configuration
|
||||
# executable: "xelatex -no-pdf"
|
||||
# template_file: "ctex_template.tex"
|
||||
# intermediate_filetype: "xdv"
|
||||
universal_import_line: "from manimlib import *"
|
||||
style:
|
||||
tex_template: "default"
|
||||
font: "Consolas"
|
||||
text_alignment: "LEFT"
|
||||
background_color: "#333333"
|
||||
|
@ -49,4 +41,4 @@ camera_resolutions:
|
|||
high: "1920x1080"
|
||||
4k: "3840x2160"
|
||||
default_resolution: "high"
|
||||
fps: 30
|
||||
fps: 30
|
||||
|
|
|
@ -8,7 +8,7 @@ import numpy as np
|
|||
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UL, UP, UR
|
||||
from manimlib.constants import GREY_A, RED, WHITE
|
||||
from manimlib.constants import MED_SMALL_BUFF
|
||||
from manimlib.constants import PI, TAU, DEGREES
|
||||
from manimlib.constants import DEGREES, PI, TAU
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.types.vectorized_mobject import DashedVMobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
|
@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from colour import Color
|
||||
from typing import Union
|
||||
from typing import Iterable, Union
|
||||
|
||||
ManimColor = Union[str, Color]
|
||||
|
||||
|
|
|
@ -2020,7 +2020,9 @@ class _AnimationBuilder:
|
|||
self.overridden_animation = None
|
||||
self.mobject.generate_target()
|
||||
self.is_chaining = False
|
||||
self.methods = []
|
||||
self.methods: list[Callable] = []
|
||||
self.anim_args = {}
|
||||
self.can_pass_args = True
|
||||
|
||||
def __getattr__(self, method_name: str):
|
||||
method = getattr(self.mobject.target, method_name)
|
||||
|
@ -2045,13 +2047,40 @@ class _AnimationBuilder:
|
|||
self.is_chaining = True
|
||||
return update_target
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
return self.set_anim_args(**kwargs)
|
||||
|
||||
def set_anim_args(self, **kwargs):
|
||||
'''
|
||||
You can change the args of :class:`~manimlib.animation.transform.Transform`, such as
|
||||
|
||||
- ``run_time``
|
||||
- ``time_span``
|
||||
- ``rate_func``
|
||||
- ``lag_ratio``
|
||||
- ``path_arc``
|
||||
- ``path_func``
|
||||
|
||||
and so on.
|
||||
'''
|
||||
|
||||
if not self.can_pass_args:
|
||||
raise ValueError(
|
||||
"Animation arguments can only be passed by calling ``animate`` "
|
||||
"or ``set_anim_args`` and can only be passed once",
|
||||
)
|
||||
|
||||
self.anim_args = kwargs
|
||||
self.can_pass_args = False
|
||||
return self
|
||||
|
||||
def build(self):
|
||||
from manimlib.animation.transform import _MethodAnimation
|
||||
|
||||
if self.overridden_animation:
|
||||
return self.overridden_animation
|
||||
|
||||
return _MethodAnimation(self.mobject, self.methods)
|
||||
return _MethodAnimation(self.mobject, self.methods, **self.anim_args)
|
||||
|
||||
|
||||
def override_animate(method):
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from manimlib.constants import BLUE, BLUE_E, GREEN_E, GREY_B, GREY_D, MAROON_B, YELLOW
|
||||
from manimlib.constants import DOWN, LEFT, RIGHT, UP
|
||||
from manimlib.constants import MED_LARGE_BUFF, MED_SMALL_BUFF, SMALL_BUFF
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from manimlib.mobject.svg.string_mobject import StringMobject
|
||||
from manimlib.utils.tex_file_writing import display_during_execution
|
||||
from manimlib.utils.tex_file_writing import get_tex_config
|
||||
from manimlib.utils.tex_file_writing import tex_to_svg_file
|
||||
from manimlib.utils.tex_file_writing import tex_content_to_svg_file
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
@ -37,6 +38,8 @@ class MTex(StringMobject):
|
|||
"alignment": "\\centering",
|
||||
"tex_environment": "align*",
|
||||
"tex_to_color_map": {},
|
||||
"template": "",
|
||||
"additional_preamble": "",
|
||||
}
|
||||
|
||||
def __init__(self, tex_string: str, **kwargs):
|
||||
|
@ -57,77 +60,112 @@ class MTex(StringMobject):
|
|||
self.path_string_config,
|
||||
self.base_color,
|
||||
self.isolate,
|
||||
self.protect,
|
||||
self.tex_string,
|
||||
self.alignment,
|
||||
self.tex_environment,
|
||||
self.tex_to_color_map
|
||||
self.tex_to_color_map,
|
||||
self.template,
|
||||
self.additional_preamble
|
||||
)
|
||||
|
||||
def get_file_path_by_content(self, content: str) -> str:
|
||||
tex_config = get_tex_config()
|
||||
full_tex = tex_config["tex_body"].replace(
|
||||
tex_config["text_to_replace"],
|
||||
content
|
||||
)
|
||||
with display_during_execution(f"Writing \"{self.string}\""):
|
||||
file_path = tex_to_svg_file(full_tex)
|
||||
with display_during_execution(f"Writing \"{self.tex_string}\""):
|
||||
file_path = tex_content_to_svg_file(
|
||||
content, self.template, self.additional_preamble
|
||||
)
|
||||
return file_path
|
||||
|
||||
# Parsing
|
||||
|
||||
def get_cmd_spans(self) -> list[Span]:
|
||||
return self.find_spans(r"\\(?:[a-zA-Z]+|\s|\S)|[_^{}]")
|
||||
|
||||
def get_substr_flag(self, substr: str) -> int:
|
||||
return {"{": 1, "}": -1}.get(substr, 0)
|
||||
|
||||
def get_repl_substr_for_content(self, substr: str) -> str:
|
||||
return substr
|
||||
|
||||
def get_repl_substr_for_matching(self, substr: str) -> str:
|
||||
return substr if substr.startswith("\\") else ""
|
||||
|
||||
def get_specified_items(
|
||||
self, cmd_span_pairs: list[tuple[Span, Span]]
|
||||
) -> list[tuple[Span, dict[str, str]]]:
|
||||
cmd_content_spans = [
|
||||
(span_begin, span_end)
|
||||
for (_, span_begin), (span_end, _) in cmd_span_pairs
|
||||
]
|
||||
specified_spans = [
|
||||
*[
|
||||
cmd_content_spans[range_begin]
|
||||
for _, (range_begin, range_end) in self.compress_neighbours([
|
||||
(span_begin + index, span_end - index)
|
||||
for index, (span_begin, span_end) in enumerate(
|
||||
cmd_content_spans
|
||||
)
|
||||
])
|
||||
if range_end - range_begin >= 2
|
||||
],
|
||||
*[
|
||||
span
|
||||
for selector in self.tex_to_color_map
|
||||
for span in self.find_spans_by_selector(selector)
|
||||
],
|
||||
*self.find_spans_by_selector(self.isolate)
|
||||
]
|
||||
return [(span, {}) for span in specified_spans]
|
||||
@staticmethod
|
||||
def get_command_matches(string: str) -> list[re.Match]:
|
||||
# Lump together adjacent brace pairs
|
||||
pattern = re.compile(r"""
|
||||
(?P<command>\\(?:[a-zA-Z]+|.))
|
||||
|(?P<open>{+)
|
||||
|(?P<close>}+)
|
||||
""", flags=re.X | re.S)
|
||||
result = []
|
||||
open_stack = []
|
||||
for match_obj in pattern.finditer(string):
|
||||
if match_obj.group("open"):
|
||||
open_stack.append((match_obj.span(), len(result)))
|
||||
elif match_obj.group("close"):
|
||||
close_start, close_end = match_obj.span()
|
||||
while True:
|
||||
if not open_stack:
|
||||
raise ValueError("Missing '{' inserted")
|
||||
(open_start, open_end), index = open_stack.pop()
|
||||
n = min(open_end - open_start, close_end - close_start)
|
||||
result.insert(index, pattern.fullmatch(
|
||||
string, pos=open_end - n, endpos=open_end
|
||||
))
|
||||
result.append(pattern.fullmatch(
|
||||
string, pos=close_start, endpos=close_start + n
|
||||
))
|
||||
close_start += n
|
||||
if close_start < close_end:
|
||||
continue
|
||||
open_end -= n
|
||||
if open_start < open_end:
|
||||
open_stack.append(((open_start, open_end), index))
|
||||
break
|
||||
else:
|
||||
result.append(match_obj)
|
||||
if open_stack:
|
||||
raise ValueError("Missing '}' inserted")
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_color_cmd_str(rgb_hex: str) -> str:
|
||||
def get_command_flag(match_obj: re.Match) -> int:
|
||||
if match_obj.group("open"):
|
||||
return 1
|
||||
if match_obj.group("close"):
|
||||
return -1
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def replace_for_content(match_obj: re.Match) -> str:
|
||||
return match_obj.group()
|
||||
|
||||
@staticmethod
|
||||
def replace_for_matching(match_obj: re.Match) -> str:
|
||||
if match_obj.group("command"):
|
||||
return match_obj.group()
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def get_attr_dict_from_command_pair(
|
||||
open_command: re.Match, close_command: re.Match
|
||||
) -> dict[str, str] | None:
|
||||
if len(open_command.group()) >= 2:
|
||||
return {}
|
||||
return None
|
||||
|
||||
def get_configured_items(self) -> list[tuple[Span, dict[str, str]]]:
|
||||
return [
|
||||
(span, {})
|
||||
for selector in self.tex_to_color_map
|
||||
for span in self.find_spans_by_selector(selector)
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_color_command(rgb_hex: str) -> str:
|
||||
rgb = MTex.hex_to_int(rgb_hex)
|
||||
rg, b = divmod(rgb, 256)
|
||||
r, g = divmod(rg, 256)
|
||||
return f"\\color[RGB]{{{r}, {g}, {b}}}"
|
||||
|
||||
@staticmethod
|
||||
def get_cmd_str_pair(
|
||||
attr_dict: dict[str, str], label_hex: str | None
|
||||
) -> tuple[str, str]:
|
||||
def get_command_string(
|
||||
attr_dict: dict[str, str], is_end: bool, label_hex: str | None
|
||||
) -> str:
|
||||
if label_hex is None:
|
||||
return "", ""
|
||||
return "{{" + MTex.get_color_cmd_str(label_hex), "}}"
|
||||
return ""
|
||||
if is_end:
|
||||
return "}}"
|
||||
return "{{" + MTex.get_color_command(label_hex)
|
||||
|
||||
def get_content_prefix_and_suffix(
|
||||
self, is_labelled: bool
|
||||
|
@ -135,17 +173,14 @@ class MTex(StringMobject):
|
|||
prefix_lines = []
|
||||
suffix_lines = []
|
||||
if not is_labelled:
|
||||
prefix_lines.append(self.get_color_cmd_str(self.base_color_hex))
|
||||
prefix_lines.append(self.get_color_command(
|
||||
self.color_to_hex(self.base_color)
|
||||
))
|
||||
if self.alignment:
|
||||
prefix_lines.append(self.alignment)
|
||||
if self.tex_environment:
|
||||
if isinstance(self.tex_environment, str):
|
||||
env_prefix = f"\\begin{{{self.tex_environment}}}"
|
||||
env_suffix = f"\\end{{{self.tex_environment}}}"
|
||||
else:
|
||||
env_prefix, env_suffix = self.tex_environment
|
||||
prefix_lines.append(env_prefix)
|
||||
suffix_lines.append(env_suffix)
|
||||
prefix_lines.append(f"\\begin{{{self.tex_environment}}}")
|
||||
suffix_lines.append(f"\\end{{{self.tex_environment}}}")
|
||||
return (
|
||||
"".join([line + "\n" for line in prefix_lines]),
|
||||
"".join(["\n" + line for line in suffix_lines])
|
||||
|
@ -156,8 +191,8 @@ class MTex(StringMobject):
|
|||
def get_parts_by_tex(self, selector: Selector) -> VGroup:
|
||||
return self.select_parts(selector)
|
||||
|
||||
def get_part_by_tex(self, selector: Selector) -> VGroup:
|
||||
return self.select_part(selector)
|
||||
def get_part_by_tex(self, selector: Selector, **kwargs) -> VGroup:
|
||||
return self.select_part(selector, **kwargs)
|
||||
|
||||
def set_color_by_tex(self, selector: Selector, color: ManimColor):
|
||||
return self.set_parts_color(selector, color)
|
||||
|
|
|
@ -18,7 +18,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from colour import Color
|
||||
from typing import Iterable, Sequence, TypeVar, Union
|
||||
from typing import Callable, Iterable, Union
|
||||
|
||||
ManimColor = Union[str, Color]
|
||||
Span = tuple[int, int]
|
||||
|
@ -32,7 +32,6 @@ if TYPE_CHECKING:
|
|||
tuple[Union[int, None], Union[int, None]]
|
||||
]]
|
||||
]
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class StringMobject(SVGMobject, ABC):
|
||||
|
@ -47,7 +46,7 @@ class StringMobject(SVGMobject, ABC):
|
|||
if they want to do anything with their corresponding submobjects.
|
||||
`isolate` parameter can be either a string, a `re.Pattern` object,
|
||||
or a 2-tuple containing integers or None, or a collection of the above.
|
||||
Note, substrings specified cannot *partially* overlap with each other.
|
||||
Note, substrings specified cannot *partly* overlap with each other.
|
||||
|
||||
Each instance of `StringMobject` generates 2 svg files.
|
||||
The additional one is generated with some color commands inserted,
|
||||
|
@ -64,6 +63,7 @@ class StringMobject(SVGMobject, ABC):
|
|||
},
|
||||
"base_color": WHITE,
|
||||
"isolate": (),
|
||||
"protect": (),
|
||||
}
|
||||
|
||||
def __init__(self, string: str, **kwargs):
|
||||
|
@ -71,9 +71,7 @@ class StringMobject(SVGMobject, ABC):
|
|||
digest_config(self, kwargs)
|
||||
if self.base_color is None:
|
||||
self.base_color = WHITE
|
||||
self.base_color_hex = self.color_to_hex(self.base_color)
|
||||
|
||||
self.full_span = (0, len(self.string))
|
||||
self.parse()
|
||||
super().__init__(**kwargs)
|
||||
self.labels = [submob.label for submob in self.submobjects]
|
||||
|
@ -90,9 +88,9 @@ class StringMobject(SVGMobject, ABC):
|
|||
super().generate_mobject()
|
||||
|
||||
labels_count = len(self.labelled_spans)
|
||||
if not labels_count:
|
||||
if labels_count == 1:
|
||||
for submob in self.submobjects:
|
||||
submob.label = -1
|
||||
submob.label = 0
|
||||
return
|
||||
|
||||
labelled_content = self.get_content(is_labelled=True)
|
||||
|
@ -104,7 +102,7 @@ class StringMobject(SVGMobject, ABC):
|
|||
"to the original svg. Skip the labelling process."
|
||||
)
|
||||
for submob in self.submobjects:
|
||||
submob.label = -1
|
||||
submob.label = 0
|
||||
return
|
||||
|
||||
self.rearrange_submobjects_by_positions(labelled_svg)
|
||||
|
@ -112,18 +110,21 @@ class StringMobject(SVGMobject, ABC):
|
|||
for submob, labelled_svg_submob in zip(
|
||||
self.submobjects, labelled_svg.submobjects
|
||||
):
|
||||
color_int = self.hex_to_int(self.color_to_hex(
|
||||
label = self.hex_to_int(self.color_to_hex(
|
||||
labelled_svg_submob.get_fill_color()
|
||||
))
|
||||
if color_int > labels_count:
|
||||
unrecognizable_colors.append(color_int)
|
||||
color_int = 0
|
||||
submob.label = color_int - 1
|
||||
if label >= labels_count:
|
||||
unrecognizable_colors.append(label)
|
||||
label = 0
|
||||
submob.label = label
|
||||
if unrecognizable_colors:
|
||||
log.warning(
|
||||
"Unrecognizable color labels detected (%s, etc). "
|
||||
"Unrecognizable color labels detected (%s). "
|
||||
"The result could be unexpected.",
|
||||
self.int_to_hex(unrecognizable_colors[0])
|
||||
", ".join(
|
||||
self.int_to_hex(color)
|
||||
for color in unrecognizable_colors
|
||||
)
|
||||
)
|
||||
|
||||
def rearrange_submobjects_by_positions(
|
||||
|
@ -153,30 +154,27 @@ class StringMobject(SVGMobject, ABC):
|
|||
|
||||
# Toolkits
|
||||
|
||||
def get_substr(self, span: Span) -> str:
|
||||
return self.string[slice(*span)]
|
||||
|
||||
def find_spans(self, pattern: str | re.Pattern) -> list[Span]:
|
||||
return [
|
||||
match_obj.span()
|
||||
for match_obj in re.finditer(pattern, self.string)
|
||||
]
|
||||
|
||||
def find_spans_by_selector(self, selector: Selector) -> list[Span]:
|
||||
def find_spans_by_single_selector(sel):
|
||||
if isinstance(sel, str):
|
||||
return self.find_spans(re.escape(sel))
|
||||
return [
|
||||
match_obj.span()
|
||||
for match_obj in re.finditer(re.escape(sel), self.string)
|
||||
]
|
||||
if isinstance(sel, re.Pattern):
|
||||
return self.find_spans(sel)
|
||||
return [
|
||||
match_obj.span()
|
||||
for match_obj in sel.finditer(self.string)
|
||||
]
|
||||
if isinstance(sel, tuple) and len(sel) == 2 and all(
|
||||
isinstance(index, int) or index is None
|
||||
for index in sel
|
||||
):
|
||||
l = self.full_span[1]
|
||||
l = len(self.string)
|
||||
span = tuple(
|
||||
default_index if index is None else
|
||||
min(index, l) if index >= 0 else max(index + l, 0)
|
||||
if index is not None else default_index
|
||||
for index, default_index in zip(sel, self.full_span)
|
||||
for index, default_index in zip(sel, (0, l))
|
||||
)
|
||||
return [span]
|
||||
return None
|
||||
|
@ -189,57 +187,12 @@ class StringMobject(SVGMobject, ABC):
|
|||
if spans is None:
|
||||
raise TypeError(f"Invalid selector: '{sel}'")
|
||||
result.extend(spans)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_neighbouring_pairs(vals: Sequence[T]) -> list[tuple[T, T]]:
|
||||
return list(zip(vals[:-1], vals[1:]))
|
||||
|
||||
@staticmethod
|
||||
def compress_neighbours(vals: Sequence[T]) -> list[tuple[T, Span]]:
|
||||
if not vals:
|
||||
return []
|
||||
|
||||
unique_vals = [vals[0]]
|
||||
indices = [0]
|
||||
for index, val in enumerate(vals):
|
||||
if val == unique_vals[-1]:
|
||||
continue
|
||||
unique_vals.append(val)
|
||||
indices.append(index)
|
||||
indices.append(len(vals))
|
||||
val_ranges = StringMobject.get_neighbouring_pairs(indices)
|
||||
return list(zip(unique_vals, val_ranges))
|
||||
return list(filter(lambda span: span[0] <= span[1], result))
|
||||
|
||||
@staticmethod
|
||||
def span_contains(span_0: Span, span_1: Span) -> bool:
|
||||
return span_0[0] <= span_1[0] and span_0[1] >= span_1[1]
|
||||
|
||||
@staticmethod
|
||||
def get_complement_spans(
|
||||
universal_span: Span, interval_spans: list[Span]
|
||||
) -> list[Span]:
|
||||
if not interval_spans:
|
||||
return [universal_span]
|
||||
|
||||
span_ends, span_begins = zip(*interval_spans)
|
||||
return list(zip(
|
||||
(universal_span[0], *span_begins),
|
||||
(*span_ends, universal_span[1])
|
||||
))
|
||||
|
||||
def replace_substr(self, span: Span, repl_items: list[Span, str]):
|
||||
if not repl_items:
|
||||
return self.get_substr(span)
|
||||
|
||||
repl_spans, repl_strs = zip(*sorted(repl_items, key=lambda t: t[0]))
|
||||
pieces = [
|
||||
self.get_substr(piece_span)
|
||||
for piece_span in self.get_complement_spans(span, repl_spans)
|
||||
]
|
||||
repl_strs = [*repl_strs, ""]
|
||||
return "".join(it.chain(*zip(pieces, repl_strs)))
|
||||
|
||||
@staticmethod
|
||||
def color_to_hex(color: ManimColor) -> str:
|
||||
return rgb_to_hex(color_to_rgb(color))
|
||||
|
@ -255,131 +208,220 @@ class StringMobject(SVGMobject, ABC):
|
|||
# Parsing
|
||||
|
||||
def parse(self) -> None:
|
||||
cmd_spans = self.get_cmd_spans()
|
||||
cmd_substrs = [self.get_substr(span) for span in cmd_spans]
|
||||
flags = [self.get_substr_flag(substr) for substr in cmd_substrs]
|
||||
specified_items = self.get_specified_items(
|
||||
self.get_cmd_span_pairs(cmd_spans, flags)
|
||||
)
|
||||
split_items = [
|
||||
(span, attr_dict)
|
||||
for specified_span, attr_dict in specified_items
|
||||
for span in self.split_span_by_levels(
|
||||
specified_span, cmd_spans, flags
|
||||
def get_substr(span: Span) -> str:
|
||||
return self.string[slice(*span)]
|
||||
|
||||
configured_items = self.get_configured_items()
|
||||
isolated_spans = self.find_spans_by_selector(self.isolate)
|
||||
protected_spans = self.find_spans_by_selector(self.protect)
|
||||
command_matches = self.get_command_matches(self.string)
|
||||
|
||||
def get_key(category, i, flag):
|
||||
def get_span_by_category(category, i):
|
||||
if category == 0:
|
||||
return configured_items[i][0]
|
||||
if category == 1:
|
||||
return isolated_spans[i]
|
||||
if category == 2:
|
||||
return protected_spans[i]
|
||||
return command_matches[i].span()
|
||||
|
||||
index, paired_index = get_span_by_category(category, i)[::flag]
|
||||
return (
|
||||
index,
|
||||
flag * (2 if index != paired_index else -1),
|
||||
-paired_index,
|
||||
flag * category,
|
||||
flag * i
|
||||
)
|
||||
]
|
||||
|
||||
self.specified_spans = [span for span, _ in specified_items]
|
||||
self.split_items = split_items
|
||||
self.labelled_spans = [span for span, _ in split_items]
|
||||
self.cmd_repl_items_for_content = [
|
||||
(span, self.get_repl_substr_for_content(substr))
|
||||
for span, substr in zip(cmd_spans, cmd_substrs)
|
||||
]
|
||||
self.cmd_repl_items_for_matching = [
|
||||
(span, self.get_repl_substr_for_matching(substr))
|
||||
for span, substr in zip(cmd_spans, cmd_substrs)
|
||||
]
|
||||
self.check_overlapping()
|
||||
index_items = sorted([
|
||||
(category, i, flag)
|
||||
for category, item_length in enumerate((
|
||||
len(configured_items),
|
||||
len(isolated_spans),
|
||||
len(protected_spans),
|
||||
len(command_matches)
|
||||
))
|
||||
for i in range(item_length)
|
||||
for flag in (1, -1)
|
||||
], key=lambda t: get_key(*t))
|
||||
|
||||
inserted_items = []
|
||||
labelled_items = []
|
||||
overlapping_spans = []
|
||||
level_mismatched_spans = []
|
||||
|
||||
label = 1
|
||||
protect_level = 0
|
||||
bracket_stack = [0]
|
||||
bracket_count = 0
|
||||
open_command_stack = []
|
||||
open_stack = []
|
||||
for category, i, flag in index_items:
|
||||
if category >= 2:
|
||||
protect_level += flag
|
||||
if flag == 1 or category == 2:
|
||||
continue
|
||||
inserted_items.append((i, 0))
|
||||
command_match = command_matches[i]
|
||||
command_flag = self.get_command_flag(command_match)
|
||||
if command_flag == 1:
|
||||
bracket_count += 1
|
||||
bracket_stack.append(bracket_count)
|
||||
open_command_stack.append((len(inserted_items), i))
|
||||
continue
|
||||
if command_flag == 0:
|
||||
continue
|
||||
pos, i_ = open_command_stack.pop()
|
||||
bracket_stack.pop()
|
||||
open_command_match = command_matches[i_]
|
||||
attr_dict = self.get_attr_dict_from_command_pair(
|
||||
open_command_match, command_match
|
||||
)
|
||||
if attr_dict is None:
|
||||
continue
|
||||
span = (open_command_match.end(), command_match.start())
|
||||
labelled_items.append((span, attr_dict))
|
||||
inserted_items.insert(pos, (label, 1))
|
||||
inserted_items.insert(-1, (label, -1))
|
||||
label += 1
|
||||
continue
|
||||
if flag == 1:
|
||||
open_stack.append((
|
||||
len(inserted_items), category, i,
|
||||
protect_level, bracket_stack.copy()
|
||||
))
|
||||
continue
|
||||
span, attr_dict = configured_items[i] \
|
||||
if category == 0 else (isolated_spans[i], {})
|
||||
pos, category_, i_, protect_level_, bracket_stack_ \
|
||||
= open_stack.pop()
|
||||
if category_ != category or i_ != i:
|
||||
overlapping_spans.append(span)
|
||||
continue
|
||||
if protect_level_ or protect_level:
|
||||
continue
|
||||
if bracket_stack_ != bracket_stack:
|
||||
level_mismatched_spans.append(span)
|
||||
continue
|
||||
labelled_items.append((span, attr_dict))
|
||||
inserted_items.insert(pos, (label, 1))
|
||||
inserted_items.append((label, -1))
|
||||
label += 1
|
||||
labelled_items.insert(0, ((0, len(self.string)), {}))
|
||||
inserted_items.insert(0, (0, 1))
|
||||
inserted_items.append((0, -1))
|
||||
|
||||
if overlapping_spans:
|
||||
log.warning(
|
||||
"Partly overlapping substrings detected: %s",
|
||||
", ".join(
|
||||
f"'{get_substr(span)}'"
|
||||
for span in overlapping_spans
|
||||
)
|
||||
)
|
||||
if level_mismatched_spans:
|
||||
log.warning(
|
||||
"Cannot handle substrings: %s",
|
||||
", ".join(
|
||||
f"'{get_substr(span)}'"
|
||||
for span in level_mismatched_spans
|
||||
)
|
||||
)
|
||||
|
||||
def reconstruct_string(
|
||||
start_item: tuple[int, int],
|
||||
end_item: tuple[int, int],
|
||||
command_replace_func: Callable[[re.Match], str],
|
||||
command_insert_func: Callable[[int, int, dict[str, str]], str]
|
||||
) -> str:
|
||||
def get_edge_item(i: int, flag: int) -> tuple[Span, str]:
|
||||
if flag == 0:
|
||||
match_obj = command_matches[i]
|
||||
return (
|
||||
match_obj.span(),
|
||||
command_replace_func(match_obj)
|
||||
)
|
||||
span, attr_dict = labelled_items[i]
|
||||
index = span[flag < 0]
|
||||
return (
|
||||
(index, index),
|
||||
command_insert_func(i, flag, attr_dict)
|
||||
)
|
||||
|
||||
items = [
|
||||
get_edge_item(i, flag)
|
||||
for i, flag in inserted_items[slice(
|
||||
inserted_items.index(start_item),
|
||||
inserted_items.index(end_item) + 1
|
||||
)]
|
||||
]
|
||||
pieces = [
|
||||
get_substr((start, end))
|
||||
for start, end in zip(
|
||||
[interval_end for (_, interval_end), _ in items[:-1]],
|
||||
[interval_start for (interval_start, _), _ in items[1:]]
|
||||
)
|
||||
]
|
||||
interval_pieces = [piece for _, piece in items[1:-1]]
|
||||
return "".join(it.chain(*zip(pieces, (*interval_pieces, ""))))
|
||||
|
||||
self.labelled_spans = [span for span, _ in labelled_items]
|
||||
self.reconstruct_string = reconstruct_string
|
||||
|
||||
def get_content(self, is_labelled: bool) -> str:
|
||||
content = self.reconstruct_string(
|
||||
(0, 1), (0, -1),
|
||||
self.replace_for_content,
|
||||
lambda label, flag, attr_dict: self.get_command_string(
|
||||
attr_dict,
|
||||
is_end=flag < 0,
|
||||
label_hex=self.int_to_hex(label) if is_labelled else None
|
||||
)
|
||||
)
|
||||
prefix, suffix = self.get_content_prefix_and_suffix(
|
||||
is_labelled=is_labelled
|
||||
)
|
||||
return "".join((prefix, content, suffix))
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_cmd_spans(self) -> list[Span]:
|
||||
def get_command_matches(string: str) -> list[re.Match]:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_substr_flag(self, substr: str) -> int:
|
||||
def get_command_flag(match_obj: re.Match) -> int:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_repl_substr_for_content(self, substr: str) -> str:
|
||||
return ""
|
||||
|
||||
@abstractmethod
|
||||
def get_repl_substr_for_matching(self, substr: str) -> str:
|
||||
def replace_for_content(match_obj: re.Match) -> str:
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def get_cmd_span_pairs(
|
||||
cmd_spans: list[Span], flags: list[int]
|
||||
) -> list[tuple[Span, Span]]:
|
||||
result = []
|
||||
begin_cmd_spans_stack = []
|
||||
for cmd_span, flag in zip(cmd_spans, flags):
|
||||
if flag == 1:
|
||||
begin_cmd_spans_stack.append(cmd_span)
|
||||
elif flag == -1:
|
||||
if not begin_cmd_spans_stack:
|
||||
raise ValueError("Missing open command")
|
||||
begin_cmd_span = begin_cmd_spans_stack.pop()
|
||||
result.append((begin_cmd_span, cmd_span))
|
||||
if begin_cmd_spans_stack:
|
||||
raise ValueError("Missing close command")
|
||||
return result
|
||||
@abstractmethod
|
||||
def replace_for_matching(match_obj: re.Match) -> str:
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_attr_dict_from_command_pair(
|
||||
open_command: re.Match, close_command: re.Match,
|
||||
) -> dict[str, str] | None:
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
def get_specified_items(
|
||||
self, cmd_span_pairs: list[tuple[Span, Span]]
|
||||
) -> list[tuple[Span, dict[str, str]]]:
|
||||
def get_configured_items(self) -> list[tuple[Span, dict[str, str]]]:
|
||||
return []
|
||||
|
||||
def split_span_by_levels(
|
||||
self, arbitrary_span: Span, cmd_spans: list[Span], flags: list[int]
|
||||
) -> list[Span]:
|
||||
cmd_range = (
|
||||
sum([
|
||||
arbitrary_span[0] > interval_begin
|
||||
for interval_begin, _ in cmd_spans
|
||||
]),
|
||||
sum([
|
||||
arbitrary_span[1] >= interval_end
|
||||
for _, interval_end in cmd_spans
|
||||
])
|
||||
)
|
||||
complement_spans = self.get_complement_spans(
|
||||
self.full_span, cmd_spans
|
||||
)
|
||||
adjusted_span = (
|
||||
max(arbitrary_span[0], complement_spans[cmd_range[0]][0]),
|
||||
min(arbitrary_span[1], complement_spans[cmd_range[1]][1])
|
||||
)
|
||||
if adjusted_span[0] > adjusted_span[1]:
|
||||
return []
|
||||
|
||||
upward_cmd_spans = []
|
||||
downward_cmd_spans = []
|
||||
for cmd_span, flag in list(zip(cmd_spans, flags))[slice(*cmd_range)]:
|
||||
if flag == 1:
|
||||
upward_cmd_spans.append(cmd_span)
|
||||
elif flag == -1:
|
||||
if upward_cmd_spans:
|
||||
upward_cmd_spans.pop()
|
||||
else:
|
||||
downward_cmd_spans.append(cmd_span)
|
||||
return list(filter(
|
||||
lambda span: self.get_substr(span).strip(),
|
||||
self.get_complement_spans(
|
||||
adjusted_span, downward_cmd_spans + upward_cmd_spans
|
||||
)
|
||||
))
|
||||
|
||||
def check_overlapping(self) -> None:
|
||||
labelled_spans = self.labelled_spans
|
||||
if len(labelled_spans) >= 16777216:
|
||||
raise ValueError("Cannot handle that many substrings")
|
||||
for span_0, span_1 in it.product(labelled_spans, repeat=2):
|
||||
if not span_0[0] < span_1[0] < span_0[1] < span_1[1]:
|
||||
continue
|
||||
raise ValueError(
|
||||
"Partially overlapping substrings detected: "
|
||||
f"'{self.get_substr(span_0)}' and '{self.get_substr(span_1)}'"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_cmd_str_pair(
|
||||
attr_dict: dict[str, str], label_hex: str | None
|
||||
) -> tuple[str, str]:
|
||||
return "", ""
|
||||
def get_command_string(
|
||||
attr_dict: dict[str, str], is_end: bool, label_hex: str | None
|
||||
) -> str:
|
||||
return ""
|
||||
|
||||
@abstractmethod
|
||||
def get_content_prefix_and_suffix(
|
||||
|
@ -387,38 +429,6 @@ class StringMobject(SVGMobject, ABC):
|
|||
) -> tuple[str, str]:
|
||||
return "", ""
|
||||
|
||||
def get_content(self, is_labelled: bool) -> str:
|
||||
inserted_str_pairs = [
|
||||
(span, self.get_cmd_str_pair(
|
||||
attr_dict,
|
||||
label_hex=self.int_to_hex(label + 1) if is_labelled else None
|
||||
))
|
||||
for label, (span, attr_dict) in enumerate(self.split_items)
|
||||
]
|
||||
inserted_str_items = sorted([
|
||||
(index, s)
|
||||
for (index, _), s in [
|
||||
*sorted([
|
||||
(span[::-1], end_str)
|
||||
for span, (_, end_str) in reversed(inserted_str_pairs)
|
||||
], key=lambda t: (t[0][0], -t[0][1])),
|
||||
*sorted([
|
||||
(span, begin_str)
|
||||
for span, (begin_str, _) in inserted_str_pairs
|
||||
], key=lambda t: (t[0][0], -t[0][1]))
|
||||
]
|
||||
], key=lambda t: t[0])
|
||||
repl_items = self.cmd_repl_items_for_content + [
|
||||
((index, index), inserted_str)
|
||||
for index, inserted_str in inserted_str_items
|
||||
]
|
||||
prefix, suffix = self.get_content_prefix_and_suffix(is_labelled)
|
||||
return "".join([
|
||||
prefix,
|
||||
self.replace_substr(self.full_span, repl_items),
|
||||
suffix
|
||||
])
|
||||
|
||||
# Selector
|
||||
|
||||
def get_submob_indices_list_by_span(
|
||||
|
@ -427,59 +437,69 @@ class StringMobject(SVGMobject, ABC):
|
|||
return [
|
||||
submob_index
|
||||
for submob_index, label in enumerate(self.labels)
|
||||
if label != -1 and self.span_contains(
|
||||
arbitrary_span, self.labelled_spans[label]
|
||||
)
|
||||
if self.span_contains(arbitrary_span, self.labelled_spans[label])
|
||||
]
|
||||
|
||||
def get_specified_part_items(self) -> list[tuple[str, list[int]]]:
|
||||
return [
|
||||
(
|
||||
self.get_substr(span),
|
||||
self.string[slice(*span)],
|
||||
self.get_submob_indices_list_by_span(span)
|
||||
)
|
||||
for span in self.specified_spans
|
||||
for span in self.labelled_spans[1:]
|
||||
]
|
||||
|
||||
def get_group_part_items(self) -> list[tuple[str, list[int]]]:
|
||||
if not self.labels:
|
||||
return []
|
||||
|
||||
group_labels, labelled_submob_ranges = zip(
|
||||
*self.compress_neighbours(self.labels)
|
||||
)
|
||||
ordered_spans = [
|
||||
self.labelled_spans[label] if label != -1 else self.full_span
|
||||
for label in group_labels
|
||||
]
|
||||
interval_spans = [
|
||||
(
|
||||
next_span[0]
|
||||
if self.span_contains(prev_span, next_span)
|
||||
else prev_span[1],
|
||||
prev_span[1]
|
||||
if self.span_contains(next_span, prev_span)
|
||||
else next_span[0]
|
||||
)
|
||||
for prev_span, next_span in self.get_neighbouring_pairs(
|
||||
ordered_spans
|
||||
)
|
||||
]
|
||||
group_substrs = [
|
||||
re.sub(r"\s+", "", self.replace_substr(
|
||||
span, [
|
||||
(cmd_span, repl_str)
|
||||
for cmd_span, repl_str in self.cmd_repl_items_for_matching
|
||||
if self.span_contains(span, cmd_span)
|
||||
]
|
||||
))
|
||||
for span in self.get_complement_spans(
|
||||
(ordered_spans[0][0], ordered_spans[-1][1]), interval_spans
|
||||
)
|
||||
]
|
||||
def get_neighbouring_pairs(vals):
|
||||
return list(zip(vals[:-1], vals[1:]))
|
||||
|
||||
range_lens, group_labels = zip(*(
|
||||
(len(list(grouper)), val)
|
||||
for val, grouper in it.groupby(self.labels)
|
||||
))
|
||||
submob_indices_lists = [
|
||||
list(range(*submob_range))
|
||||
for submob_range in labelled_submob_ranges
|
||||
for submob_range in get_neighbouring_pairs(
|
||||
[0, *it.accumulate(range_lens)]
|
||||
)
|
||||
]
|
||||
labelled_spans = self.labelled_spans
|
||||
start_items = [
|
||||
(group_labels[0], 1),
|
||||
*(
|
||||
(curr_label, 1)
|
||||
if self.span_contains(
|
||||
labelled_spans[prev_label], labelled_spans[curr_label]
|
||||
)
|
||||
else (prev_label, -1)
|
||||
for prev_label, curr_label in get_neighbouring_pairs(
|
||||
group_labels
|
||||
)
|
||||
)
|
||||
]
|
||||
end_items = [
|
||||
*(
|
||||
(curr_label, -1)
|
||||
if self.span_contains(
|
||||
labelled_spans[next_label], labelled_spans[curr_label]
|
||||
)
|
||||
else (next_label, 1)
|
||||
for curr_label, next_label in get_neighbouring_pairs(
|
||||
group_labels
|
||||
)
|
||||
),
|
||||
(group_labels[-1], -1)
|
||||
]
|
||||
group_substrs = [
|
||||
re.sub(r"\s+", "", self.reconstruct_string(
|
||||
start_item, end_item,
|
||||
self.replace_for_matching,
|
||||
lambda label, flag, attr_dict: ""
|
||||
))
|
||||
for start_item, end_item in zip(start_items, end_items)
|
||||
]
|
||||
return list(zip(group_substrs, submob_indices_lists))
|
||||
|
||||
|
@ -497,13 +517,13 @@ class StringMobject(SVGMobject, ABC):
|
|||
def build_parts_from_indices_lists(
|
||||
self, indices_lists: list[list[int]]
|
||||
) -> VGroup:
|
||||
return VGroup(*[
|
||||
VGroup(*[
|
||||
return VGroup(*(
|
||||
VGroup(*(
|
||||
self.submobjects[submob_index]
|
||||
for submob_index in indices_list
|
||||
])
|
||||
))
|
||||
for indices_list in indices_lists
|
||||
])
|
||||
))
|
||||
|
||||
def build_groups(self) -> VGroup:
|
||||
return self.build_parts_from_indices_lists([
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
|
@ -19,6 +18,7 @@ from manimlib.mobject.types.vectorized_mobject import VMobject
|
|||
from manimlib.utils.directories import get_mobject_data_dir
|
||||
from manimlib.utils.images import get_full_vector_image_path
|
||||
from manimlib.utils.iterables import hash_obj
|
||||
from manimlib.utils.simple_functions import hash_string
|
||||
|
||||
|
||||
SVG_HASH_TO_MOB_MAP: dict[int, VMobject] = {}
|
||||
|
@ -106,7 +106,7 @@ class SVGMobject(VMobject):
|
|||
return get_full_vector_image_path(self.file_name)
|
||||
|
||||
def modify_xml_tree(self, element_tree: ET.ElementTree) -> ET.ElementTree:
|
||||
config_style_dict = self.generate_config_style_dict()
|
||||
config_style_attrs = self.generate_config_style_dict()
|
||||
style_keys = (
|
||||
"fill",
|
||||
"fill-opacity",
|
||||
|
@ -116,14 +116,17 @@ class SVGMobject(VMobject):
|
|||
"style"
|
||||
)
|
||||
root = element_tree.getroot()
|
||||
root_style_dict = {
|
||||
k: v for k, v in root.attrib.items()
|
||||
style_attrs = {
|
||||
k: v
|
||||
for k, v in root.attrib.items()
|
||||
if k in style_keys
|
||||
}
|
||||
|
||||
new_root = ET.Element("svg", {})
|
||||
config_style_node = ET.SubElement(new_root, "g", config_style_dict)
|
||||
root_style_node = ET.SubElement(config_style_node, "g", root_style_dict)
|
||||
# Ignore other attributes in case that svgelements cannot parse them
|
||||
SVG_XMLNS = "{http://www.w3.org/2000/svg}"
|
||||
new_root = ET.Element("svg")
|
||||
config_style_node = ET.SubElement(new_root, f"{SVG_XMLNS}g", config_style_attrs)
|
||||
root_style_node = ET.SubElement(config_style_node, f"{SVG_XMLNS}g", style_attrs)
|
||||
root_style_node.extend(root)
|
||||
return ET.ElementTree(new_root)
|
||||
|
||||
|
@ -147,7 +150,7 @@ class SVGMobject(VMobject):
|
|||
def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]:
|
||||
result = []
|
||||
for shape in svg.elements():
|
||||
if isinstance(shape, se.Group):
|
||||
if isinstance(shape, (se.Group, se.Use)):
|
||||
continue
|
||||
elif isinstance(shape, se.Path):
|
||||
mob = self.path_to_mobject(shape)
|
||||
|
@ -155,9 +158,7 @@ class SVGMobject(VMobject):
|
|||
mob = self.line_to_mobject(shape)
|
||||
elif isinstance(shape, se.Rect):
|
||||
mob = self.rect_to_mobject(shape)
|
||||
elif isinstance(shape, se.Circle):
|
||||
mob = self.circle_to_mobject(shape)
|
||||
elif isinstance(shape, se.Ellipse):
|
||||
elif isinstance(shape, (se.Circle, se.Ellipse)):
|
||||
mob = self.ellipse_to_mobject(shape)
|
||||
elif isinstance(shape, se.Polygon):
|
||||
mob = self.polygon_to_mobject(shape)
|
||||
|
@ -168,11 +169,12 @@ class SVGMobject(VMobject):
|
|||
elif type(shape) == se.SVGElement:
|
||||
continue
|
||||
else:
|
||||
log.warning(f"Unsupported element type: {type(shape)}")
|
||||
log.warning("Unsupported element type: %s", type(shape))
|
||||
continue
|
||||
if not mob.has_points():
|
||||
continue
|
||||
self.apply_style_to_mobject(mob, shape)
|
||||
if isinstance(shape, se.GraphicObject):
|
||||
self.apply_style_to_mobject(mob, shape)
|
||||
if isinstance(shape, se.Transformable) and shape.apply:
|
||||
self.handle_transform(mob, shape.transform)
|
||||
result.append(mob)
|
||||
|
@ -203,21 +205,10 @@ class SVGMobject(VMobject):
|
|||
)
|
||||
return mob
|
||||
|
||||
@staticmethod
|
||||
def handle_transform(mob, matrix):
|
||||
mat = np.array([
|
||||
[matrix.a, matrix.c],
|
||||
[matrix.b, matrix.d]
|
||||
])
|
||||
vec = np.array([matrix.e, matrix.f, 0.0])
|
||||
mob.apply_matrix(mat)
|
||||
mob.shift(vec)
|
||||
return mob
|
||||
|
||||
def path_to_mobject(self, path: se.Path) -> VMobjectFromSVGPath:
|
||||
return VMobjectFromSVGPath(path, **self.path_string_config)
|
||||
|
||||
def line_to_mobject(self, line: se.Line) -> Line:
|
||||
def line_to_mobject(self, line: se.SimpleLine) -> Line:
|
||||
return Line(
|
||||
start=_convert_point_to_3d(line.x1, line.y1),
|
||||
end=_convert_point_to_3d(line.x2, line.y2)
|
||||
|
@ -242,15 +233,7 @@ class SVGMobject(VMobject):
|
|||
))
|
||||
return mob
|
||||
|
||||
def circle_to_mobject(self, circle: se.Circle) -> Circle:
|
||||
# svgelements supports `rx` & `ry` but `r`
|
||||
mob = Circle(radius=circle.rx)
|
||||
mob.shift(_convert_point_to_3d(
|
||||
circle.cx, circle.cy
|
||||
))
|
||||
return mob
|
||||
|
||||
def ellipse_to_mobject(self, ellipse: se.Ellipse) -> Circle:
|
||||
def ellipse_to_mobject(self, ellipse: se.Circle | se.Ellipse) -> Circle:
|
||||
mob = Circle(radius=ellipse.rx)
|
||||
mob.stretch_to_fit_height(2 * ellipse.ry)
|
||||
mob.shift(_convert_point_to_3d(
|
||||
|
@ -302,8 +285,7 @@ class VMobjectFromSVGPath(VMobject):
|
|||
# will be saved to a file so that future calls for the same path
|
||||
# don't need to retrace the same computation.
|
||||
path_string = self.path_obj.d()
|
||||
hasher = hashlib.sha256(path_string.encode())
|
||||
path_hash = hasher.hexdigest()[:16]
|
||||
path_hash = hash_string(path_string)
|
||||
points_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_points.npy")
|
||||
tris_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_tris.npy")
|
||||
|
||||
|
|
|
@ -13,8 +13,7 @@ from manimlib.mobject.svg.svg_mobject import SVGMobject
|
|||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.tex_file_writing import display_during_execution
|
||||
from manimlib.utils.tex_file_writing import get_tex_config
|
||||
from manimlib.utils.tex_file_writing import tex_to_svg_file
|
||||
from manimlib.utils.tex_file_writing import tex_content_to_svg_file
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
@ -44,6 +43,8 @@ class SingleStringTex(SVGMobject):
|
|||
"alignment": "\\centering",
|
||||
"math_mode": True,
|
||||
"organize_left_to_right": False,
|
||||
"template": "",
|
||||
"additional_preamble": "",
|
||||
}
|
||||
|
||||
def __init__(self, tex_string: str, **kwargs):
|
||||
|
@ -64,27 +65,24 @@ class SingleStringTex(SVGMobject):
|
|||
self.path_string_config,
|
||||
self.tex_string,
|
||||
self.alignment,
|
||||
self.math_mode
|
||||
self.math_mode,
|
||||
self.template,
|
||||
self.additional_preamble
|
||||
)
|
||||
|
||||
def get_file_path(self) -> str:
|
||||
full_tex = self.get_tex_file_body(self.tex_string)
|
||||
content = self.get_tex_file_body(self.tex_string)
|
||||
with display_during_execution(f"Writing \"{self.tex_string}\""):
|
||||
file_path = tex_to_svg_file(full_tex)
|
||||
file_path = tex_content_to_svg_file(
|
||||
content, self.template, self.additional_preamble
|
||||
)
|
||||
return file_path
|
||||
|
||||
def get_tex_file_body(self, tex_string: str) -> str:
|
||||
new_tex = self.get_modified_expression(tex_string)
|
||||
if self.math_mode:
|
||||
new_tex = "\\begin{align*}\n" + new_tex + "\n\\end{align*}"
|
||||
|
||||
new_tex = self.alignment + "\n" + new_tex
|
||||
|
||||
tex_config = get_tex_config()
|
||||
return tex_config["tex_body"].replace(
|
||||
tex_config["text_to_replace"],
|
||||
new_tex
|
||||
)
|
||||
return self.alignment + "\n" + new_tex
|
||||
|
||||
def get_modified_expression(self, tex_string: str) -> str:
|
||||
return self.modify_special_strings(tex_string.strip())
|
||||
|
|
|
@ -18,7 +18,7 @@ from manimlib.utils.config_ops import digest_config
|
|||
from manimlib.utils.customization import get_customization
|
||||
from manimlib.utils.directories import get_downloads_dir
|
||||
from manimlib.utils.directories import get_text_dir
|
||||
from manimlib.utils.tex_file_writing import tex_hash
|
||||
from manimlib.utils.simple_functions import hash_string
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
@ -63,7 +63,6 @@ class _Alignment:
|
|||
|
||||
class MarkupText(StringMobject):
|
||||
CONFIG = {
|
||||
"is_markup": True,
|
||||
"font_size": 48,
|
||||
"lsh": None,
|
||||
"justify": False,
|
||||
|
@ -81,21 +80,11 @@ class MarkupText(StringMobject):
|
|||
"t2w": {},
|
||||
"global_config": {},
|
||||
"local_configs": {},
|
||||
# For backward compatibility
|
||||
"isolate": (re.compile(r"[a-zA-Z]+"), re.compile(r"\S+")),
|
||||
"disable_ligatures": True,
|
||||
"isolate": re.compile(r"\w+", re.U),
|
||||
}
|
||||
|
||||
# See https://docs.gtk.org/Pango/pango_markup.html
|
||||
MARKUP_COLOR_KEYS = {
|
||||
"foreground": False,
|
||||
"fgcolor": False,
|
||||
"color": False,
|
||||
"background": True,
|
||||
"bgcolor": True,
|
||||
"underline_color": True,
|
||||
"overline_color": True,
|
||||
"strikethrough_color": True,
|
||||
}
|
||||
MARKUP_TAGS = {
|
||||
"b": {"font_weight": "bold"},
|
||||
"big": {"font_size": "larger"},
|
||||
|
@ -107,17 +96,24 @@ class MarkupText(StringMobject):
|
|||
"tt": {"font_family": "monospace"},
|
||||
"u": {"underline": "single"},
|
||||
}
|
||||
MARKUP_ENTITY_DICT = {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&": "&",
|
||||
"\"": """,
|
||||
"'": "'"
|
||||
}
|
||||
|
||||
def __init__(self, text: str, **kwargs):
|
||||
self.full2short(kwargs)
|
||||
digest_config(self, kwargs)
|
||||
|
||||
if not isinstance(self, Text):
|
||||
self.validate_markup_string(text)
|
||||
if not self.font:
|
||||
self.font = get_customization()["style"]["font"]
|
||||
if not self.alignment:
|
||||
self.alignment = get_customization()["style"]["text_alignment"]
|
||||
if self.is_markup:
|
||||
self.validate_markup_string(text)
|
||||
|
||||
self.text = text
|
||||
super().__init__(text, **kwargs)
|
||||
|
@ -140,8 +136,8 @@ class MarkupText(StringMobject):
|
|||
self.path_string_config,
|
||||
self.base_color,
|
||||
self.isolate,
|
||||
self.protect,
|
||||
self.text,
|
||||
self.is_markup,
|
||||
self.font_size,
|
||||
self.lsh,
|
||||
self.justify,
|
||||
|
@ -156,7 +152,8 @@ class MarkupText(StringMobject):
|
|||
self.t2s,
|
||||
self.t2w,
|
||||
self.global_config,
|
||||
self.local_configs
|
||||
self.local_configs,
|
||||
self.disable_ligatures
|
||||
)
|
||||
|
||||
def full2short(self, config: dict) -> None:
|
||||
|
@ -182,7 +179,7 @@ class MarkupText(StringMobject):
|
|||
self.line_width
|
||||
))
|
||||
svg_file = os.path.join(
|
||||
get_text_dir(), tex_hash(hash_content) + ".svg"
|
||||
get_text_dir(), hash_string(hash_content) + ".svg"
|
||||
)
|
||||
if not os.path.exists(svg_file):
|
||||
self.markup_to_svg(content, svg_file)
|
||||
|
@ -229,76 +226,92 @@ class MarkupText(StringMobject):
|
|||
f"{validate_error}"
|
||||
)
|
||||
|
||||
# Toolkits
|
||||
|
||||
@staticmethod
|
||||
def escape_markup_char(substr: str) -> str:
|
||||
return MarkupText.MARKUP_ENTITY_DICT.get(substr, substr)
|
||||
|
||||
@staticmethod
|
||||
def unescape_markup_char(substr: str) -> str:
|
||||
return {
|
||||
v: k
|
||||
for k, v in MarkupText.MARKUP_ENTITY_DICT.items()
|
||||
}.get(substr, substr)
|
||||
|
||||
# Parsing
|
||||
|
||||
def get_cmd_spans(self) -> list[Span]:
|
||||
if not self.is_markup:
|
||||
return self.find_spans(r"""[<>&"']""")
|
||||
@staticmethod
|
||||
def get_command_matches(string: str) -> list[re.Match]:
|
||||
pattern = re.compile(r"""
|
||||
(?P<tag>
|
||||
<
|
||||
(?P<close_slash>/)?
|
||||
(?P<tag_name>\w+)\s*
|
||||
(?P<attr_list>(?:\w+\s*\=\s*(?P<quot>["']).*?(?P=quot)\s*)*)
|
||||
(?P<elision_slash>/)?
|
||||
>
|
||||
)
|
||||
|(?P<passthrough>
|
||||
<\?.*?\?>|<!--.*?-->|<!\[CDATA\[.*?\]\]>|<!DOCTYPE.*?>
|
||||
)
|
||||
|(?P<entity>&(?P<unicode>\#(?P<hex>x)?)?(?P<content>.*?);)
|
||||
|(?P<char>[>"'])
|
||||
""", flags=re.X | re.S)
|
||||
return list(pattern.finditer(string))
|
||||
|
||||
# Unsupported passthroughs:
|
||||
# "<?...?>", "<!--...-->", "<![CDATA[...]]>", "<!DOCTYPE...>"
|
||||
# See https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gmarkup.c
|
||||
return self.find_spans(
|
||||
r"""&[\s\S]*?;|[>"']|</?\w+(?:\s*\w+\s*\=\s*(["'])[\s\S]*?\1)*/?>"""
|
||||
)
|
||||
|
||||
def get_substr_flag(self, substr: str) -> int:
|
||||
if re.fullmatch(r"<\w[\s\S]*[^/]>", substr):
|
||||
return 1
|
||||
if substr.startswith("</"):
|
||||
return -1
|
||||
@staticmethod
|
||||
def get_command_flag(match_obj: re.Match) -> int:
|
||||
if match_obj.group("tag"):
|
||||
if match_obj.group("close_slash"):
|
||||
return -1
|
||||
if not match_obj.group("elision_slash"):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def get_repl_substr_for_content(self, substr: str) -> str:
|
||||
if substr.startswith("<") and substr.endswith(">"):
|
||||
@staticmethod
|
||||
def replace_for_content(match_obj: re.Match) -> str:
|
||||
if match_obj.group("tag"):
|
||||
return ""
|
||||
return {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&": "&",
|
||||
"\"": """,
|
||||
"'": "'"
|
||||
}.get(substr, substr)
|
||||
if match_obj.group("char"):
|
||||
return MarkupText.escape_markup_char(match_obj.group("char"))
|
||||
return match_obj.group()
|
||||
|
||||
def get_repl_substr_for_matching(self, substr: str) -> str:
|
||||
if substr.startswith("<") and substr.endswith(">"):
|
||||
@staticmethod
|
||||
def replace_for_matching(match_obj: re.Match) -> str:
|
||||
if match_obj.group("tag") or match_obj.group("passthrough"):
|
||||
return ""
|
||||
if substr.startswith("&#") and substr.endswith(";"):
|
||||
if substr.startswith("&#x"):
|
||||
char_reference = int(substr[3:-1], 16)
|
||||
else:
|
||||
char_reference = int(substr[2:-1], 10)
|
||||
return chr(char_reference)
|
||||
return {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&": "&",
|
||||
""": "\"",
|
||||
"'": "'"
|
||||
}.get(substr, substr)
|
||||
if match_obj.group("entity"):
|
||||
if match_obj.group("unicode"):
|
||||
base = 10
|
||||
if match_obj.group("hex"):
|
||||
base = 16
|
||||
return chr(int(match_obj.group("content"), base))
|
||||
return MarkupText.unescape_markup_char(match_obj.group("entity"))
|
||||
return match_obj.group()
|
||||
|
||||
def get_specified_items(
|
||||
self, cmd_span_pairs: list[tuple[Span, Span]]
|
||||
) -> list[tuple[Span, dict[str, str]]]:
|
||||
attr_pattern = r"""(\w+)\s*\=\s*(["'])([\s\S]*?)\2"""
|
||||
internal_items = []
|
||||
for begin_cmd_span, end_cmd_span in cmd_span_pairs:
|
||||
begin_tag = self.get_substr(begin_cmd_span)
|
||||
tag_name = re.match(r"<(\w+)", begin_tag).group(1)
|
||||
if tag_name == "span":
|
||||
attr_dict = {
|
||||
attr_match_obj.group(1): attr_match_obj.group(3)
|
||||
for attr_match_obj in re.finditer(attr_pattern, begin_tag)
|
||||
}
|
||||
else:
|
||||
attr_dict = MarkupText.MARKUP_TAGS.get(tag_name, {})
|
||||
internal_items.append(
|
||||
((begin_cmd_span[1], end_cmd_span[0]), attr_dict)
|
||||
)
|
||||
@staticmethod
|
||||
def get_attr_dict_from_command_pair(
|
||||
open_command: re.Match, close_command: re.Match
|
||||
) -> dict[str, str] | None:
|
||||
pattern = r"""
|
||||
(?P<attr_name>\w+)
|
||||
\s*\=\s*
|
||||
(?P<quot>["'])(?P<attr_val>.*?)(?P=quot)
|
||||
"""
|
||||
tag_name = open_command.group("tag_name")
|
||||
if tag_name == "span":
|
||||
return {
|
||||
match_obj.group("attr_name"): match_obj.group("attr_val")
|
||||
for match_obj in re.finditer(
|
||||
pattern, open_command.group("attr_list"), re.S | re.X
|
||||
)
|
||||
}
|
||||
return MarkupText.MARKUP_TAGS.get(tag_name, {})
|
||||
|
||||
def get_configured_items(self) -> list[tuple[Span, dict[str, str]]]:
|
||||
return [
|
||||
*internal_items,
|
||||
*[
|
||||
*(
|
||||
(span, {key: val})
|
||||
for t2x_dict, key in (
|
||||
(self.t2c, "foreground"),
|
||||
|
@ -308,49 +321,49 @@ class MarkupText(StringMobject):
|
|||
)
|
||||
for selector, val in t2x_dict.items()
|
||||
for span in self.find_spans_by_selector(selector)
|
||||
],
|
||||
*[
|
||||
),
|
||||
*(
|
||||
(span, local_config)
|
||||
for selector, local_config in self.local_configs.items()
|
||||
for span in self.find_spans_by_selector(selector)
|
||||
],
|
||||
*[
|
||||
(span, {})
|
||||
for span in self.find_spans_by_selector(self.isolate)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_cmd_str_pair(
|
||||
attr_dict: dict[str, str], label_hex: str | None
|
||||
) -> tuple[str, str]:
|
||||
def get_command_string(
|
||||
attr_dict: dict[str, str], is_end: bool, label_hex: str | None
|
||||
) -> str:
|
||||
if is_end:
|
||||
return "</span>"
|
||||
|
||||
if label_hex is not None:
|
||||
converted_attr_dict = {"foreground": label_hex}
|
||||
for key, val in attr_dict.items():
|
||||
substitute_key = MarkupText.MARKUP_COLOR_KEYS.get(key, None)
|
||||
if substitute_key is None:
|
||||
converted_attr_dict[key] = val
|
||||
elif substitute_key:
|
||||
if key in (
|
||||
"background", "bgcolor",
|
||||
"underline_color", "overline_color", "strikethrough_color"
|
||||
):
|
||||
converted_attr_dict[key] = "black"
|
||||
elif key not in ("foreground", "fgcolor", "color"):
|
||||
converted_attr_dict[key] = val
|
||||
else:
|
||||
converted_attr_dict = attr_dict.copy()
|
||||
attrs_str = " ".join([
|
||||
f"{key}='{val}'"
|
||||
for key, val in converted_attr_dict.items()
|
||||
])
|
||||
return f"<span {attrs_str}>", "</span>"
|
||||
return f"<span {attrs_str}>"
|
||||
|
||||
def get_content_prefix_and_suffix(
|
||||
self, is_labelled: bool
|
||||
) -> tuple[str, str]:
|
||||
global_attr_dict = {
|
||||
"foreground": self.base_color_hex,
|
||||
"foreground": self.color_to_hex(self.base_color),
|
||||
"font_family": self.font,
|
||||
"font_style": self.slant,
|
||||
"font_weight": self.weight,
|
||||
"font_size": str(self.font_size * 1024),
|
||||
"font_size": str(round(self.font_size * 1024)),
|
||||
}
|
||||
global_attr_dict.update(self.global_config)
|
||||
# `line_height` attribute is supported since Pango 1.50.
|
||||
pango_version = manimpango.pango_version()
|
||||
if tuple(map(int, pango_version.split("."))) < (1, 50):
|
||||
|
@ -365,10 +378,17 @@ class MarkupText(StringMobject):
|
|||
global_attr_dict["line_height"] = str(
|
||||
((line_spacing_scale) + 1) * 0.6
|
||||
)
|
||||
if self.disable_ligatures:
|
||||
global_attr_dict["font_features"] = "liga=0,dlig=0,clig=0,hlig=0"
|
||||
|
||||
return self.get_cmd_str_pair(
|
||||
global_attr_dict,
|
||||
label_hex=self.int_to_hex(0) if is_labelled else None
|
||||
global_attr_dict.update(self.global_config)
|
||||
return tuple(
|
||||
self.get_command_string(
|
||||
global_attr_dict,
|
||||
is_end=is_end,
|
||||
label_hex=self.int_to_hex(0) if is_labelled else None
|
||||
)
|
||||
for is_end in (False, True)
|
||||
)
|
||||
|
||||
# Method alias
|
||||
|
@ -376,8 +396,8 @@ class MarkupText(StringMobject):
|
|||
def get_parts_by_text(self, selector: Selector) -> VGroup:
|
||||
return self.select_parts(selector)
|
||||
|
||||
def get_part_by_text(self, selector: Selector) -> VGroup:
|
||||
return self.select_part(selector)
|
||||
def get_part_by_text(self, selector: Selector, **kwargs) -> VGroup:
|
||||
return self.select_part(selector, **kwargs)
|
||||
|
||||
def set_color_by_text(self, selector: Selector, color: ManimColor):
|
||||
return self.set_parts_color(selector, color)
|
||||
|
@ -393,9 +413,27 @@ class MarkupText(StringMobject):
|
|||
|
||||
class Text(MarkupText):
|
||||
CONFIG = {
|
||||
"is_markup": False,
|
||||
# For backward compatibility
|
||||
"isolate": (re.compile(r"\w+", re.U), re.compile(r"\S+", re.U)),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_command_matches(string: str) -> list[re.Match]:
|
||||
pattern = re.compile(r"""[<>&"']""")
|
||||
return list(pattern.finditer(string))
|
||||
|
||||
@staticmethod
|
||||
def get_command_flag(match_obj: re.Match) -> int:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def replace_for_content(match_obj: re.Match) -> str:
|
||||
return Text.escape_markup_char(match_obj.group())
|
||||
|
||||
@staticmethod
|
||||
def replace_for_matching(match_obj: re.Match) -> str:
|
||||
return match_obj.group()
|
||||
|
||||
|
||||
class Code(MarkupText):
|
||||
CONFIG = {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from manimlib.constants import BLACK
|
||||
from manimlib.constants import ORIGIN
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
|
|
732
manimlib/tex_templates.yml
Normal file
732
manimlib/tex_templates.yml
Normal file
|
@ -0,0 +1,732 @@
|
|||
# Classical TeX templates
|
||||
|
||||
default:
|
||||
description: ""
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage[english]{babel}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{dsfont}
|
||||
\usepackage{setspace}
|
||||
\usepackage{tipa}
|
||||
\usepackage{relsize}
|
||||
\usepackage{textcomp}
|
||||
\usepackage{mathrsfs}
|
||||
\usepackage{calligra}
|
||||
\usepackage{wasysym}
|
||||
\usepackage{ragged2e}
|
||||
\usepackage{physics}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{microtype}
|
||||
\usepackage{pifont}
|
||||
\DisableLigatures{encoding = *, family = * }
|
||||
\linespread{1}
|
||||
|
||||
ctex:
|
||||
description: ""
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage[UTF8]{ctex}
|
||||
\usepackage[english]{babel}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{dsfont}
|
||||
\usepackage{setspace}
|
||||
\usepackage{tipa}
|
||||
\usepackage{relsize}
|
||||
\usepackage{textcomp}
|
||||
\usepackage{mathrsfs}
|
||||
\usepackage{calligra}
|
||||
\usepackage{wasysym}
|
||||
\usepackage{ragged2e}
|
||||
\usepackage{physics}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{microtype}
|
||||
\linespread{1}
|
||||
|
||||
# Simplified TeX templates
|
||||
|
||||
basic:
|
||||
description: ""
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage[english]{babel}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
|
||||
basic_ctex:
|
||||
description: ""
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage[UTF8]{ctex}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
|
||||
empty:
|
||||
description: ""
|
||||
compiler: latex
|
||||
preamble: ""
|
||||
|
||||
empty_ctex:
|
||||
description: ""
|
||||
compiler: xelatex
|
||||
preamble: ""
|
||||
|
||||
# A collection of TeX templates for the fonts described at
|
||||
# http://jf.burnol.free.fr/showcase.html
|
||||
|
||||
american_typewriter:
|
||||
description: American Typewriter
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{American Typewriter}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
antykwa:
|
||||
description: Antykwa Poltawskiego (TX Fonts for Greek and math symbols)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[OT4,OT1]{fontenc}
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
\usepackage{antpolt}
|
||||
\usepackage[defaultmathsizes,nolessnomore]{mathastext}
|
||||
|
||||
apple_chancery:
|
||||
description: Apple Chancery
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Apple Chancery}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
auriocus_kalligraphicus:
|
||||
description: Auriocus Kalligraphicus (Symbol Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{aurical}
|
||||
\renewcommand{\rmdefault}{AuriocusKalligraphicus}
|
||||
\usepackage[symbolgreek]{mathastext}
|
||||
|
||||
baskervald_adf_fourier:
|
||||
description: Baskervald ADF with Fourier
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[upright]{fourier}
|
||||
\usepackage{baskervald}
|
||||
\usepackage[defaultmathsizes,noasterisk]{mathastext}
|
||||
|
||||
baskerville_it:
|
||||
description: Baskerville (Italic)
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Baskerville}
|
||||
\usepackage[defaultmathsizes,italic]{mathastext}
|
||||
|
||||
biolinum:
|
||||
description: Biolinum
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Minion Pro}
|
||||
\setsansfont[Mapping=tex-text,Scale=MatchUppercase]{Myriad Pro}
|
||||
\renewcommand\familydefault\sfdefault
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
\renewcommand\familydefault\rmdefault
|
||||
|
||||
brushscriptx:
|
||||
description: BrushScriptX-Italic (PX math and Greek)
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{pxfonts}
|
||||
\renewcommand{\rmdefault}{pbsi}
|
||||
\renewcommand{\mddefault}{xl}
|
||||
\renewcommand{\bfdefault}{xl}
|
||||
\usepackage[defaultmathsizes,noasterisk]{mathastext}
|
||||
\boldmath
|
||||
|
||||
chalkboard_se:
|
||||
description: Chalkboard SE
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Chalkboard SE}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
chalkduster:
|
||||
description: Chalkduster
|
||||
compiler: lualatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Chalkduster}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
comfortaa:
|
||||
description: Comfortaa
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[default]{comfortaa}
|
||||
\usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext}
|
||||
\let\varphi\phi
|
||||
\linespread{1.06}
|
||||
|
||||
comic_sans:
|
||||
description: Comic Sans MS
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Comic Sans MS}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
droid_sans:
|
||||
description: Droid Sans
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{droidsans}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\let\varepsilon\epsilon
|
||||
|
||||
droid_sans_it:
|
||||
description: Droid Sans (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{droidsans}
|
||||
\usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}
|
||||
\let\varphi\phi
|
||||
|
||||
droid_serif:
|
||||
description: Droid Serif
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{droidserif}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\let\varepsilon\epsilon
|
||||
|
||||
droid_serif_px_it:
|
||||
description: Droid Serif (PX math symbols) (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{pxfonts}
|
||||
\usepackage[default]{droidserif}
|
||||
\usepackage[LGRgreek,defaultmathsizes,italic,basic]{mathastext}
|
||||
\let\varphi\phi
|
||||
|
||||
ecf_augie:
|
||||
description: ECF Augie (Euler Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\renewcommand\familydefault{fau}
|
||||
\usepackage[defaultmathsizes,eulergreek]{mathastext}
|
||||
|
||||
ecf_jd:
|
||||
description: ECF JD (with TX fonts)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
\renewcommand\familydefault{fjd}
|
||||
\usepackage{mathastext}
|
||||
\mathversion{bold}
|
||||
|
||||
ecf_skeetch:
|
||||
description: ECF Skeetch (CM Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\DeclareFontFamily{T1}{fsk}{}
|
||||
\DeclareFontShape{T1}{fsk}{m}{n}{<->s*[1.315] fskmw8t}{}
|
||||
\renewcommand\rmdefault{fsk}
|
||||
\usepackage[noendash,defaultmathsizes,nohbar,defaultimath]{mathastext}
|
||||
|
||||
ecf_tall_paul:
|
||||
description: ECF Tall Paul (with Symbol font)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\DeclareFontFamily{T1}{ftp}{}
|
||||
\DeclareFontShape{T1}{ftp}{m}{n}{<->s*[1.4] ftpmw8t}{}
|
||||
\renewcommand\familydefault{ftp}
|
||||
\usepackage[symbol]{mathastext}
|
||||
\let\infty\inftypsy
|
||||
|
||||
ecf_webster:
|
||||
description: ECF Webster (with TX fonts)
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
\renewcommand\familydefault{fwb}
|
||||
\usepackage{mathastext}
|
||||
\renewcommand{\int}{\intop\limits}
|
||||
\linespread{1.5}
|
||||
\mathversion{bold}
|
||||
|
||||
electrum_adf:
|
||||
description: Electrum ADF (CM Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[LGRgreek,basic,defaultmathsizes]{mathastext}
|
||||
\usepackage[lf]{electrum}
|
||||
\Mathastext
|
||||
\let\varphi\phi
|
||||
|
||||
epigrafica:
|
||||
description: Epigrafica
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[LGR,OT1]{fontenc}
|
||||
\usepackage{epigrafica}
|
||||
\usepackage[basic,LGRgreek,defaultmathsizes]{mathastext}
|
||||
\let\varphi\phi
|
||||
\linespread{1.2}
|
||||
|
||||
fourier_utopia:
|
||||
description: Fourier Utopia (Fourier upright Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[upright]{fourier}
|
||||
\usepackage{mathastext}
|
||||
|
||||
french_cursive:
|
||||
description: French Cursive (Euler Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{frcursive}
|
||||
\usepackage[eulergreek,noplusnominus,noequal,nohbar,nolessnomore,noasterisk]{mathastext}
|
||||
|
||||
gfs_bodoni:
|
||||
description: GFS Bodoni
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\renewcommand{\rmdefault}{bodoni}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\let\varphi\phi
|
||||
\linespread{1.06}
|
||||
|
||||
gfs_didot:
|
||||
description: GFS Didot (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\renewcommand\rmdefault{udidot}
|
||||
\usepackage[LGRgreek,defaultmathsizes,italic]{mathastext}
|
||||
\let\varphi\phi
|
||||
|
||||
gfs_neohellenic:
|
||||
description: GFS NeoHellenic
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\renewcommand{\rmdefault}{neohellenic}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\let\varphi\phi
|
||||
\linespread{1.06}
|
||||
|
||||
gnu_freesans_tx:
|
||||
description: GNU FreeSerif (and TX fonts symbols)
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\usepackage{txfonts}
|
||||
\setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
gnu_freeserif_freesans:
|
||||
description: GNU FreeSerif and FreeSans
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSerifBold,ItalicFont=FreeSerifItalic,BoldItalicFont=FreeSerifBoldItalic]{FreeSerif}
|
||||
\setsansfont[ExternalLocation,Mapping=tex-text,BoldFont=FreeSansBold,ItalicFont=FreeSansOblique,BoldItalicFont=FreeSansBoldOblique,Scale=MatchLowercase]{FreeSans}
|
||||
\renewcommand{\familydefault}{lmss}
|
||||
\usepackage[LGRgreek,defaultmathsizes,noasterisk]{mathastext}
|
||||
\renewcommand{\familydefault}{\sfdefault}
|
||||
\Mathastext
|
||||
\let\varphi\phi
|
||||
\renewcommand{\familydefault}{\rmdefault}
|
||||
|
||||
helvetica_fourier_it:
|
||||
description: Helvetica with Fourier (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[scaled]{helvet}
|
||||
\usepackage{fourier}
|
||||
\renewcommand{\rmdefault}{phv}
|
||||
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
|
||||
|
||||
latin_modern_tw:
|
||||
description: Latin Modern Typewriter Proportional
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[variablett]{lmodern}
|
||||
\renewcommand{\rmdefault}{\ttdefault}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\MTgreekfont{lmtt}
|
||||
\Mathastext
|
||||
\let\varepsilon\epsilon
|
||||
|
||||
latin_modern_tw_it:
|
||||
description: Latin Modern Typewriter Proportional (CM Greek) (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[variablett,nomath]{lmodern}
|
||||
\renewcommand{\familydefault}{\ttdefault}
|
||||
\usepackage[frenchmath]{mathastext}
|
||||
\linespread{1.08}
|
||||
|
||||
libertine:
|
||||
description: Libertine
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{libertine}
|
||||
\usepackage[greek=n]{libgreek}
|
||||
\usepackage[noasterisk,defaultmathsizes]{mathastext}
|
||||
|
||||
libris_adf_fourier:
|
||||
description: Libris ADF with Fourier
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[upright]{fourier}
|
||||
\usepackage{libris}
|
||||
\renewcommand{\familydefault}{\sfdefault}
|
||||
\usepackage[noasterisk]{mathastext}
|
||||
|
||||
minion_pro_myriad_pro:
|
||||
description: Minion Pro and Myriad Pro (and TX fonts symbols)
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[default]{droidserif}
|
||||
\usepackage[LGRgreek]{mathastext}
|
||||
\let\varepsilon\epsilon
|
||||
|
||||
minion_pro_tx:
|
||||
description: Minion Pro (and TX fonts symbols)
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{txfonts}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Minion Pro}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
new_century_schoolbook:
|
||||
description: New Century Schoolbook (Symbol Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{newcent}
|
||||
\usepackage[symbolgreek]{mathastext}
|
||||
\linespread{1.1}
|
||||
|
||||
new_century_schoolbook_px:
|
||||
description: New Century Schoolbook (Symbol Greek, PX math symbols)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{pxfonts}
|
||||
\usepackage{newcent}
|
||||
\usepackage[symbolgreek,defaultmathsizes]{mathastext}
|
||||
\linespread{1.06}
|
||||
|
||||
noteworthy_light:
|
||||
description: Noteworthy Light
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Noteworthy Light}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
palatino:
|
||||
description: Palatino (Symbol Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{palatino}
|
||||
\usepackage[symbolmax,defaultmathsizes]{mathastext}
|
||||
|
||||
papyrus:
|
||||
description: Papyrus
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Papyrus}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
romande_adf_fourier_it:
|
||||
description: Romande ADF with Fourier (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{fourier}
|
||||
\usepackage{romande}
|
||||
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
|
||||
\renewcommand{\itshape}{\swashstyle}
|
||||
|
||||
slitex:
|
||||
description: SliTeX (Euler Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{tpslifonts}
|
||||
\usepackage[eulergreek,defaultmathsizes]{mathastext}
|
||||
\MTEulerScale{1.06}
|
||||
\linespread{1.2}
|
||||
|
||||
times_fourier_it:
|
||||
description: Times with Fourier (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{fourier}
|
||||
\renewcommand{\rmdefault}{ptm}
|
||||
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
|
||||
|
||||
urw_avant_garde:
|
||||
description: URW Avant Garde (Symbol Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{avant}
|
||||
\renewcommand{\familydefault}{\sfdefault}
|
||||
\usepackage[symbolgreek,defaultmathsizes]{mathastext}
|
||||
|
||||
urw_zapf_chancery:
|
||||
description: URW Zapf Chancery (CM Greek)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\DeclareFontFamily{T1}{pzc}{}
|
||||
\DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{}
|
||||
\DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{}
|
||||
\DeclareFontShape{T1}{pzc}{mb}{sl}{<->ssub * pzc/mb/it}{}
|
||||
\DeclareFontShape{T1}{pzc}{m}{sl}{<->ssub * pzc/mb/sl}{}
|
||||
\DeclareFontShape{T1}{pzc}{m}{n}{<->ssub * pzc/mb/it}{}
|
||||
\usepackage{chancery}
|
||||
\usepackage{mathastext}
|
||||
\linespread{1.05}
|
||||
\boldmath
|
||||
|
||||
venturis_adf_fourier_it:
|
||||
description: Venturis ADF with Fourier (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{fourier}
|
||||
\usepackage[lf]{venturis}
|
||||
\usepackage[italic,defaultmathsizes,noasterisk]{mathastext}
|
||||
|
||||
verdana_it:
|
||||
description: Verdana (Italic)
|
||||
compiler: xelatex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[no-math]{fontspec}
|
||||
\setmainfont[Mapping=tex-text]{Verdana}
|
||||
\usepackage[defaultmathsizes,italic]{mathastext}
|
||||
|
||||
vollkorn:
|
||||
description: Vollkorn (TX fonts for Greek and math symbols)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{txfonts}
|
||||
\usepackage[upright]{txgreeks}
|
||||
\usepackage{vollkorn}
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
|
||||
vollkorn_fourier_it:
|
||||
description: Vollkorn with Fourier (Italic)
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{fourier}
|
||||
\usepackage{vollkorn}
|
||||
\usepackage[italic,nohbar]{mathastext}
|
||||
|
||||
zapf_chancery:
|
||||
description: Zapf Chancery
|
||||
compiler: latex
|
||||
preamble: |-
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{xcolor}
|
||||
\DeclareFontFamily{T1}{pzc}{}
|
||||
\DeclareFontShape{T1}{pzc}{mb}{it}{<->s*[1.2] pzcmi8t}{}
|
||||
\DeclareFontShape{T1}{pzc}{m}{it}{<->ssub * pzc/mb/it}{}
|
||||
\usepackage{chancery}
|
||||
\renewcommand\shapedefault\itdefault
|
||||
\renewcommand\bfdefault\mddefault
|
||||
\usepackage[defaultmathsizes]{mathastext}
|
||||
\linespread{1.05}
|
|
@ -1,25 +0,0 @@
|
|||
\documentclass[preview]{standalone}
|
||||
\usepackage[UTF8]{ctex}
|
||||
|
||||
\usepackage[english]{babel}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{dsfont}
|
||||
\usepackage{setspace}
|
||||
\usepackage{tipa}
|
||||
\usepackage{relsize}
|
||||
\usepackage{textcomp}
|
||||
\usepackage{mathrsfs}
|
||||
\usepackage{calligra}
|
||||
\usepackage{wasysym}
|
||||
\usepackage{ragged2e}
|
||||
\usepackage{physics}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{microtype}
|
||||
\linespread{1}
|
||||
|
||||
\begin{document}
|
||||
|
||||
[tex_expression]
|
||||
|
||||
\end{document}
|
|
@ -1,28 +0,0 @@
|
|||
\documentclass[preview]{standalone}
|
||||
|
||||
\usepackage[english]{babel}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{dsfont}
|
||||
\usepackage{setspace}
|
||||
\usepackage{tipa}
|
||||
\usepackage{relsize}
|
||||
\usepackage{textcomp}
|
||||
\usepackage{mathrsfs}
|
||||
\usepackage{calligra}
|
||||
\usepackage{wasysym}
|
||||
\usepackage{ragged2e}
|
||||
\usepackage{physics}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{microtype}
|
||||
\usepackage{pifont}
|
||||
\DisableLigatures{encoding = *, family = * }
|
||||
\linespread{1}
|
||||
|
||||
\begin{document}
|
||||
|
||||
[tex_expression]
|
||||
|
||||
\end{document}
|
|
@ -42,14 +42,9 @@ def init_customization() -> None:
|
|||
"sounds": "",
|
||||
"temporary_storage": "",
|
||||
},
|
||||
"tex": {
|
||||
"executable": "",
|
||||
"template_file": "",
|
||||
"intermediate_filetype": "",
|
||||
"text_to_replace": "[tex_expression]",
|
||||
},
|
||||
"universal_import_line": "from manimlib import *",
|
||||
"style": {
|
||||
"tex_template": "",
|
||||
"font": "Consolas",
|
||||
"background_color": "",
|
||||
},
|
||||
|
@ -62,7 +57,7 @@ def init_customization() -> None:
|
|||
"medium": "1280x720",
|
||||
"high": "1920x1080",
|
||||
"4k": "3840x2160",
|
||||
"default_resolution": "high",
|
||||
"default_resolution": "",
|
||||
},
|
||||
"fps": 30,
|
||||
}
|
||||
|
@ -109,24 +104,14 @@ def init_customization() -> None:
|
|||
show_default=False
|
||||
)
|
||||
|
||||
console.print("[bold]LaTeX:[/bold]")
|
||||
tex_config = configuration["tex"]
|
||||
tex = Prompt.ask(
|
||||
" Select an executable program to use to compile a LaTeX source file",
|
||||
choices=["latex", "xelatex"],
|
||||
default="latex"
|
||||
)
|
||||
if tex == "latex":
|
||||
tex_config["executable"] = "latex"
|
||||
tex_config["template_file"] = "tex_template.tex"
|
||||
tex_config["intermediate_filetype"] = "dvi"
|
||||
else:
|
||||
tex_config["executable"] = "xelatex -no-pdf"
|
||||
tex_config["template_file"] = "ctex_template.tex"
|
||||
tex_config["intermediate_filetype"] = "xdv"
|
||||
|
||||
console.print("[bold]Styles:[/bold]")
|
||||
configuration["style"]["background_color"] = Prompt.ask(
|
||||
style_config = configuration["style"]
|
||||
tex_template = Prompt.ask(
|
||||
" Select a TeX template to compile a LaTeX source file",
|
||||
default="default"
|
||||
)
|
||||
style_config["tex_template"] = tex_template
|
||||
style_config["background_color"] = Prompt.ask(
|
||||
" Which [bold]background color[/bold] do you want [italic](hex code)",
|
||||
default="#333333"
|
||||
)
|
||||
|
@ -139,7 +124,7 @@ def init_customization() -> None:
|
|||
)
|
||||
table.add_row("480p15", "720p30", "1080p60", "2160p60")
|
||||
console.print(table)
|
||||
configuration["camera_qualities"]["default_quality"] = Prompt.ask(
|
||||
configuration["camera_resolutions"]["default_resolution"] = Prompt.ask(
|
||||
" Which one to choose as the default rendering quality",
|
||||
choices=["low", "medium", "high", "ultra_high"],
|
||||
default="high"
|
||||
|
@ -161,7 +146,7 @@ def init_customization() -> None:
|
|||
file_name = os.path.join(os.getcwd(), "custom_config.yml")
|
||||
with open(file_name, "w", encoding="utf-8") as f:
|
||||
yaml.dump(configuration, f)
|
||||
|
||||
|
||||
console.print(f"\n:rocket: You have successfully set up a {scope} configuration file!")
|
||||
console.print(f"You can manually modify it in: [cyan]`{file_name}`[/cyan]")
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from functools import lru_cache
|
||||
import hashlib
|
||||
import inspect
|
||||
import math
|
||||
|
||||
|
@ -76,3 +77,9 @@ def binary_search(function,
|
|||
else:
|
||||
return None
|
||||
return mh
|
||||
|
||||
|
||||
def hash_string(string):
|
||||
# Truncating at 16 bytes for cleanliness
|
||||
hasher = hashlib.sha256(string.encode())
|
||||
return hasher.hexdigest()[:16]
|
||||
|
|
|
@ -1,135 +1,152 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import yaml
|
||||
|
||||
from manimlib.config import get_custom_config
|
||||
from manimlib.config import get_manim_dir
|
||||
from manimlib.logger import log
|
||||
from manimlib.utils.directories import get_tex_dir
|
||||
from manimlib.utils.simple_functions import hash_string
|
||||
|
||||
|
||||
SAVED_TEX_CONFIG = {}
|
||||
|
||||
|
||||
def get_tex_template_config(template_name: str) -> dict[str, str]:
|
||||
name = template_name.replace(" ", "_").lower()
|
||||
with open(os.path.join(
|
||||
get_manim_dir(), "manimlib", "tex_templates.yml"
|
||||
), encoding="utf-8") as tex_templates_file:
|
||||
templates_dict = yaml.safe_load(tex_templates_file)
|
||||
if name not in templates_dict:
|
||||
log.warning(
|
||||
"Cannot recognize template '%s', falling back to 'default'.",
|
||||
name
|
||||
)
|
||||
name = "default"
|
||||
return templates_dict[name]
|
||||
|
||||
|
||||
def get_tex_config() -> dict[str, str]:
|
||||
"""
|
||||
Returns a dict which should look something like this:
|
||||
{
|
||||
"executable": "latex",
|
||||
"template_file": "tex_template.tex",
|
||||
"intermediate_filetype": "dvi",
|
||||
"text_to_replace": "YourTextHere",
|
||||
"tex_body": "..."
|
||||
"template": "default",
|
||||
"compiler": "latex",
|
||||
"preamble": "..."
|
||||
}
|
||||
"""
|
||||
# Only load once, then save thereafter
|
||||
if not SAVED_TEX_CONFIG:
|
||||
custom_config = get_custom_config()
|
||||
SAVED_TEX_CONFIG.update(custom_config["tex"])
|
||||
# Read in template file
|
||||
template_filename = os.path.join(
|
||||
get_manim_dir(), "manimlib", "tex_templates",
|
||||
SAVED_TEX_CONFIG["template_file"],
|
||||
)
|
||||
with open(template_filename, "r", encoding="utf-8") as file:
|
||||
SAVED_TEX_CONFIG["tex_body"] = file.read()
|
||||
template_name = get_custom_config()["style"]["tex_template"]
|
||||
template_config = get_tex_template_config(template_name)
|
||||
SAVED_TEX_CONFIG.update({
|
||||
"template": template_name,
|
||||
"compiler": template_config["compiler"],
|
||||
"preamble": template_config["preamble"]
|
||||
})
|
||||
return SAVED_TEX_CONFIG
|
||||
|
||||
|
||||
def tex_hash(tex_file_content: str) -> int:
|
||||
# Truncating at 16 bytes for cleanliness
|
||||
hasher = hashlib.sha256(tex_file_content.encode())
|
||||
return hasher.hexdigest()[:16]
|
||||
def tex_content_to_svg_file(
|
||||
content: str, template: str, additional_preamble: str
|
||||
) -> str:
|
||||
tex_config = get_tex_config()
|
||||
if not template or template == tex_config["template"]:
|
||||
compiler = tex_config["compiler"]
|
||||
preamble = tex_config["preamble"]
|
||||
else:
|
||||
config = get_tex_template_config(template)
|
||||
compiler = config["compiler"]
|
||||
preamble = config["preamble"]
|
||||
|
||||
if additional_preamble:
|
||||
preamble += "\n" + additional_preamble
|
||||
full_tex = "\n\n".join((
|
||||
"\\documentclass[preview]{standalone}",
|
||||
preamble,
|
||||
"\\begin{document}",
|
||||
content,
|
||||
"\\end{document}"
|
||||
)) + "\n"
|
||||
|
||||
def tex_to_svg_file(tex_file_content: str) -> str:
|
||||
svg_file = os.path.join(
|
||||
get_tex_dir(), tex_hash(tex_file_content) + ".svg"
|
||||
get_tex_dir(), hash_string(full_tex) + ".svg"
|
||||
)
|
||||
if not os.path.exists(svg_file):
|
||||
# If svg doesn't exist, create it
|
||||
tex_to_svg(tex_file_content, svg_file)
|
||||
create_tex_svg(full_tex, svg_file, compiler)
|
||||
return svg_file
|
||||
|
||||
|
||||
def tex_to_svg(tex_file_content: str, svg_file: str) -> str:
|
||||
tex_file = svg_file.replace(".svg", ".tex")
|
||||
with open(tex_file, "w", encoding="utf-8") as outfile:
|
||||
outfile.write(tex_file_content)
|
||||
svg_file = dvi_to_svg(tex_to_dvi(tex_file))
|
||||
def create_tex_svg(full_tex: str, svg_file: str, compiler: str) -> None:
|
||||
if compiler == "latex":
|
||||
program = "latex"
|
||||
dvi_ext = ".dvi"
|
||||
elif compiler == "xelatex":
|
||||
program = "xelatex -no-pdf"
|
||||
dvi_ext = ".xdv"
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Compiler '{compiler}' is not implemented"
|
||||
)
|
||||
|
||||
# Write tex file
|
||||
root, _ = os.path.splitext(svg_file)
|
||||
with open(root + ".tex", "w", encoding="utf-8") as tex_file:
|
||||
tex_file.write(full_tex)
|
||||
|
||||
# tex to dvi
|
||||
if os.system(" ".join((
|
||||
program,
|
||||
"-interaction=batchmode",
|
||||
"-halt-on-error",
|
||||
f"-output-directory=\"{os.path.dirname(svg_file)}\"",
|
||||
f"\"{root}.tex\"",
|
||||
">",
|
||||
os.devnull
|
||||
))):
|
||||
log.error(
|
||||
"LaTeX Error! Not a worry, it happens to the best of us."
|
||||
)
|
||||
with open(root + ".log", "r", encoding="utf-8") as log_file:
|
||||
error_match_obj = re.search(r"(?<=\n! ).*", log_file.read())
|
||||
if error_match_obj:
|
||||
log.debug(
|
||||
"The error could be: `%s`",
|
||||
error_match_obj.group()
|
||||
)
|
||||
raise LatexError()
|
||||
|
||||
# dvi to svg
|
||||
os.system(" ".join((
|
||||
"dvisvgm",
|
||||
f"\"{root}{dvi_ext}\"",
|
||||
"-n",
|
||||
"-v",
|
||||
"0",
|
||||
"-o",
|
||||
f"\"{svg_file}\"",
|
||||
">",
|
||||
os.devnull
|
||||
)))
|
||||
|
||||
# Cleanup superfluous documents
|
||||
tex_dir, name = os.path.split(svg_file)
|
||||
stem, end = name.split(".")
|
||||
for file in filter(lambda s: s.startswith(stem), os.listdir(tex_dir)):
|
||||
if not file.endswith(end):
|
||||
os.remove(os.path.join(tex_dir, file))
|
||||
|
||||
return svg_file
|
||||
|
||||
|
||||
def tex_to_dvi(tex_file: str) -> str:
|
||||
tex_config = get_tex_config()
|
||||
program = tex_config["executable"]
|
||||
file_type = tex_config["intermediate_filetype"]
|
||||
result = tex_file.replace(".tex", "." + file_type)
|
||||
if not os.path.exists(result):
|
||||
commands = [
|
||||
program,
|
||||
"-interaction=batchmode",
|
||||
"-halt-on-error",
|
||||
f"-output-directory=\"{os.path.dirname(tex_file)}\"",
|
||||
f"\"{tex_file}\"",
|
||||
">",
|
||||
os.devnull
|
||||
]
|
||||
exit_code = os.system(" ".join(commands))
|
||||
if exit_code != 0:
|
||||
log_file = tex_file.replace(".tex", ".log")
|
||||
log.error("LaTeX Error! Not a worry, it happens to the best of us.")
|
||||
error_str = ""
|
||||
with open(log_file, "r", encoding="utf-8") as file:
|
||||
for line in file.readlines():
|
||||
if line.startswith("!"):
|
||||
error_str = line[2:-1]
|
||||
log.debug(f"The error could be: `{error_str}`")
|
||||
raise LatexError(error_str)
|
||||
return result
|
||||
|
||||
|
||||
def dvi_to_svg(dvi_file: str) -> str:
|
||||
"""
|
||||
Converts a dvi, which potentially has multiple slides, into a
|
||||
directory full of enumerated pngs corresponding with these slides.
|
||||
Returns a list of PIL Image objects for these images sorted as they
|
||||
where in the dvi
|
||||
"""
|
||||
file_type = get_tex_config()["intermediate_filetype"]
|
||||
result = dvi_file.replace("." + file_type, ".svg")
|
||||
if not os.path.exists(result):
|
||||
commands = [
|
||||
"dvisvgm",
|
||||
"\"{}\"".format(dvi_file),
|
||||
"-n",
|
||||
"-v",
|
||||
"0",
|
||||
"-o",
|
||||
"\"{}\"".format(result),
|
||||
">",
|
||||
os.devnull
|
||||
]
|
||||
os.system(" ".join(commands))
|
||||
return result
|
||||
for ext in (".tex", dvi_ext, ".log", ".aux"):
|
||||
try:
|
||||
os.remove(root + ext)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
# TODO, perhaps this should live elsewhere
|
||||
@contextmanager
|
||||
def display_during_execution(message: str) -> None:
|
||||
# Only show top line
|
||||
to_print = message.split("\n")[0]
|
||||
def display_during_execution(message: str):
|
||||
# Merge into a single line
|
||||
to_print = message.replace("\n", " ")
|
||||
max_characters = os.get_terminal_size().columns - 1
|
||||
if len(to_print) > max_characters:
|
||||
to_print = to_print[:max_characters - 3] + "..."
|
||||
|
@ -140,6 +157,5 @@ def display_during_execution(message: str) -> None:
|
|||
print(" " * len(to_print), end="\r")
|
||||
|
||||
|
||||
|
||||
class LatexError(Exception):
|
||||
pass
|
||||
|
|
|
@ -17,7 +17,7 @@ rich
|
|||
scipy
|
||||
screeninfo
|
||||
skia-pathops
|
||||
svgelements
|
||||
svgelements>=1.8.1
|
||||
sympy
|
||||
tqdm
|
||||
validators
|
||||
|
|
|
@ -48,7 +48,7 @@ install_requires =
|
|||
scipy
|
||||
screeninfo
|
||||
skia-pathops
|
||||
svgelements
|
||||
svgelements>=1.8.1
|
||||
sympy
|
||||
tqdm
|
||||
validators
|
||||
|
|
Loading…
Add table
Reference in a new issue