manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. manim/__init__.py +11 -6
  2. manim/__main__.py +62 -19
  3. manim/_config/__init__.py +10 -9
  4. manim/_config/cli_colors.py +26 -9
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +23 -13
  7. manim/_config/utils.py +662 -468
  8. manim/animation/animation.py +164 -18
  9. manim/animation/changing.py +34 -23
  10. manim/animation/composition.py +265 -67
  11. manim/animation/creation.py +208 -26
  12. manim/animation/fading.py +16 -18
  13. manim/animation/growing.py +35 -15
  14. manim/animation/indication.py +150 -76
  15. manim/animation/movement.py +56 -22
  16. manim/animation/numbers.py +64 -6
  17. manim/animation/rotation.py +78 -7
  18. manim/animation/specialized.py +6 -7
  19. manim/animation/speedmodifier.py +13 -10
  20. manim/animation/transform.py +14 -11
  21. manim/animation/transform_matching_parts.py +3 -4
  22. manim/animation/updaters/mobject_update_utils.py +152 -30
  23. manim/animation/updaters/update.py +10 -7
  24. manim/camera/camera.py +182 -118
  25. manim/camera/mapping_camera.py +34 -3
  26. manim/camera/moving_camera.py +95 -74
  27. manim/camera/multi_camera.py +23 -15
  28. manim/camera/three_d_camera.py +70 -52
  29. manim/cli/__init__.py +17 -0
  30. manim/cli/cfg/group.py +76 -44
  31. manim/cli/checkhealth/checks.py +192 -0
  32. manim/cli/checkhealth/commands.py +90 -0
  33. manim/cli/default_group.py +158 -25
  34. manim/cli/init/commands.py +33 -25
  35. manim/cli/plugins/commands.py +16 -3
  36. manim/cli/render/commands.py +72 -60
  37. manim/cli/render/ease_of_access_options.py +4 -3
  38. manim/cli/render/global_options.py +59 -17
  39. manim/cli/render/output_options.py +6 -5
  40. manim/cli/render/render_options.py +98 -33
  41. manim/constants.py +109 -59
  42. manim/data_structures.py +31 -0
  43. manim/mobject/frame.py +8 -5
  44. manim/mobject/geometry/__init__.py +1 -0
  45. manim/mobject/geometry/arc.py +277 -135
  46. manim/mobject/geometry/boolean_ops.py +32 -31
  47. manim/mobject/geometry/labeled.py +376 -0
  48. manim/mobject/geometry/line.py +192 -87
  49. manim/mobject/geometry/polygram.py +224 -58
  50. manim/mobject/geometry/shape_matchers.py +61 -25
  51. manim/mobject/geometry/tips.py +122 -48
  52. manim/mobject/graph.py +1027 -419
  53. manim/mobject/graphing/coordinate_systems.py +533 -278
  54. manim/mobject/graphing/functions.py +53 -32
  55. manim/mobject/graphing/number_line.py +123 -65
  56. manim/mobject/graphing/probability.py +88 -62
  57. manim/mobject/graphing/scale.py +33 -19
  58. manim/mobject/logo.py +118 -28
  59. manim/mobject/matrix.py +87 -83
  60. manim/mobject/mobject.py +912 -442
  61. manim/mobject/opengl/dot_cloud.py +16 -5
  62. manim/mobject/opengl/opengl_compatibility.py +4 -2
  63. manim/mobject/opengl/opengl_geometry.py +254 -153
  64. manim/mobject/opengl/opengl_image_mobject.py +3 -1
  65. manim/mobject/opengl/opengl_mobject.py +779 -482
  66. manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
  67. manim/mobject/opengl/opengl_surface.py +14 -92
  68. manim/mobject/opengl/opengl_three_dimensions.py +12 -8
  69. manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
  70. manim/mobject/svg/brace.py +173 -41
  71. manim/mobject/svg/svg_mobject.py +139 -53
  72. manim/mobject/table.py +61 -68
  73. manim/mobject/text/code_mobject.py +193 -539
  74. manim/mobject/text/numbers.py +81 -34
  75. manim/mobject/text/tex_mobject.py +130 -78
  76. manim/mobject/text/text_mobject.py +288 -164
  77. manim/mobject/three_d/polyhedra.py +111 -13
  78. manim/mobject/three_d/three_d_utils.py +17 -8
  79. manim/mobject/three_d/three_dimensions.py +239 -106
  80. manim/mobject/types/image_mobject.py +50 -30
  81. manim/mobject/types/point_cloud_mobject.py +120 -75
  82. manim/mobject/types/vectorized_mobject.py +841 -408
  83. manim/mobject/value_tracker.py +105 -38
  84. manim/mobject/vector_field.py +50 -31
  85. manim/opengl/__init__.py +3 -3
  86. manim/plugins/__init__.py +14 -1
  87. manim/plugins/plugins_flags.py +10 -14
  88. manim/renderer/cairo_renderer.py +65 -50
  89. manim/renderer/opengl_renderer.py +89 -69
  90. manim/renderer/opengl_renderer_window.py +39 -18
  91. manim/renderer/shader.py +123 -87
  92. manim/renderer/shader_wrapper.py +44 -28
  93. manim/renderer/vectorized_mobject_rendering.py +38 -10
  94. manim/scene/moving_camera_scene.py +32 -3
  95. manim/scene/scene.py +507 -242
  96. manim/scene/scene_file_writer.py +371 -220
  97. manim/scene/section.py +20 -16
  98. manim/scene/three_d_scene.py +14 -22
  99. manim/scene/vector_space_scene.py +223 -129
  100. manim/scene/zoomed_scene.py +46 -41
  101. manim/typing.py +990 -0
  102. manim/utils/bezier.py +1823 -371
  103. manim/utils/caching.py +12 -5
  104. manim/utils/color/AS2700.py +236 -0
  105. manim/utils/color/BS381.py +318 -0
  106. manim/utils/color/DVIPSNAMES.py +96 -0
  107. manim/utils/color/SVGNAMES.py +179 -0
  108. manim/utils/color/X11.py +533 -0
  109. manim/utils/color/XKCD.py +952 -0
  110. manim/utils/color/__init__.py +61 -0
  111. manim/utils/color/core.py +1667 -0
  112. manim/utils/color/manim_colors.py +218 -0
  113. manim/utils/commands.py +48 -20
  114. manim/utils/config_ops.py +39 -19
  115. manim/utils/debug.py +8 -7
  116. manim/utils/deprecation.py +86 -39
  117. manim/utils/docbuild/__init__.py +17 -0
  118. manim/utils/docbuild/autoaliasattr_directive.py +236 -0
  119. manim/utils/docbuild/autocolor_directive.py +99 -0
  120. manim/utils/docbuild/manim_directive.py +94 -41
  121. manim/utils/docbuild/module_parsing.py +245 -0
  122. manim/utils/exceptions.py +6 -0
  123. manim/utils/family.py +5 -3
  124. manim/utils/family_ops.py +17 -4
  125. manim/utils/file_ops.py +27 -17
  126. manim/utils/hashing.py +55 -45
  127. manim/utils/images.py +13 -7
  128. manim/utils/ipython_magic.py +13 -7
  129. manim/utils/iterables.py +163 -120
  130. manim/utils/module_ops.py +66 -24
  131. manim/utils/opengl.py +77 -24
  132. manim/utils/parameter_parsing.py +32 -0
  133. manim/utils/paths.py +30 -33
  134. manim/utils/polylabel.py +235 -0
  135. manim/utils/qhull.py +218 -0
  136. manim/utils/rate_functions.py +98 -32
  137. manim/utils/simple_functions.py +25 -33
  138. manim/utils/sounds.py +7 -1
  139. manim/utils/space_ops.py +188 -115
  140. manim/utils/testing/__init__.py +17 -0
  141. manim/utils/testing/_frames_testers.py +13 -8
  142. manim/utils/testing/_show_diff.py +5 -3
  143. manim/utils/testing/_test_class_makers.py +34 -18
  144. manim/utils/testing/frames_comparison.py +37 -19
  145. manim/utils/tex.py +130 -198
  146. manim/utils/tex_file_writing.py +77 -47
  147. manim/utils/tex_templates.py +2 -1
  148. manim/utils/unit.py +6 -5
  149. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
  150. manim-0.19.1.dist-info/RECORD +220 -0
  151. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
  152. manim-0.19.1.dist-info/entry_points.txt +3 -0
  153. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
  154. manim/cli/new/group.py +0 -189
  155. manim/communitycolors.py +0 -9
  156. manim/gui/__init__.py +0 -0
  157. manim/gui/gui.py +0 -82
  158. manim/plugins/import_plugins.py +0 -43
  159. manim/utils/color.py +0 -552
  160. manim-0.17.0.dist-info/RECORD +0 -206
  161. manim-0.17.0.dist-info/entry_points.txt +0 -4
  162. /manim/cli/{new → checkhealth}/__init__.py +0 -0
  163. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
