manim 0.18.1__py3-none-any.whl → 0.19.0__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.

Potentially problematic release.


This version of manim might be problematic. Click here for more details.

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