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