@@ -6,50 +6,28 @@ __all__ = [
6
6
  "Code",
7
7
  ]
8
8
 
9
- import html
10
- import os
11
9
  import re
12
10
  from pathlib import Path
11
+ from typing import Any, Literal
13
12
 
14
- import numpy as np
13
+ from bs4 import BeautifulSoup, Tag
15
14
  from pygments import highlight
16
15
  from pygments.formatters.html import HtmlFormatter
17
- from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
16
+ from pygments.lexers import get_lexer_by_name, guess_lexer, guess_lexer_for_filename
18
17
  from pygments.styles import get_all_styles
19
18
 
20
19
  from manim.constants import *
21
20
  from manim.mobject.geometry.arc import Dot
22
- from manim.mobject.geometry.polygram import RoundedRectangle
23
21
  from manim.mobject.geometry.shape_matchers import SurroundingRectangle
24
- from manim.mobject.text.text_mobject import Paragraph
25
- from manim.mobject.types.vectorized_mobject import VGroup
26
- from manim.utils.color import WHITE
22
+ from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
23
+ from manim.mobject.types.vectorized_mobject import VGroup, VMobject
24
+ from manim.typing import StrPath
25
+ from manim.utils.color import WHITE, ManimColor
27
26
 
28
27
 
29
- class Code(VGroup):
28
+ class Code(VMobject, metaclass=ConvertToOpenGL):
30
29
  """A highlighted source code listing.
31
30
 
32
- An object ``listing`` of :class:`.Code` is a :class:`.VGroup` consisting
33
- of three objects:
34
-
35
- - The background, ``listing.background_mobject``. This is either
36
- a :class:`.Rectangle` (if the listing has been initialized with
37
- ``background="rectangle"``, the default option) or a :class:`.VGroup`
38
- resembling a window (if ``background="window"`` has been passed).
39
-
40
- - The line numbers, ``listing.line_numbers`` (a :class:`.Paragraph`
41
- object).
42
-
43
- - The highlighted code itself, ``listing.code`` (a :class:`.Paragraph`
44
- object).
45
-
46
- .. WARNING::
47
-
48
- Using a :class:`.Transform` on text with leading whitespace (and in
49
- this particular case: code) can look
50
- `weird <https://github.com/3b1b/manim/issues/1067>`_. Consider using
51
- :meth:`remove_invisible_chars` to resolve this issue.
52
-
53
31
  Examples
54
32
  --------
55
33
 
@@ -58,16 +36,16 @@ class Code(VGroup):
58
36
  listing = Code(
59
37
  "helloworldcpp.cpp",
60
38
  tab_width=4,
61
- background_stroke_width=1,
62
- background_stroke_color=WHITE,
63
- insert_line_no=True,
64
- style=Code.styles_list[15],
39
+ formatter_style="emacs",
65
40
  background="window",
66
41
  language="cpp",
42
+ background_config={"stroke_color": WHITE},
43
+ paragraph_config={"font": "Noto Sans Mono"},
67
44
  )
68
45
 
69
- We can also render code passed as a string (but note that
70
- the language has to be specified in this case):
46
+ We can also render code passed as a string. As the automatic language
47
+ detection can be a bit flaky, it is recommended to specify the language
48
+ explicitly:
71
49
 
72
50
  .. manim:: CodeFromString
73
51
  :save_last_frame:
@@ -81,532 +59,208 @@ class Code(VGroup):
81
59
  s = Square()
82
60
  self.play(FadeIn(s))
83
61
  self.play(s.animate.scale(2))
84
- self.wait()
85
- '''
86
- rendered_code = Code(code=code, tab_width=4, background="window",
87
- language="Python", font="Monospace")
62
+ self.wait()'''
63
+
64
+ rendered_code = Code(
65
+ code_string=code,
66
+ language="python",
67
+ background="window",
68
+ background_config={"stroke_color": "maroon"},
69
+ )
88
70
  self.add(rendered_code)
89
71
 
90
72
  Parameters
91
73
  ----------
92
- file_name
93
- Name of the code file to display.
94
- code
95
- If ``file_name`` is not specified, a code string can be
96
- passed directly.
74
+ code_file
75
+ The path to the code file to display.
76
+ code_string
77
+ Alternatively, the code string to display.
78
+ language
79
+ The programming language of the code. If not specified, it will be
80
+ guessed from the file extension or the code itself.
81
+ formatter_style
82
+ The style to use for the code highlighting. Defaults to ``"vim"``.
83
+ A list of all available styles can be obtained by calling
84
+ :meth:`.Code.get_styles_list`.
97
85
  tab_width
98
- Number of space characters corresponding to a tab character. Defaults to 3.
99
- line_spacing
100
- Amount of space between lines in relation to font size. Defaults to 0.3, which means 30% of font size.
101
- font_size
102
- A number which scales displayed code. Defaults to 24.
103
- font
104
- The name of the text font to be used. Defaults to ``"Monospac821 BT"``.
105
- stroke_width
106
- Stroke width for text. 0 is recommended, and the default.
107
- margin
108
- Inner margin of text from the background. Defaults to 0.3.
109
- indentation_chars
110
- "Indentation chars" refers to the spaces/tabs at the beginning of a given code line. Defaults to ``" "`` (spaces).
86
+ The width of a tab character in spaces. Defaults to 4.
87
+ add_line_numbers
88
+ Whether to display line numbers. Defaults to ``True``.
89
+ line_numbers_from
90
+ The first line number to display. Defaults to 1.
111
91
  background
112
- Defines the background's type. Currently supports only ``"rectangle"`` (default) and ``"window"``.
113
- background_stroke_width
114
- Defines the stroke width of the background. Defaults to 1.
115
- background_stroke_color
116
- Defines the stroke color for the background. Defaults to ``WHITE``.
117
- corner_radius
118
- Defines the corner radius for the background. Defaults to 0.2.
119
- insert_line_no
120
- Defines whether line numbers should be inserted in displayed code. Defaults to ``True``.
121
- line_no_from
122
- Defines the first line's number in the line count. Defaults to 1.
123
- line_no_buff
124
- Defines the spacing between line numbers and displayed code. Defaults to 0.4.
125
- style
126
- Defines the style type of displayed code. You can see possible names of styles in with :attr:`styles_list`. Defaults to ``"vim"``.
127
- language
128
- Specifies the programming language the given code was written in. If ``None``
129
- (the default), the language will be automatically detected. For the list of
130
- possible options, visit https://pygments.org/docs/lexers/ and look for
131
- 'aliases or short names'.
132
- generate_html_file
133
- Defines whether to generate highlighted html code to the folder `assets/codes/generated_html_files`. Defaults to `False`.
134
-
135
- Attributes
136
- ----------
137
- background_mobject : :class:`~.VGroup`
138
- The background of the code listing.
139
- line_numbers : :class:`~.Paragraph`
140
- The line numbers for the code listing. Empty, if
141
- ``insert_line_no=False`` has been specified.
142
- code : :class:`~.Paragraph`
143
- The highlighted code.
144
-
92
+ The type of background to use. Can be either ``"rectangle"`` (the
93
+ default) or ``"window"``.
94
+ background_config
95
+ Keyword arguments passed to the background constructor. Default
96
+ settings are stored in the class attribute
97
+ :attr:`.default_background_config` (which can also be modified
98
+ directly).
99
+ paragraph_config
100
+ Keyword arguments passed to the constructor of the
101
+ :class:`.Paragraph` objects holding the code, and the line
102
+ numbers. Default settings are stored in the class attribute
103
+ :attr:`.default_paragraph_config` (which can also be modified
104
+ directly).
145
105
  """
