yerba 0.0.1a0.dev0__tar.gz → 0.0.2a0.dev0__tar.gz

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 (30) hide show
  1. {yerba-0.0.1a0.dev0/yerba.egg-info → yerba-0.0.2a0.dev0}/PKG-INFO +17 -5
  2. {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/README.md +16 -4
  3. {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/pyproject.toml +7 -1
  4. {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0/yerba.egg-info}/PKG-INFO +17 -5
  5. yerba-0.0.2a0.dev0/yerba.egg-info/SOURCES.txt +9 -0
  6. yerba-0.0.2a0.dev0/yerba.egg-info/entry_points.txt +2 -0
  7. yerba-0.0.2a0.dev0/yerba.egg-info/top_level.txt +1 -0
  8. yerba-0.0.1a0.dev0/yerba/__init__.py +0 -0
  9. yerba-0.0.1a0.dev0/yerba/__main__.py +0 -29
  10. yerba-0.0.1a0.dev0/yerba/base/box.py +0 -342
  11. yerba-0.0.1a0.dev0/yerba/base/image.py +0 -137
  12. yerba-0.0.1a0.dev0/yerba/base/presentation.py +0 -202
  13. yerba-0.0.1a0.dev0/yerba/base/ptext.py +0 -48
  14. yerba-0.0.1a0.dev0/yerba/base/slide.py +0 -250
  15. yerba-0.0.1a0.dev0/yerba/base/templates.py +0 -292
  16. yerba-0.0.1a0.dev0/yerba/defaults.py +0 -56
  17. yerba-0.0.1a0.dev0/yerba/main_rutine.py +0 -248
  18. yerba-0.0.1a0.dev0/yerba/properties.py +0 -39
  19. yerba-0.0.1a0.dev0/yerba/templates/basic.py +0 -5
  20. yerba-0.0.1a0.dev0/yerba/templates/nice.py +0 -87
  21. yerba-0.0.1a0.dev0/yerba/utils/constants.py +0 -25
  22. yerba-0.0.1a0.dev0/yerba/utils/latex.py +0 -123
  23. yerba-0.0.1a0.dev0/yerba/utils/others.py +0 -102
  24. yerba-0.0.1a0.dev0/yerba/utils/parser.py +0 -127
  25. yerba-0.0.1a0.dev0/yerba.egg-info/SOURCES.txt +0 -25
  26. yerba-0.0.1a0.dev0/yerba.egg-info/top_level.txt +0 -1
  27. {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/LICENSE +0 -0
  28. {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/setup.cfg +0 -0
  29. {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/yerba.egg-info/dependency_links.txt +0 -0
  30. {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/yerba.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: yerba
3
- Version: 0.0.1a0.dev0
3
+ Version: 0.0.2a0.dev0
4
4
  Author-email: "Bernardo L. Español" <esp.bernardo@gmail.com>
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown
@@ -14,7 +14,12 @@ Requires-Dist: numpy
14
14
  Requires-Dist: pillow
15
15
  Requires-Dist: pyyaml
16
16
 
17
- # Yerba
17
+ <p align="center">
18
+ <a href="https://github.com/bersp/yerba">
19
+ <img src="https://raw.githubusercontent.com/bersp/yerba/main/logo/Yerba.png">
20
+ </a>
21
+ </p>
22
+ <br />
18
23
 
19
24
  Yerba is a Python CLI application that facilitates creating presentations by writing files in markdown format. The goal is to build an application that allows for creating aesthetically pleasing presentations in a short amount of time, while also being highly customizable and versatile.
20
25
 
@@ -58,10 +63,17 @@ You can check if you have it installed by running the following command:
58
63
  rsvg-convert --version
59
64
  ```
60
65
 
61
- Finally, you can install Yerba, for now, from TestPyPI:
66
+ Finally, you can install Yerba from PyPI:
62
67
  ```bash
63
- python -m pip install -i https://test.pypi.org/simple/ yerba
68
+ python -m pip install yerba
64
69
  ```
65
70
 
66
71
  ## Usage
67
- This section is a work in progress. For now, check [usage_example.md](examples/usage_example.md) (use "code" view mode) and [usage_example.pdf](examples/usage_example.pdf) to give you and idea on how to use Yerba.
72
+
73
+ This section is a work in progress. For now, check [usage_example.md](examples/usage_example.md) (use "code" view mode) and [usage_example.pdf](examples/usage_example.pdf) to give you and idea on how to use Yerba. You can render this presentation using:
74
+ ```
75
+ yerba usage_example.md
76
+ # or
77
+ python -m yerba usage_example.md
78
+ ```
79
+ ---
@@ -1,4 +1,9 @@
1
- # Yerba
1
+ <p align="center">
2
+ <a href="https://github.com/bersp/yerba">
3
+ <img src="https://raw.githubusercontent.com/bersp/yerba/main/logo/Yerba.png">
4
+ </a>
5
+ </p>
6
+ <br />
2
7
 
3
8
  Yerba is a Python CLI application that facilitates creating presentations by writing files in markdown format. The goal is to build an application that allows for creating aesthetically pleasing presentations in a short amount of time, while also being highly customizable and versatile.
4
9
 
@@ -42,10 +47,17 @@ You can check if you have it installed by running the following command:
42
47
  rsvg-convert --version
43
48
  ```
44
49
 
45
- Finally, you can install Yerba, for now, from TestPyPI:
50
+ Finally, you can install Yerba from PyPI:
46
51
  ```bash
47
- python -m pip install -i https://test.pypi.org/simple/ yerba
52
+ python -m pip install yerba
48
53
  ```
49
54
 
50
55
  ## Usage
51
- This section is a work in progress. For now, check [usage_example.md](examples/usage_example.md) (use "code" view mode) and [usage_example.pdf](examples/usage_example.pdf) to give you and idea on how to use Yerba.
56
+
57
+ This section is a work in progress. For now, check [usage_example.md](examples/usage_example.md) (use "code" view mode) and [usage_example.pdf](examples/usage_example.pdf) to give you and idea on how to use Yerba. You can render this presentation using:
58
+ ```
59
+ yerba usage_example.md
60
+ # or
61
+ python -m yerba usage_example.md
62
+ ```
63
+ ---
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "yerba"
6
- version = "0.0.1-alpha-dev0"
6
+ version = "0.0.2-alpha-dev0"
7
7
  dependencies = [
8
8
  "manim",
9
9
  "manim-mobject-svg",
@@ -20,3 +20,9 @@ authors = [
20
20
  ]
21
21
  readme = "README.md"
22
22
  license = {file = "LICENSE.txt"}
23
+
24
+ [project.scripts]
25
+ yerba = "yerba.__main__:cli_entry"
26
+
27
+ [tool.setuptools]
28
+ py-modules = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: yerba
3
- Version: 0.0.1a0.dev0
3
+ Version: 0.0.2a0.dev0
4
4
  Author-email: "Bernardo L. Español" <esp.bernardo@gmail.com>
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown
@@ -14,7 +14,12 @@ Requires-Dist: numpy
14
14
  Requires-Dist: pillow
15
15
  Requires-Dist: pyyaml
16
16
 
17
- # Yerba
17
+ <p align="center">
18
+ <a href="https://github.com/bersp/yerba">
19
+ <img src="https://raw.githubusercontent.com/bersp/yerba/main/logo/Yerba.png">
20
+ </a>
21
+ </p>
22
+ <br />
18
23
 
19
24
  Yerba is a Python CLI application that facilitates creating presentations by writing files in markdown format. The goal is to build an application that allows for creating aesthetically pleasing presentations in a short amount of time, while also being highly customizable and versatile.
20
25
 
@@ -58,10 +63,17 @@ You can check if you have it installed by running the following command:
58
63
  rsvg-convert --version
59
64
  ```
60
65
 
61
- Finally, you can install Yerba, for now, from TestPyPI:
66
+ Finally, you can install Yerba from PyPI:
62
67
  ```bash
63
- python -m pip install -i https://test.pypi.org/simple/ yerba
68
+ python -m pip install yerba
64
69
  ```
65
70
 
66
71
  ## Usage
67
- This section is a work in progress. For now, check [usage_example.md](examples/usage_example.md) (use "code" view mode) and [usage_example.pdf](examples/usage_example.pdf) to give you and idea on how to use Yerba.
72
+
73
+ This section is a work in progress. For now, check [usage_example.md](examples/usage_example.md) (use "code" view mode) and [usage_example.pdf](examples/usage_example.pdf) to give you and idea on how to use Yerba. You can render this presentation using:
74
+ ```
75
+ yerba usage_example.md
76
+ # or
77
+ python -m yerba usage_example.md
78
+ ```
79
+ ---
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ yerba.egg-info/PKG-INFO
5
+ yerba.egg-info/SOURCES.txt
6
+ yerba.egg-info/dependency_links.txt
7
+ yerba.egg-info/entry_points.txt
8
+ yerba.egg-info/requires.txt
9
+ yerba.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ yerba = yerba.__main__:cli_entry
File without changes
@@ -1,29 +0,0 @@
1
- import sys
2
- import os
3
- from .main_rutine import MainRutine
4
- from manim import logger
5
-
6
-
7
- def main():
8
- if len(sys.argv) == 1:
9
- logger.error("You should specify an input filename")
10
- quit()
11
- elif len(sys.argv) == 2:
12
- filename = sys.argv[1]
13
- if os.path.exists(filename):
14
- pass
15
- elif os.path.exists(filename+".md"):
16
- filename = filename+".md"
17
- else:
18
- logger.error(f"File '{filename}' not found")
19
- quit()
20
- else:
21
- logger.error("Too many args")
22
- quit()
23
-
24
- main_rutine = MainRutine(filename)
25
- main_rutine.run()
26
-
27
-
28
- if __name__ == "__main__":
29
- main()
@@ -1,342 +0,0 @@
1
- from __future__ import annotations
2
- import numpy as np
3
- from manim import VMobject, VGroup, Rectangle
4
-
5
- from ..defaults import box_params, colors
6
- from ..utils.constants import (
7
- UP, DOWN, LEFT, RIGHT, ORIGIN, SLIDE_HEIGHT, SLIDE_WIDTH,
8
- LEFT_EDGE, RIGHT_EDGE, SLIDE_X_RAD, SLIDE_Y_RAD, TOP_EDGE, BOTTOM_EDGE
9
- )
10
- from ..utils.others import (restructure_list_to_exclude_certain_family_members,
11
- replace_in_list)
12
-
13
-
14
- class Box():
15
- def __init__(self, center: np.ndarray,
16
- width: float, height: float, arrange: str | None = "top left",
17
- arrange_buff: float = box_params["arrange_buff"],
18
- is_null: bool = False):
19
- self.center = center
20
- self.width = width
21
- self.height = height
22
- self.arrange = None if arrange == "none" else arrange
23
- self.arrange_buff = arrange_buff
24
- self.is_null = is_null
25
-
26
- self.grid: dict[str, Box] | None = None
27
- self.mobjects: list = []
28
-
29
- @classmethod
30
- def from_vertex(cls, tl_vertex: tuple[float, float],
31
- br_vertex: tuple[float, float], **kwargs):
32
- top_edge, left_edge = tl_vertex
33
- bottom_edge, right_edge = br_vertex
34
- center = np.array(
35
- [(right_edge+left_edge)/2, (top_edge+bottom_edge)/2, 0])
36
- width = right_edge-left_edge
37
- height = top_edge-bottom_edge
38
- return cls(center, width, height, **kwargs)
39
-
40
- @classmethod
41
- def get_full_box(cls, arrange):
42
- return cls(ORIGIN, SLIDE_WIDTH, SLIDE_HEIGHT, arrange=arrange)
43
-
44
- @classmethod
45
- def get_left_box(cls, width, *, arrange):
46
- return cls(LEFT_EDGE+RIGHT*width/2, width, SLIDE_HEIGHT,
47
- arrange=arrange)
48
-
49
- @classmethod
50
- def get_right_box(cls, width, *, arrange):
51
- return cls(RIGHT_EDGE+LEFT*width/2, width, SLIDE_HEIGHT,
52
- arrange=arrange)
53
-
54
- @classmethod
55
- def get_top_box(cls, height, *, arrange):
56
- return cls(TOP_EDGE+DOWN*height/2, SLIDE_WIDTH, height,
57
- arrange=arrange)
58
-
59
- @classmethod
60
- def get_bottom_box(cls, height, *, arrange):
61
- return cls(BOTTOM_EDGE+UP*height/2, SLIDE_WIDTH, height,
62
- arrange=arrange)
63
-
64
- @classmethod
65
- def get_inner_box(cls, *, left_box=None, right_box=None,
66
- top_box=None, bottom_box=None, arrange):
67
-
68
- lg = SLIDE_X_RAD + left_box.center[0] + left_box.width/2 \
69
- if left_box else 0
70
- rg = SLIDE_X_RAD - right_box.center[0] + right_box.width/2 \
71
- if right_box else 0
72
- tg = SLIDE_Y_RAD - top_box.center[1] + top_box.height/2 \
73
- if top_box else 0
74
- bg = SLIDE_Y_RAD + bottom_box.center[1] + bottom_box.height/2 \
75
- if bottom_box else 0
76
-
77
- box = cls.get_full_box(arrange)
78
- box.shrink(left_gap=lg, right_gap=rg, top_gap=tg, bottom_gap=bg)
79
-
80
- return box
81
-
82
- @classmethod
83
- def get_null_box(cls):
84
- return cls(ORIGIN, SLIDE_WIDTH, SLIDE_HEIGHT, is_null=True)
85
-
86
- @staticmethod
87
- def merge_boxes(boxes, arrange):
88
- if len(boxes) == 1:
89
- return boxes[0]
90
- left_edge = min([b.center[0]-b.width/2 for b in boxes])
91
- right_edge = max([b.center[0]+b.width/2 for b in boxes])
92
- top_edge = max([b.center[1]+b.height/2 for b in boxes])
93
- bottom_edge = min([b.center[1]-b.height/2 for b in boxes])
94
-
95
- return Box.from_vertex(tl_vertex=(top_edge, left_edge),
96
- br_vertex=(bottom_edge, right_edge),
97
- arrange=arrange)
98
-
99
- def add(self, mobject) -> None:
100
- if hasattr(mobject, "width_units") and mobject.width_units == "box":
101
- mobject.set(width=self.width*mobject.width)
102
- if hasattr(mobject, "height_units") and mobject.height_units == "box":
103
- mobject.set(height=self.height*mobject.height)
104
-
105
- self.mobjects.append(mobject)
106
-
107
- def remove(self, mobjects):
108
- """Remove specified mobjects from the box."""
109
- new_l = restructure_list_to_exclude_certain_family_members(
110
- self.mobjects, mobjects)
111
- self.mobjects = new_l
112
-
113
- def replace(self, old_mo, new_mo):
114
- """Remove specified mobjects from the box."""
115
- replace_in_list(self.mobjects, old_mo, new_mo)
116
-
117
- def remove_all_mobjects(self) -> None:
118
- self.mobjects = []
119
-
120
- def auto_arrange(self) -> None:
121
- """TODO(bersp): DOC"""
122
- if self.arrange is None or len(self.mobjects) == 0 or self.is_null:
123
- return
124
-
125
- centers = [mo.get_center() for mo in self.mobjects]
126
-
127
- if self.arrange == "center":
128
- (VGroup(*self.mobjects)
129
- .arrange(direction=DOWN, buff=self.arrange_buff)
130
- .move_to(self.center))
131
- elif self.arrange == "relative center":
132
- (VGroup(*self.mobjects)
133
- .move_to(self.center))
134
- elif self.arrange == "top center":
135
- (VGroup(*self.mobjects)
136
- .arrange(DOWN, buff=self.arrange_buff)
137
- .next_to(self.get_top(), DOWN, buff=0))
138
- elif self.arrange == "top left":
139
- (VGroup(*self.mobjects)
140
- .arrange(DOWN, buff=self.arrange_buff, aligned_edge=LEFT)
141
- .next_to(self.get_corner(LEFT, UP), DOWN+RIGHT, buff=0))
142
- elif self.arrange == "top right":
143
- (VGroup(*self.mobjects)
144
- .arrange(DOWN, buff=self.arrange_buff, aligned_edge=RIGHT)
145
- .next_to(self.get_corner(RIGHT, UP), DOWN+LEFT, buff=0))
146
- elif self.arrange == "center left":
147
- (VGroup(*self.mobjects)
148
- .arrange(DOWN, buff=self.arrange_buff, aligned_edge=LEFT)
149
- .next_to(self.get_left(), RIGHT, buff=0))
150
- elif self.arrange == "center right":
151
- (VGroup(*self.mobjects)
152
- .arrange(DOWN, buff=self.arrange_buff, aligned_edge=RIGHT)
153
- .next_to(self.get_right(), LEFT, buff=0))
154
- else:
155
- ValueError(f"'arrange' {self.arrange!r} is not defined")
156
-
157
- for cent, mo in zip(centers, self.mobjects):
158
-
159
- if hasattr(mo, 'box_arrange'):
160
- if mo.box_arrange == "center":
161
- mo.move_to(self.center)
162
- elif mo.box_arrange == "hcenter":
163
- mo.set_x(self.center[0])
164
- elif mo.box_arrange == "vcenter":
165
- mo.set_y(self.center[1])
166
- else:
167
- ValueError(
168
- f"'box arrange' {mo.box_arrange!r} is not defined")
169
-
170
- mo.shift(cent)
171
-
172
- def def_grid(self, grid, hspace=0.5, vspace=0.5,
173
- width_ratios=None, height_ratios=None,
174
- arrange="self") -> dict[str, Box]:
175
- """
176
- TODO(bersp): Handle errors (bad defined grid)
177
- """
178
-
179
- if arrange == "self":
180
- arrange = self.arrange
181
-
182
- grid = np.asarray(grid)
183
- nrows, ncols = grid.shape
184
-
185
- if width_ratios is None:
186
- width_ratios = np.asarray([1]*ncols)/ncols
187
- else:
188
- width_ratios = np.asarray(width_ratios)
189
- width_ratios = width_ratios/width_ratios.sum()
190
-
191
- if height_ratios is None:
192
- height_ratios = np.asarray([1]*nrows)/nrows
193
- else:
194
- height_ratios = np.asarray(height_ratios)
195
- height_ratios = height_ratios/height_ratios.sum()
196
-
197
- grid_widths = width_ratios * (self.width-hspace*(ncols-1))
198
- grid_heights = height_ratios * (self.height-vspace*(nrows-1))
199
-
200
- centers_x = []
201
- cx = -self.width/2 - hspace
202
- for gw in grid_widths:
203
- cx += gw/2 + hspace
204
- centers_x.append(cx)
205
- cx += gw/2
206
-
207
- centers_y = []
208
- cy = self.height/2 + hspace
209
- for gh in grid_heights:
210
- cy -= gh/2 + hspace
211
- centers_y.append(cy)
212
- cy -= gh/2
213
-
214
- x0, y0, _ = self.center
215
- subgrid = np.zeros(shape=(nrows, ncols), dtype='object')
216
- for row, (cy, gh) in enumerate(zip(centers_y, grid_heights)):
217
- for col, (cx, gw) in enumerate(zip(centers_x, grid_widths)):
218
- subgrid[row, col] = Box(center=np.array((x0+cx, y0+cy, 0)),
219
- height=gh, width=gw, arrange=arrange)
220
-
221
- self.grid = {}
222
- for label in np.unique(grid):
223
- self.grid[label] = Box.merge_boxes(
224
- subgrid[np.where(grid == label)], arrange=arrange
225
- )
226
-
227
- return self.grid
228
-
229
- def get_bbox_mo(self, color=colors["BLACK"]) -> VMobject:
230
- r = (
231
- Rectangle(height=self.height, width=self.width,
232
- fill_color=color, fill_opacity=0.2)
233
- .set_stroke(color=color, opacity=0.0)
234
- .move_to(self.center)
235
- .set_z_index(-1)
236
- )
237
-
238
- return r
239
-
240
- def get_bbox_grid(self, color=colors["RED"]) -> VMobject:
241
- if self.grid is None:
242
- raise ValueError(f"{self!r} has no defined grid")
243
-
244
- o = VGroup()
245
- for g in self.grid.values():
246
- o.add(g.get_bbox_mo(color=color))
247
-
248
- return o
249
-
250
- def shrink(self, *, left_gap=0, right_gap=0,
251
- top_gap=0, bottom_gap=0) -> Box:
252
- self.width = self.width - (left_gap + right_gap)
253
- self.height = self.height - (top_gap + bottom_gap)
254
- self.center = np.array([self.center[0] + (left_gap - right_gap)/2,
255
- self.center[1] + (bottom_gap - top_gap)/2,
256
- 0])
257
- return self
258
-
259
- # ---
260
-
261
- def set_arrange(self, value):
262
- self.arrange = None if value == "none" else value
263
- return self
264
-
265
- def set_arrange_buff(self, value):
266
- self.arrange_buff = value
267
- return self
268
-
269
- # ---
270
-
271
- def get_left(self):
272
- return self.center + self.width/2*LEFT
273
-
274
- def get_right(self):
275
- return self.center + self.width/2*RIGHT
276
-
277
- def get_top(self):
278
- return self.center + self.height/2*UP
279
-
280
- def get_bottom(self):
281
- return self.center + self.height/2*DOWN
282
-
283
- def get_corner(self, x_direction, y_direction):
284
- return self.center+self.width/2*x_direction+self.height/2*y_direction
285
-
286
- # ---
287
-
288
- def __eq__(self, other_box):
289
- return (np.all(self.center == other_box.center) and
290
- self.width == other_box.width and
291
- self.height == other_box.height and
292
- self.arrange == other_box.arrange and
293
- self.arrange_buff == other_box.arrange_buff)
294
-
295
- def __repr__(self):
296
- c = f"[{self.center[0]:.2g}, {self.center[1]:.2g}]"
297
- return f"Box(center={c}, width={self.width:.2g}, height={self.height:.2g}, arrange={self.arrange}, is_null={self.is_null})"
298
-
299
-
300
- class NamedBoxes:
301
- def __init__(self, **boxes):
302
- """
303
- Parameters
304
- ----------
305
- **boxes : dict
306
- Named boxes
307
- """
308
- for name, box in boxes.items():
309
- self.add(name, box)
310
-
311
- def add(self, name, box):
312
- setattr(self, name, box)
313
-
314
- def set_current_box(self, box_or_box_name):
315
- """
316
- TODO(bersp): Documentation.
317
- TODO(bersp): Handle errors.
318
- """
319
-
320
- if isinstance(box_or_box_name, Box):
321
- setattr(self, "active", box_or_box_name)
322
- else:
323
- setattr(self, "active", self.__dict__[box_or_box_name])
324
-
325
- def remove_all_mobjects(self):
326
- """Remove all mobjects from all boxes in the slide layout."""
327
- for _, box in self.__dict__.items():
328
- box.remove_all_mobjects()
329
-
330
- def get_bbox_mo(self):
331
- """Return a VGroup containing all the bounding boxes of the all boxes."""
332
- o = VGroup()
333
- for _, box in self.__dict__.items():
334
- o += box.get_bbox_mo()
335
- return o
336
-
337
- def __repr__(self):
338
- s = ''
339
- for name, box in self.__dict__.items():
340
- s += f"{name} -> {box}\n"
341
-
342
- return s
@@ -1,137 +0,0 @@
1
- import os
2
- import re
3
- import subprocess
4
- import shutil
5
- from manim import VGroup, Rectangle
6
- from xml.etree import ElementTree
7
- from PIL import Image
8
-
9
- from ..utils.constants import SLIDE_X_RAD, SLIDE_Y_RAD, TO_PX, UL
10
- from ..defaults import colors
11
- from ..base.ptext import Ptex
12
-
13
-
14
- class ImageSvgBase(VGroup):
15
- # TODO(bersp): Implement .rotate to imgs
16
- def __init__(self, filename, width, height, draft_mode):
17
-
18
- self.filename = filename
19
- self.basename = os.path.basename(filename)
20
- self.draft_mode = draft_mode
21
-
22
- rec = (Rectangle(width=width, height=height)
23
- .set_stroke(opacity=0)
24
- .set_fill(color=colors["BLACK"], opacity=0.8))
25
-
26
- # TODO(bersp): Change this to Ptex
27
- super().__init__(
28
- rec,
29
- Ptex(filename, color=colors["BLACK"]).move_to(
30
- rec).set(width=width*0.9)
31
- )
32
-
33
- def _manim_to_svg_coords(self):
34
- x0, y0 = self.get_corner(UL)[:2]
35
- x0 = (SLIDE_X_RAD+x0)*TO_PX
36
- y0 = (SLIDE_Y_RAD-y0)*TO_PX
37
- w = self.width*TO_PX
38
- h = self.height*TO_PX
39
-
40
- return x0, y0, w, h
41
-
42
- def _get_width_and_height(self, width, height,
43
- file_width, file_height):
44
-
45
- ERR = ValueError(
46
- r"width or height must be numbers or percentages like '90%'"
47
- )
48
-
49
- porcentage_pattern = r"(^\d+(\.\d+)?)%$"
50
-
51
- if isinstance(width, str):
52
- m = re.match(porcentage_pattern, width)
53
- if m:
54
- self.width_units = "box"
55
- width = float(m[1])/100
56
- else:
57
- raise ERR
58
-
59
- if isinstance(height, str):
60
- m = re.match(porcentage_pattern, height)
61
- if m:
62
- self.height_units = "box"
63
- height = float(m[1])/100
64
- else:
65
- raise ERR
66
-
67
- if not ((isinstance(width, (int, float)) or width is None) and
68
- (isinstance(height, (int, float)) or height is None)):
69
- raise ERR
70
-
71
- if width is not None and height is None:
72
- height = file_height/file_width*width
73
- elif width is None and height is not None:
74
- width = file_width/file_height*height
75
- else:
76
- raise ValueError(
77
- "You have to specify either 'width' or 'height', not both")
78
-
79
- return width, height
80
-
81
-
82
- class ImageSvg(ImageSvgBase):
83
- def __init__(self, filename, width=None, height=None,
84
- draft_mode=False):
85
-
86
- fw, fh = Image.open(filename).size
87
- width, height = self._get_width_and_height(width, height, fw, fh)
88
-
89
- super().__init__(filename, width, height, draft_mode)
90
-
91
- def get_svg_str(self):
92
- x0, y0, w, h = self._manim_to_svg_coords()
93
- img_base64 = (
94
- subprocess.run(["base64", self.filename],
95
- stdout=subprocess.PIPE)
96
- .stdout.decode("utf-8").replace("\n", "")
97
- )
98
- s = (f'<image width="{w}" height="{h}" x="{x0}" y="{y0}" '
99
- f'href="data:image/png;base64,{img_base64}"/>')
100
-
101
- return s
102
-
103
-
104
- class ImagePDFSvg(ImageSvgBase):
105
- def __init__(self, filename, width=None, height=None,
106
- draft_mode=False):
107
- if not shutil.which("inkscape"):
108
- raise FileNotFoundError("Inkscape is required to use PDF images.")
109
-
110
- svg_str_raw = self._get_svg_str_raw(filename)
111
- self.xml_tree = ElementTree.fromstring(svg_str_raw)
112
-
113
- fw = float(self.xml_tree.get('width') or 1)
114
- fh = float(self.xml_tree.get('height') or 1)
115
- width, height = self._get_width_and_height(width, height, fw, fh)
116
-
117
- super().__init__(filename, width, height, draft_mode)
118
-
119
- def get_svg_str(self):
120
- x0, y0, w, h = self._manim_to_svg_coords()
121
-
122
- self.xml_tree.set("x", str(x0))
123
- self.xml_tree.set("y", str(y0))
124
- self.xml_tree.set("width", str(w))
125
- self.xml_tree.set("height", str(h))
126
-
127
- s = ElementTree.tostring(self.xml_tree, encoding='unicode')
128
- return s
129
-
130
- def _get_svg_str_raw(self, filename):
131
- s = subprocess.run(["inkscape", "--export-plain-svg",
132
- "--export-type=svg", "--export-filename=-",
133
- filename],
134
- stdout=subprocess.PIPE, stderr=open(os.devnull, "w")
135
- ).stdout.decode("utf-8")
136
-
137
- return s