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.
- {yerba-0.0.1a0.dev0/yerba.egg-info → yerba-0.0.2a0.dev0}/PKG-INFO +17 -5
- {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/README.md +16 -4
- {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/pyproject.toml +7 -1
- {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0/yerba.egg-info}/PKG-INFO +17 -5
- yerba-0.0.2a0.dev0/yerba.egg-info/SOURCES.txt +9 -0
- yerba-0.0.2a0.dev0/yerba.egg-info/entry_points.txt +2 -0
- yerba-0.0.2a0.dev0/yerba.egg-info/top_level.txt +1 -0
- yerba-0.0.1a0.dev0/yerba/__init__.py +0 -0
- yerba-0.0.1a0.dev0/yerba/__main__.py +0 -29
- yerba-0.0.1a0.dev0/yerba/base/box.py +0 -342
- yerba-0.0.1a0.dev0/yerba/base/image.py +0 -137
- yerba-0.0.1a0.dev0/yerba/base/presentation.py +0 -202
- yerba-0.0.1a0.dev0/yerba/base/ptext.py +0 -48
- yerba-0.0.1a0.dev0/yerba/base/slide.py +0 -250
- yerba-0.0.1a0.dev0/yerba/base/templates.py +0 -292
- yerba-0.0.1a0.dev0/yerba/defaults.py +0 -56
- yerba-0.0.1a0.dev0/yerba/main_rutine.py +0 -248
- yerba-0.0.1a0.dev0/yerba/properties.py +0 -39
- yerba-0.0.1a0.dev0/yerba/templates/basic.py +0 -5
- yerba-0.0.1a0.dev0/yerba/templates/nice.py +0 -87
- yerba-0.0.1a0.dev0/yerba/utils/constants.py +0 -25
- yerba-0.0.1a0.dev0/yerba/utils/latex.py +0 -123
- yerba-0.0.1a0.dev0/yerba/utils/others.py +0 -102
- yerba-0.0.1a0.dev0/yerba/utils/parser.py +0 -127
- yerba-0.0.1a0.dev0/yerba.egg-info/SOURCES.txt +0 -25
- yerba-0.0.1a0.dev0/yerba.egg-info/top_level.txt +0 -1
- {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/LICENSE +0 -0
- {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/setup.cfg +0 -0
- {yerba-0.0.1a0.dev0 → yerba-0.0.2a0.dev0}/yerba.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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
|
-
|
|
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
|
|
66
|
+
Finally, you can install Yerba from PyPI:
|
|
62
67
|
```bash
|
|
63
|
-
python -m pip install
|
|
68
|
+
python -m pip install yerba
|
|
64
69
|
```
|
|
65
70
|
|
|
66
71
|
## Usage
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
50
|
+
Finally, you can install Yerba from PyPI:
|
|
46
51
|
```bash
|
|
47
|
-
python -m pip install
|
|
52
|
+
python -m pip install yerba
|
|
48
53
|
```
|
|
49
54
|
|
|
50
55
|
## Usage
|
|
51
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
66
|
+
Finally, you can install Yerba from PyPI:
|
|
62
67
|
```bash
|
|
63
|
-
python -m pip install
|
|
68
|
+
python -m pip install yerba
|
|
64
69
|
```
|
|
65
70
|
|
|
66
71
|
## Usage
|
|
67
|
-
|
|
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 @@
|
|
|
1
|
+
|
|
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
|