146
106
 
147
- # tuples in the form (name, aliases, filetypes, mimetypes)
148
- # 'language' is aliases or short names
149
- # For more information about pygments.lexers visit https://pygments.org/docs/lexers/
150
- # from pygments.lexers import get_all_lexers
151
- # all_lexers = get_all_lexers()
152
- styles_list = list(get_all_styles())
153
- # For more information about pygments.styles visit https://pygments.org/docs/styles/
107
+ _styles_list_cache: list[str] | None = None
108
+ default_background_config: dict[str, Any] = {
109
+ "buff": 0.3,
110
+ "fill_color": ManimColor("#222"),
111
+ "stroke_color": WHITE,
112
+ "corner_radius": 0.2,
113
+ "stroke_width": 1,
114
+ "fill_opacity": 1,
115
+ }
116
+ default_paragraph_config: dict[str, Any] = {
117
+ "font": "Monospace",
118
+ "font_size": 24,
119
+ "line_spacing": 0.5,
120
+ "disable_ligatures": True,
121
+ }
122
+ code: VMobject
154
123
 
155
124
  def __init__(
156
125
  self,
157
- file_name: str | os.PathLike | None = None,
158
- code: str | None = None,
159
- tab_width: int = 3,
160
- line_spacing: float = 0.3,
161
- font_size: float = 24,
162
- font: str = "Monospac821 BT",
163
- stroke_width: float = 0,
164
- margin: float = 0.3,
165
- indentation_chars: str = " ",
166
- background: str = "rectangle", # or window
167
- background_stroke_width: float = 1,
168
- background_stroke_color: str = WHITE,
169
- corner_radius: float = 0.2,
170
- insert_line_no: bool = True,
171
- line_no_from: int = 1,
172
- line_no_buff: float = 0.4,
173
- style: str = "vim",
126
+ code_file: StrPath | None = None,
127
+ code_string: str | None = None,
174
128
  language: str | None = None,
175
- generate_html_file: bool = False,
176
- **kwargs,
129
+ formatter_style: str = "vim",
130
+ tab_width: int = 4,
131
+ add_line_numbers: bool = True,
132
+ line_numbers_from: int = 1,
133
+ background: Literal["rectangle", "window"] = "rectangle",
134
+ background_config: dict[str, Any] | None = None,
135
+ paragraph_config: dict[str, Any] | None = None,
177
136
  ):
178
- super().__init__(
179
- stroke_width=stroke_width,
180
- **kwargs,
181
- )
182
- self.background_stroke_color = background_stroke_color
183
- self.background_stroke_width = background_stroke_width
184
- self.tab_width = tab_width
185
- self.line_spacing = line_spacing
186
- self.font = font
187
- self.font_size = font_size
188
- self.margin = margin
189
- self.indentation_chars = indentation_chars
190
- self.background = background
191
- self.corner_radius = corner_radius
192
- self.insert_line_no = insert_line_no
193
- self.line_no_from = line_no_from
194
- self.line_no_buff = line_no_buff
195
- self.style = style
196
- self.language = language
197
- self.generate_html_file = generate_html_file
198
-
199
- self.file_path = None
200
- self.file_name = file_name
201
- if self.file_name:
202
- self._ensure_valid_file()
203
- self.code_string = self.file_path.read_text(encoding="utf-8")
204
- elif code:
205
- self.code_string = code
206
- else:
207
- raise ValueError(
208
- "Neither a code file nor a code string have been specified.",
209
- )
210
- if isinstance(self.style, str):
211
- self.style = self.style.lower()
212
- self._gen_html_string()
213
- strati = self.html_string.find("background:")
214
- self.background_color = self.html_string[strati + 12 : strati + 19]
215
- self._gen_code_json()
216
-
217
- self.code = self._gen_colored_lines()
218
- if self.insert_line_no:
219
- self.line_numbers = self._gen_line_numbers()
220
- self.line_numbers.next_to(self.code, direction=LEFT, buff=self.line_no_buff)
221
- if self.background == "rectangle":
222
- if self.insert_line_no:
223
- foreground = VGroup(self.code, self.line_numbers)
137
+ super().__init__()
138
+
139
+ if code_file is not None:
140
+ code_file = Path(code_file)
141
+ code_string = code_file.read_text(encoding="utf-8")
142
+ lexer = guess_lexer_for_filename(code_file.name, code_string)
143
+ elif code_string is not None:
144
+ if language is not None:
145
+ lexer = get_lexer_by_name(language)
224
146
  else:
225
- foreground = self.code
226
- rect = SurroundingRectangle(
227
- foreground,
228
- buff=self.margin,
229
- color=self.background_color,
230
- fill_color=self.background_color,
231
- stroke_width=self.background_stroke_width,
232
- stroke_color=self.background_stroke_color,
233
- fill_opacity=1,
234
- )
235
- rect.round_corners(self.corner_radius)
236
- self.background_mobject = rect
147
+ lexer = guess_lexer(code_string)
237
148
  else:
238
- if self.insert_line_no:
239
- foreground = VGroup(self.code, self.line_numbers)
240
- else:
241
- foreground = self.code
242
- height = foreground.height + 0.1 * 3 + 2 * self.margin
243
- width = foreground.width + 0.1 * 3 + 2 * self.margin
244
-
245
- rect = RoundedRectangle(
246
- corner_radius=self.corner_radius,
247
- height=height,
248
- width=width,
249
- stroke_width=self.background_stroke_width,
250
- stroke_color=self.background_stroke_color,
251
- color=self.background_color,
252
- fill_opacity=1,
253
- )
254
- red_button = Dot(radius=0.1, stroke_width=0, color="#ff5f56")
255
- red_button.shift(LEFT * 0.1 * 3)
256
- yellow_button = Dot(radius=0.1, stroke_width=0, color="#ffbd2e")
257
- green_button = Dot(radius=0.1, stroke_width=0, color="#27c93f")
258
- green_button.shift(RIGHT * 0.1 * 3)
259
- buttons = VGroup(red_button, yellow_button, green_button)
260
- buttons.shift(
261
- UP * (height / 2 - 0.1 * 2 - 0.05)
262
- + LEFT * (width / 2 - 0.1 * 5 - self.corner_radius / 2 - 0.05),
263
- )
149
+ raise ValueError("Either a code file or a code string must be specified.")
264
150
 
265
- self.background_mobject = VGroup(rect, buttons)
266
- x = (height - foreground.height) / 2 - 0.1 * 3
267
- self.background_mobject.shift(foreground.get_center())
268
- self.background_mobject.shift(UP * x)
269
- if self.insert_line_no:
270
- super().__init__(
271
- self.background_mobject, self.line_numbers, self.code, **kwargs
272
- )
273
- else:
274
- super().__init__(
275
- self.background_mobject,
276
- Dot(fill_opacity=0, stroke_opacity=0),
277
- self.code,
278
- **kwargs,
279
- )
280
- self.move_to(np.array([0, 0, 0]))
281
-
282
- def _ensure_valid_file(self):
283
- """Function to validate file."""
284
- if self.file_name is None:
285
- raise Exception("Must specify file for Code")
286
- possible_paths = [
287
- Path() / "assets" / "codes" / self.file_name,
288
- Path(self.file_name).expanduser(),
289
- ]
290
- for path in possible_paths:
291
- if path.exists():
292
- self.file_path = path
293
- return
294
- error = (
295
- f"From: {Path.cwd()}, could not find {self.file_name} at either "
296
- + f"of these locations: {list(map(str, possible_paths))}"
297
- )
298
- raise OSError(error)
299
-
300
- def _gen_line_numbers(self):
301
- """Function to generate line_numbers.
302
-
303
- Returns
304
- -------
305
- :class:`~.Paragraph`
306
- The generated line_numbers according to parameters.
307
- """
308
- line_numbers_array = []
309
- for line_no in range(0, self.code_json.__len__()):
310
- number = str(self.line_no_from + line_no)
311
- line_numbers_array.append(number)
312
- line_numbers = Paragraph(
313
- *list(line_numbers_array),
314
- line_spacing=self.line_spacing,
315
- alignment="right",
316
- font_size=self.font_size,
317
- font=self.font,
318
- disable_ligatures=True,
319
- stroke_width=self.stroke_width,
320
- )
321
- for i in line_numbers:
322
- i.set_color(self.default_color)
323
- return line_numbers
324
-
325
- def _gen_colored_lines(self):
326
- """Function to generate code.
327
-
328
- Returns
329
- -------
330
- :class:`~.Paragraph`
331
- The generated code according to parameters.
332
- """
333
- lines_text = []
334
- for line_no in range(0, self.code_json.__len__()):
335
- line_str = ""
336
- for word_index in range(self.code_json[line_no].__len__()):
337
- line_str = line_str + self.code_json[line_no][word_index][0]
338
- lines_text.append(self.tab_spaces[line_no] * "\t" + line_str)
339
- code = Paragraph(
340
- *list(lines_text),
341
- line_spacing=self.line_spacing,
342
- tab_width=self.tab_width,
343
- font_size=self.font_size,
344
- font=self.font,
345
- disable_ligatures=True,
346
- stroke_width=self.stroke_width,
151
+ code_string = code_string.expandtabs(tabsize=tab_width)
152
+
153
+ formatter = HtmlFormatter(
154
+ style=formatter_style,
155
+ noclasses=True,
156
+ cssclasses="",
347
157
  )
348
- for line_no in range(code.__len__()):
349
- line = code.chars[line_no]
350
- line_char_index = self.tab_spaces[line_no]
351
- for word_index in range(self.code_json[line_no].__len__()):
352
- line[
353
- line_char_index : line_char_index
354
- + self.code_json[line_no][word_index][0].__len__()
355
- ].set_color(self.code_json[line_no][word_index][1])
356
- line_char_index += self.code_json[line_no][word_index][0].__len__()
357
- return code
358
-
359
- def _gen_html_string(self):
360
- """Function to generate html string with code highlighted and stores in variable html_string."""
361
- self.html_string = _hilite_me(
362
- self.code_string,
363
- self.language,
364
- self.style,
365
- self.insert_line_no,
366
- "border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;",
367
- self.file_path,
368
- self.line_no_from,
158
+ soup = BeautifulSoup(
159
+ highlight(code_string, lexer, formatter), features="html.parser"
369
160
  )
370
-
371
- if self.generate_html_file:
372
- output_folder = Path() / "assets" / "codes" / "generated_html_files"
373
- output_folder.mkdir(parents=True, exist_ok=True)
374
- (output_folder / f"{self.file_name}.html").write_text(self.html_string)
375
-
376
- def _gen_code_json(self):
377
- """Function to background_color, generate code_json and tab_spaces from html_string.
378
- background_color is just background color of displayed code.
379
- code_json is 2d array with rows as line numbers
380
- and columns as a array with length 2 having text and text's color value.
381
- tab_spaces is 2d array with rows as line numbers
382
- and columns as corresponding number of indentation_chars in front of that line in code.
383
- """
384
- if (
385
- self.background_color == "#111111"
386
- or self.background_color == "#272822"
387
- or self.background_color == "#202020"
388
- or self.background_color == "#000000"
389
- ):
390
- self.default_color = "#ffffff"
391
- else:
392
- self.default_color = "#000000"
393
- # print(self.default_color,self.background_color)
394
- for i in range(3, -1, -1):
395
- self.html_string = self.html_string.replace("</" + " " * i, "</")
396
-
397
- # handle pygments bug
398
- # https://github.com/pygments/pygments/issues/961
399
- self.html_string = self.html_string.replace("<span></span>", "")
400
-
401
- for i in range(10, -1, -1):
402
- self.html_string = self.html_string.replace(
403
- "</span>" + " " * i,
404
- " " * i + "</span>",
405
- )
406
- self.html_string = self.html_string.replace("background-color:", "background:")
407
-
408
- if self.insert_line_no:
409
- start_point = self.html_string.find("</td><td><pre")
410
- start_point = start_point + 9
411
- else:
412
- start_point = self.html_string.find("<pre")
413
- self.html_string = self.html_string[start_point:]
414
- # print(self.html_string)
415
- lines = self.html_string.split("\n")
416
- lines = lines[0 : lines.__len__() - 2]
417
- start_point = lines[0].find(">")
418
- lines[0] = lines[0][start_point + 1 :]
419
- # print(lines)
420
- self.code_json = []
421
- self.tab_spaces = []
422
- code_json_line_index = -1
423
- for line_index in range(0, lines.__len__()):
424
- # print(lines[line_index])
425
- self.code_json.append([])
426
- code_json_line_index = code_json_line_index + 1
427
- if lines[line_index].startswith(self.indentation_chars):
428
- start_point = lines[line_index].find("<")
429
- starting_string = lines[line_index][:start_point]
430
- indentation_chars_count = lines[line_index][:start_point].count(
431
- self.indentation_chars,
432
- )
433
- if (
434
- starting_string.__len__()
435
- != indentation_chars_count * self.indentation_chars.__len__()
436
- ):
437
- lines[line_index] = (
438
- "\t" * indentation_chars_count
439
- + starting_string[
440
- starting_string.rfind(self.indentation_chars)
441
- + self.indentation_chars.__len__() :
442
- ]
443
- + lines[line_index][start_point:]
161
+ self._code_html = soup.find("pre")
162
+ assert isinstance(self._code_html, Tag)
163
+
164
+ # as we are using Paragraph to render the text, we need to find the character indices
165
+ # of the segments of changed color in the HTML code
166
+ color_ranges = []
167
+ current_line_color_ranges = []
168
+ current_line_char_index = 0
169
+ for child in self._code_html.children:
170
+ if child.name == "span":
171
+ try:
172
+ child_style = child["style"]
173
+ match_ = re.match(
174
+ r"color: (#[A-Fa-f0-9]{6}|#[A-Fa-f0-9]{3})", child_style
444
175
  )
445
- else:
446
- lines[line_index] = (
447
- "\t" * indentation_chars_count + lines[line_index][start_point:]
176
+ color = None if match_ is None else match_.group(1)
177
+ except KeyError:
178
+ color = None
179
+ current_line_color_ranges.append(
180
+ (
181
+ current_line_char_index,
182
+ current_line_char_index + len(child.text),
183
+ color,
448
184
  )
449
- indentation_chars_count = 0
450
- if lines[line_index]:
451
- while lines[line_index][indentation_chars_count] == "\t":
452
- indentation_chars_count = indentation_chars_count + 1
453
- self.tab_spaces.append(indentation_chars_count)
454
- # print(lines[line_index])
455
- lines[line_index] = self._correct_non_span(lines[line_index])
456
- # print(lines[line_index])
457
- words = lines[line_index].split("<span")
458
- for word_index in range(1, words.__len__()):
459
- color_index = words[word_index].find("color:")
460
- if color_index == -1:
461
- color = self.default_color
462
- else:
463
- starti = words[word_index][color_index:].find("#")
464
- color = words[word_index][
465
- color_index + starti : color_index + starti + 7
466
- ]
467
- start_point = words[word_index].find(">")
468
- end_point = words[word_index].find("</span>")
469
- text = words[word_index][start_point + 1 : end_point]
470
- text = html.unescape(text)
471
- if text != "":
472
- # print(text, "'" + color + "'")
473
- self.code_json[code_json_line_index].append([text, color])
474
- # print(self.code_json)
475
-
476
- def _correct_non_span(self, line_str: str):
477
- """Function put text color to those strings that don't have one according to background_color of displayed code.
478
-
479
- Parameters
480
- ---------
481
- line_str
482
- Takes a html element's string to put color to it according to background_color of displayed code.
483
-
484
- Returns
485
- -------
486
- :class:`str`
487
- The generated html element's string with having color attributes.
488
- """
489
- words = line_str.split("</span>")
490
- line_str = ""
491
- for i in range(0, words.__len__()):
492
- if i != words.__len__() - 1:
493
- j = words[i].find("<span")
185
+ )
186
+ current_line_char_index += len(child.text)
494
187
  else:
495
- j = words[i].__len__()
496
- temp = ""
497
- starti = -1
498
- for k in range(0, j):
499
- if words[i][k] == "\t" and starti == -1:
500
- continue
501
- else:
502
- if starti == -1:
503
- starti = k
504
- temp = temp + words[i][k]
505
- if temp != "":
506
- if i != words.__len__() - 1:
507
- temp = (
508
- '<span style="color:'
509
- + self.default_color
510
- + '">'
511
- + words[i][starti:j]
512
- + "</span>"
513
- )
514
- else:
515
- temp = (
516
- '<span style="color:'
517
- + self.default_color
518
- + '">'
519
- + words[i][starti:j]
188
+ for char in child.text:
189
+ if char == "\n":
190
+ color_ranges.append(current_line_color_ranges)
191
+ current_line_color_ranges = []
192
+ current_line_char_index = 0
193
+ else:
194
+ current_line_char_index += 1
195
+
196
+ color_ranges.append(current_line_color_ranges)
197
+ code_lines = self._code_html.get_text().removesuffix("\n").split("\n")
198
+
199
+ if paragraph_config is None:
200
+ paragraph_config = {}
201
+ base_paragraph_config = self.default_paragraph_config.copy()
202
+ base_paragraph_config.update(paragraph_config)
203
+
204
+ from manim.mobject.text.text_mobject import Paragraph
205
+
206
+ self.code_lines = Paragraph(
207
+ *code_lines,
208
+ **base_paragraph_config,
209
+ )
210
+ for line, color_range in zip(self.code_lines, color_ranges):
211
+ for start, end, color in color_range:
212
+ line[start:end].set_color(color)
213
+
214
+ if add_line_numbers:
215
+ base_paragraph_config.update({"alignment": "right"})
216
+ self.line_numbers = Paragraph(
217
+ *[
218
+ str(i)
219
+ for i in range(
220
+ line_numbers_from, line_numbers_from + len(self.code_lines)
520
221
  )
521
- temp = temp + words[i][j:]
522
- words[i] = temp
523
- if words[i] != "":
524
- line_str = line_str + words[i] + "</span>"
525
- return line_str
526
-
527
-
528
- def _hilite_me(
529
- code: str,
530
- language: str,
531
- style: str,
532
- insert_line_no: bool,
533
- divstyles: str,
534
- file_path: Path,
535
- line_no_from: int,
536
- ):
537
- """Function to highlight code from string to html.
222
+ ],
223
+ **base_paragraph_config,
224
+ )
225
+ self.line_numbers.next_to(self.code_lines, direction=LEFT).align_to(
226
+ self.code_lines, UP
227
+ )
228
+ self.add(self.line_numbers)
538
229
 
539
- Parameters
540
- ---------
541
- code
542
- Code string.
543
- language
544
- The name of the programming language the given code was written in.
545
- style
546
- Code style name.
547
- insert_line_no
548
- Defines whether line numbers should be inserted in the html file.
549
- divstyles
550
- Some html css styles.
551
- file_path
552
- Path of code file.
553
- line_no_from
554
- Defines the first line's number in the line count.
555
- """
556
- style = style or "colorful"
557
- defstyles = "overflow:auto;width:auto;"
558
-
559
- formatter = HtmlFormatter(
560
- style=style,
561
- linenos=False,
562
- noclasses=True,
563
- cssclass="",
564
- cssstyles=defstyles + divstyles,
565
- prestyles="margin: 0",
566
- )
567
- if language is None and file_path:
568
- lexer = guess_lexer_for_filename(file_path, code)
569
- html = highlight(code, lexer, formatter)
570
- elif language is None:
571
- raise ValueError(
572
- "The code language has to be specified when rendering a code string",
573
- )
574
- else:
575
- html = highlight(code, get_lexer_by_name(language, **{}), formatter)
576
- if insert_line_no:
577
- html = _insert_line_numbers_in_html(html, line_no_from)
578
- html = "<!-- HTML generated by Code() -->" + html
579
- return html
230
+ for line in self.code_lines:
231
+ line.submobjects = [c for c in line if not isinstance(c, Dot)]
232
+ self.add(self.code_lines)
580
233
 
234
+ if background_config is None:
235
+ background_config = {}
236
+ background_config_base = self.default_background_config.copy()
237
+ background_config_base.update(background_config)
581
238
 
582
- def _insert_line_numbers_in_html(html: str, line_no_from: int):
583
- """Function that inserts line numbers in the highlighted HTML code.
239
+ if background == "rectangle":
240
+ self.background = SurroundingRectangle(
241
+ self,
242
+ **background_config_base,
243
+ )
244
+ elif background == "window":
245
+ buttons = VGroup(
246
+ Dot(radius=0.1, stroke_width=0, color=button_color)
247
+ for button_color in ["#ff5f56", "#ffbd2e", "#27c93f"]
248
+ ).arrange(RIGHT, buff=0.1)
249
+ buttons.next_to(self, UP, buff=0.1).align_to(self, LEFT).shift(LEFT * 0.1)
250
+ self.background = SurroundingRectangle(
251
+ VGroup(self, buttons),
252
+ **background_config_base,
253
+ )
254
+ buttons.shift(UP * 0.1 + LEFT * 0.1)
255
+ self.background.add(buttons)
256
+ else:
257
+ raise ValueError(f"Unknown background type: {background}")
584
258
 
585
- Parameters
586
- ---------
587
- html
588
- html string of highlighted code.
589
- line_no_from
590
- Defines the first line's number in the line count.
591
-
592
- Returns
593
- -------
594
- :class:`str`
595
- The generated html string with having line numbers.
596
- """
597
- match = re.search("(<pre[^>]*>)(.*)(</pre>)", html, re.DOTALL)
598
- if not match:
599
- return html
600
- pre_open = match.group(1)
601
- pre = match.group(2)
602
- pre_close = match.group(3)
603
-
604
- html = html.replace(pre_close, "</pre></td></tr></table>")
605
- numbers = range(line_no_from, line_no_from + pre.count("\n") + 1)
606
- format_lines = "%" + str(len(str(numbers[-1]))) + "i"
607
- lines = "\n".join(format_lines % i for i in numbers)
608
- html = html.replace(
609
- pre_open,
610
- "<table><tr><td>" + pre_open + lines + "</pre></td><td>" + pre_open,
611
- )
612
- return html
259
+ self.add_to_back(self.background)
260
+
261
+ @classmethod
262
+ def get_styles_list(cls) -> list[str]:
263
+ """Get the list of all available formatter styles."""
264
+ if cls._styles_list_cache is None:
265
+ cls._styles_list_cache = list(get_all_styles())
266
+ return cls._styles_list_cache