nelsie
1from .basictypes import AlignItems, AlignContent, FlexWrap, Stroke, TextAlign 2from .insteps import InSteps 3from .shapes import Arrow, Path 4from .slidedeck import Resources, SlideDeck, Slide 5from .textstyle import FontStretch, TextStyle 6from .box import Box, BoxBuilder 7 8__all__ = [ 9 "SlideDeck", 10 "InSteps", 11 "Path", 12 "Stroke", 13 "TextStyle", 14 "Resources", 15 "FontStretch", 16 "TextAlign", 17 "FlexWrap", 18 "AlignItems", 19 "AlignContent", 20 "Arrow", 21 "BoxBuilder", 22 "Box", 23 "Slide", 24]
119class SlideDeck: 120 def __init__( 121 self, 122 *, 123 width: float = 1024, 124 height: float = 768, 125 bg_color: str = "white", 126 image_directory: str | None = None, 127 resources: Resources | None = None, 128 default_font: str = "sans-serif", 129 default_monospace_font: str = "monospace", 130 default_code_theme: str = "InspiredGitHub", 131 default_code_language: str | None = None, 132 ): 133 """ 134 A top-level class of Nelsie. It represents a set of slides. 135 136 Arguments: 137 * width - default width of a slide (could be overriden for each slide) 138 * height - default width of a slide (could be overriden for each slide) 139 * bg_color - default background color a slide (could be overriden for each slide) 140 * image_directory - default path where images are searched for (could be overriden for each slide) 141 * resource - Resource instance, if None a new instance is created 142 * default_font - Name of default font 143 * default_monospace_font - Name of the default monospace font 144 * default_code_theme - Name of default theme for syntax highlighting (.code() method): 145 Available themes: 146 * "base16-ocean.dark" 147 * "base16-eighties.dark" 148 * "base16-mocha.dark" 149 * "base16-ocean.light" 150 * "InspiredGitHub" 151 * "Solarized (dark)" 152 * "Solarized (light)" 153 * default_code_language - Default language to use for syntax highlighting (.code() method) 154 """ 155 if resources is None: 156 resources = Resources() 157 158 self.width = width 159 self.height = height 160 self.bg_color = bg_color 161 self.image_directory = image_directory 162 self.resources = resources 163 self.default_code_theme = default_code_theme 164 self.default_code_language = default_code_language 165 self._deck = nelsie_rs.Deck(default_font, default_monospace_font) 166 self._slides: List[Slide] = [] 167 168 def set_style(self, name: str, style: TextStyle): 169 self._deck.set_style(self.resources._resources, name, style, False, None, None) 170 171 def update_style(self, name: str, style: TextStyle): 172 self._deck.set_style(self.resources._resources, name, style, True, None, None) 173 174 def get_style(self, name: str, step: int = 1) -> TextStyle: 175 return _data_to_text_style(self._deck.get_style(name, step, None, None)) 176 177 def new_slide( 178 self, 179 *, 180 width: float | None = None, 181 height: float | None = None, 182 bg_color: str | None = None, 183 image_directory: str | None = None, 184 name: str = "", 185 debug_steps: bool = False, 186 debug_layout: bool | str = False, 187 counters: list[str] | None = None, 188 parent_slide: tuple[Slide, int] | None = None, 189 step_1: bool = True, 190 ) -> Slide: 191 """ 192 Creates a new slide in the slide deck. 193 """ 194 if width is None: 195 width = self.width 196 if height is None: 197 height = self.height 198 if bg_color is None: 199 bg_color = self.bg_color 200 if image_directory is None: 201 image_directory = self.image_directory 202 debug_layout = parse_debug_layout(debug_layout) 203 slide_id = self._deck.new_slide(width, height, bg_color, name, step_1, debug_steps, counters, parent_slide) 204 205 slide = Slide(self, slide_id, name, image_directory, debug_layout) 206 self._slides.append(slide) 207 return slide 208 209 def slide( 210 self, 211 *, 212 width: float | None = None, 213 height: float | None = None, 214 bg_color: str | None = None, 215 image_directory: str | None = None, 216 name: str = "", 217 debug_steps: bool = False, 218 debug_layout: bool | str = False, 219 counters: list[str] | None = None, 220 parent_slide: tuple[Slide, int] | None = None, 221 step_1: bool = True, 222 ): 223 """ 224 Decorator for creating new slide. 225 It immediately calls the decorated function that should define content of the slide. 226 Slide is automatically added into the deck. 227 228 Example: 229 ```python 230 deck = SlideDeck() 231 232 @deck.slide() 233 def my_first_slide(slide): 234 slide.text("Hello!") 235 ``` 236 """ 237 238 def helper(fn): 239 slide = self.new_slide( 240 width=width, 241 height=height, 242 bg_color=bg_color, 243 image_directory=image_directory, 244 name=name, 245 debug_steps=debug_steps, 246 debug_layout=debug_layout, 247 counters=counters, 248 parent_slide=parent_slide, 249 step_1=step_1, 250 ) 251 fn(slide) 252 return slide 253 254 return helper 255 256 def render( 257 self, 258 path: str | pathlib.Path | None, 259 output_format: Literal["pdf"] | Literal["svg"] | Literal["png"] = "pdf", 260 *, 261 verbose: int = 1, 262 compression_level: int = 1, 263 n_threads: int | None = None, 264 ) -> None | list[bytes]: 265 """ 266 Render slides 267 268 If format is "pdf" then a single PDF file is created. If format is "svg" or "png" then 269 `path` specifies a directory where the slides are created as an individual files. 270 271 If `path` is None then objects are not written to the file system, and they are returned as python objects 272 from the method call. 273 274 `compression_level` defines the level of compression for PDF, allowed ranges are 0-10 275 (0 = no compression, 1 = fast compression, 10 = maximal compression) 276 """ 277 assert 0 <= compression_level <= 10 278 if path: 279 path = str(path) 280 return self._deck.render( 281 self.resources._resources, 282 verbose, 283 output_format, 284 compression_level, 285 path, 286 n_threads, 287 )
120 def __init__( 121 self, 122 *, 123 width: float = 1024, 124 height: float = 768, 125 bg_color: str = "white", 126 image_directory: str | None = None, 127 resources: Resources | None = None, 128 default_font: str = "sans-serif", 129 default_monospace_font: str = "monospace", 130 default_code_theme: str = "InspiredGitHub", 131 default_code_language: str | None = None, 132 ): 133 """ 134 A top-level class of Nelsie. It represents a set of slides. 135 136 Arguments: 137 * width - default width of a slide (could be overriden for each slide) 138 * height - default width of a slide (could be overriden for each slide) 139 * bg_color - default background color a slide (could be overriden for each slide) 140 * image_directory - default path where images are searched for (could be overriden for each slide) 141 * resource - Resource instance, if None a new instance is created 142 * default_font - Name of default font 143 * default_monospace_font - Name of the default monospace font 144 * default_code_theme - Name of default theme for syntax highlighting (.code() method): 145 Available themes: 146 * "base16-ocean.dark" 147 * "base16-eighties.dark" 148 * "base16-mocha.dark" 149 * "base16-ocean.light" 150 * "InspiredGitHub" 151 * "Solarized (dark)" 152 * "Solarized (light)" 153 * default_code_language - Default language to use for syntax highlighting (.code() method) 154 """ 155 if resources is None: 156 resources = Resources() 157 158 self.width = width 159 self.height = height 160 self.bg_color = bg_color 161 self.image_directory = image_directory 162 self.resources = resources 163 self.default_code_theme = default_code_theme 164 self.default_code_language = default_code_language 165 self._deck = nelsie_rs.Deck(default_font, default_monospace_font) 166 self._slides: List[Slide] = []
A top-level class of Nelsie. It represents a set of slides.
Arguments:
- width - default width of a slide (could be overriden for each slide)
- height - default width of a slide (could be overriden for each slide)
- bg_color - default background color a slide (could be overriden for each slide)
- image_directory - default path where images are searched for (could be overriden for each slide)
- resource - Resource instance, if None a new instance is created
- default_font - Name of default font
- default_monospace_font - Name of the default monospace font
- default_code_theme - Name of default theme for syntax highlighting (.code() method):
Available themes:
- "base16-ocean.dark"
- "base16-eighties.dark"
- "base16-mocha.dark"
- "base16-ocean.light"
- "InspiredGitHub"
- "Solarized (dark)"
- "Solarized (light)"
- default_code_language - Default language to use for syntax highlighting (.code() method)
177 def new_slide( 178 self, 179 *, 180 width: float | None = None, 181 height: float | None = None, 182 bg_color: str | None = None, 183 image_directory: str | None = None, 184 name: str = "", 185 debug_steps: bool = False, 186 debug_layout: bool | str = False, 187 counters: list[str] | None = None, 188 parent_slide: tuple[Slide, int] | None = None, 189 step_1: bool = True, 190 ) -> Slide: 191 """ 192 Creates a new slide in the slide deck. 193 """ 194 if width is None: 195 width = self.width 196 if height is None: 197 height = self.height 198 if bg_color is None: 199 bg_color = self.bg_color 200 if image_directory is None: 201 image_directory = self.image_directory 202 debug_layout = parse_debug_layout(debug_layout) 203 slide_id = self._deck.new_slide(width, height, bg_color, name, step_1, debug_steps, counters, parent_slide) 204 205 slide = Slide(self, slide_id, name, image_directory, debug_layout) 206 self._slides.append(slide) 207 return slide
Creates a new slide in the slide deck.
209 def slide( 210 self, 211 *, 212 width: float | None = None, 213 height: float | None = None, 214 bg_color: str | None = None, 215 image_directory: str | None = None, 216 name: str = "", 217 debug_steps: bool = False, 218 debug_layout: bool | str = False, 219 counters: list[str] | None = None, 220 parent_slide: tuple[Slide, int] | None = None, 221 step_1: bool = True, 222 ): 223 """ 224 Decorator for creating new slide. 225 It immediately calls the decorated function that should define content of the slide. 226 Slide is automatically added into the deck. 227 228 Example: 229 ```python 230 deck = SlideDeck() 231 232 @deck.slide() 233 def my_first_slide(slide): 234 slide.text("Hello!") 235 ``` 236 """ 237 238 def helper(fn): 239 slide = self.new_slide( 240 width=width, 241 height=height, 242 bg_color=bg_color, 243 image_directory=image_directory, 244 name=name, 245 debug_steps=debug_steps, 246 debug_layout=debug_layout, 247 counters=counters, 248 parent_slide=parent_slide, 249 step_1=step_1, 250 ) 251 fn(slide) 252 return slide 253 254 return helper
Decorator for creating new slide. It immediately calls the decorated function that should define content of the slide. Slide is automatically added into the deck.
Example:
deck = SlideDeck()
@deck.slide()
def my_first_slide(slide):
slide.text("Hello!")
256 def render( 257 self, 258 path: str | pathlib.Path | None, 259 output_format: Literal["pdf"] | Literal["svg"] | Literal["png"] = "pdf", 260 *, 261 verbose: int = 1, 262 compression_level: int = 1, 263 n_threads: int | None = None, 264 ) -> None | list[bytes]: 265 """ 266 Render slides 267 268 If format is "pdf" then a single PDF file is created. If format is "svg" or "png" then 269 `path` specifies a directory where the slides are created as an individual files. 270 271 If `path` is None then objects are not written to the file system, and they are returned as python objects 272 from the method call. 273 274 `compression_level` defines the level of compression for PDF, allowed ranges are 0-10 275 (0 = no compression, 1 = fast compression, 10 = maximal compression) 276 """ 277 assert 0 <= compression_level <= 10 278 if path: 279 path = str(path) 280 return self._deck.render( 281 self.resources._resources, 282 verbose, 283 output_format, 284 compression_level, 285 path, 286 n_threads, 287 )
Render slides
If format is "pdf" then a single PDF file is created. If format is "svg" or "png" then
path
specifies a directory where the slides are created as an individual files.
If path
is None then objects are not written to the file system, and they are returned as python objects
from the method call.
compression_level
defines the level of compression for PDF, allowed ranges are 0-10
(0 = no compression, 1 = fast compression, 10 = maximal compression)
10class InSteps(Generic[T]): 11 """ 12 InSteps is a wrapper that allows to set a different values for each step. 13 InSteps defines values in "key" steps, in other steps the value remains until it is changed 14 another key step. 15 16 Example: 17 ```python 18 slide.box(..., bg_color=InSteps({1: "green", 4: "red"}) 19 ``` 20 21 Defines "green" background for steps 1, 2, 3; and "red" for step 4 and further. 22 23 InSteps can be also initialized by a list, then it defines values for first `n` steps, 24 where `n` is a length of the list. It means that `InSteps(["a", "b", "c"])` is equal to 25 `InSteps({1: "a", 2: "b", 3: "c"})` 26 """ 27 28 def __init__( 29 self, 30 values: Sequence[T] | dict[Step | int, T], 31 ): 32 if isinstance(values, Sequence): 33 tmp = {} 34 prev = None 35 for i, v in enumerate(values): 36 if i != 0 and v == prev: 37 continue 38 tmp[i + 1] = v 39 prev = v 40 values = tmp 41 elif not isinstance(values, dict): 42 raise ValueError("Invalid type for values") 43 self.in_step_values = values 44 45 def get(self, step: int, default: S = None) -> T | None: 46 v = self.in_step_values.get(step) 47 if v is not None: 48 return v 49 if step <= 0: 50 return default 51 return self.get(step - 1, default) 52 53 def map(self, fn): 54 return InSteps( 55 {step: fn(v) for step, v in self.in_step_values.items()}, 56 ) 57 58 def key_steps(self): 59 return self.in_step_values.keys() 60 61 def zip(self, other: "InSteps[S]") -> "InSteps[tuple[S, T]]": 62 keys = set(self.key_steps()) 63 keys.update(other.key_steps()) 64 return InSteps( 65 {step: (self.get(step), other.get(step)) for step in keys}, 66 )
InSteps is a wrapper that allows to set a different values for each step. InSteps defines values in "key" steps, in other steps the value remains until it is changed another key step.
Example:
slide.box(..., bg_color=InSteps({1: "green", 4: "red"})
Defines "green" background for steps 1, 2, 3; and "red" for step 4 and further.
InSteps can be also initialized by a list, then it defines values for first n
steps,
where n
is a length of the list. It means that InSteps(["a", "b", "c"])
is equal to
InSteps({1: "a", 2: "b", 3: "c"})
28 def __init__( 29 self, 30 values: Sequence[T] | dict[Step | int, T], 31 ): 32 if isinstance(values, Sequence): 33 tmp = {} 34 prev = None 35 for i, v in enumerate(values): 36 if i != 0 and v == prev: 37 continue 38 tmp[i + 1] = v 39 prev = v 40 values = tmp 41 elif not isinstance(values, dict): 42 raise ValueError("Invalid type for values") 43 self.in_step_values = values
37class Path: 38 def __init__( 39 self, 40 *, 41 stroke: Stroke | None = None, 42 fill_color: str | None = None, 43 arrow_start: Arrow | None = None, 44 arrow_end: Arrow | None = None, 45 ): 46 self.stroke = stroke 47 self.fill_color = fill_color 48 self.commands = [] 49 self.points = [] 50 self.arrow_start = arrow_start 51 self.arrow_end = arrow_end 52 53 @staticmethod 54 def oval( 55 x1: PathValue, 56 y1: PathValue, 57 x2: PathValue, 58 y2: PathValue, 59 *, 60 stroke: Stroke | None = None, 61 fill_color: str | None = None, 62 ): 63 path = Path(stroke=stroke, fill_color=fill_color) 64 path.commands.append("oval") 65 path.points = [x1, y1, x2, y2] 66 return path 67 68 @property 69 def last_point(self): 70 """ 71 Returns a last point in the path, if path is empty, returns 0,0 72 :return: A tuple (x, y) 73 """ 74 if len(self.points) < 2: 75 return 0, 0 76 else: 77 return self.points[-2], self.points[-1] 78 79 def close(self): 80 self.commands.append("close") 81 return self 82 83 def move_to(self, x: PathValue, y: PathValue): 84 self.commands.append("move") 85 self.points.append(x) 86 self.points.append(y) 87 return self 88 89 def move_by(self, x: PathValue, y: PathValue) -> "Path": 90 """ 91 Perform a move relative to the last point of the path. 92 If path is empty, it starts from 0,0 93 """ 94 x_old, y_old = self.last_point 95 96 return self.move_to(x_old + x, y_old + y) 97 98 def line_to(self, x: PathValue, y: PathValue): 99 self.commands.append("line") 100 self.points.append(x) 101 self.points.append(y) 102 return self 103 104 def line_by(self, x: PathValue, y: PathValue): 105 """ 106 Draw a line relative to the last point of the path. 107 If path is empty, it starts from 0,0 108 """ 109 x_old, y_old = self.last_point 110 return self.line_to(x_old + x, y_old + y) 111 112 def quad_to(self, x1: PathValue, y1: PathValue, x: PathValue, y: PathValue): 113 self.commands.append("quad") 114 self.points.append(x1) 115 self.points.append(y1) 116 self.points.append(x) 117 self.points.append(y) 118 return self 119 120 def cubic_to( 121 self, 122 x1: PathValue, 123 y1: PathValue, 124 x2: PathValue, 125 y2: PathValue, 126 x: PathValue, 127 y: PathValue, 128 ): 129 self.commands.append("cubic") 130 self.points.append(x1) 131 self.points.append(y1) 132 self.points.append(x2) 133 self.points.append(y2) 134 self.points.append(x) 135 self.points.append(y) 136 return self
38 def __init__( 39 self, 40 *, 41 stroke: Stroke | None = None, 42 fill_color: str | None = None, 43 arrow_start: Arrow | None = None, 44 arrow_end: Arrow | None = None, 45 ): 46 self.stroke = stroke 47 self.fill_color = fill_color 48 self.commands = [] 49 self.points = [] 50 self.arrow_start = arrow_start 51 self.arrow_end = arrow_end
53 @staticmethod 54 def oval( 55 x1: PathValue, 56 y1: PathValue, 57 x2: PathValue, 58 y2: PathValue, 59 *, 60 stroke: Stroke | None = None, 61 fill_color: str | None = None, 62 ): 63 path = Path(stroke=stroke, fill_color=fill_color) 64 path.commands.append("oval") 65 path.points = [x1, y1, x2, y2] 66 return path
68 @property 69 def last_point(self): 70 """ 71 Returns a last point in the path, if path is empty, returns 0,0 72 :return: A tuple (x, y) 73 """ 74 if len(self.points) < 2: 75 return 0, 0 76 else: 77 return self.points[-2], self.points[-1]
Returns a last point in the path, if path is empty, returns 0,0
Returns
A tuple (x, y)
89 def move_by(self, x: PathValue, y: PathValue) -> "Path": 90 """ 91 Perform a move relative to the last point of the path. 92 If path is empty, it starts from 0,0 93 """ 94 x_old, y_old = self.last_point 95 96 return self.move_to(x_old + x, y_old + y)
Perform a move relative to the last point of the path. If path is empty, it starts from 0,0
104 def line_by(self, x: PathValue, y: PathValue): 105 """ 106 Draw a line relative to the last point of the path. 107 If path is empty, it starts from 0,0 108 """ 109 x_old, y_old = self.last_point 110 return self.line_to(x_old + x, y_old + y)
Draw a line relative to the last point of the path. If path is empty, it starts from 0,0
120 def cubic_to( 121 self, 122 x1: PathValue, 123 y1: PathValue, 124 x2: PathValue, 125 y2: PathValue, 126 x: PathValue, 127 y: PathValue, 128 ): 129 self.commands.append("cubic") 130 self.points.append(x1) 131 self.points.append(y1) 132 self.points.append(x2) 133 self.points.append(y2) 134 self.points.append(x) 135 self.points.append(y) 136 return self
14@dataclass 15class Stroke: 16 color: str 17 width: float = 1.0 18 dash_array: list[float] | None = None 19 dash_offset: float = 0.0
19@dataclass(frozen=True) 20class TextStyle: 21 font_family: str | list[str] | None = None 22 color: str | None = None 23 size: float | None = None 24 line_spacing: float | None = None 25 italic: bool | None = None 26 stretch: FontStretch | None = None 27 underline: bool | None = None 28 line_through: bool | None = None 29 30 # 1-1000; 400 = Normal, 700 = Bold 31 weight: int | None = None 32 33 # Init only fields 34 # These are used as helpers for initializing 35 # commonly used attributes 36 bold: InitVar[bool | None] = None 37 38 def __post_init__(self, bold: bool | None): 39 if self.size is not None: 40 assert self.size >= 0 41 if self.line_spacing is not None: 42 assert self.line_spacing >= 0 43 if self.weight is not None: 44 assert 1 <= self.weight <= 1000 45 if bold is not None: 46 raise Exception("Cannot set both `weight` and `bold` when creating a TextStyle") 47 if bold is not None: 48 # Workaround to set frozen attribute 49 super().__setattr__("weight", 700) 50 51 def merge(self, other: "TextStyle") -> "TextStyle": 52 assert isinstance(other, TextStyle) 53 return TextStyle( 54 *[b if b is not None else a for (a, b) in zip(unpack_dataclass(self), unpack_dataclass(other))] 55 )
18class Resources: 19 def __init__( 20 self, 21 *, 22 builtin_fonts: bool = True, 23 system_fonts: bool = False, 24 system_fonts_for_svg: bool = True, 25 default_code_syntaxes: bool = True, 26 default_code_themes: bool = True, 27 ): 28 self._resources = nelsie_rs.Resources( 29 system_fonts, system_fonts_for_svg, default_code_syntaxes, default_code_themes 30 ) 31 if builtin_fonts: 32 self._resources.load_fonts_dir(BUILTIN_FONTS_DIR) 33 self._resources.set_generic_family("sans-serif", "DejaVu Sans") 34 self._resources.set_generic_family("monospace", "DejaVu Sans Mono") 35 36 def set_sans_serif(self, font_name): 37 self._resources.set_generic_family("sans-serif", font_name) 38 39 def set_monospace(self, font_name): 40 self._resources.set_generic_family("monospace", font_name) 41 42 def set_serif(self, font_name): 43 self._resources.set_generic_family("serif", font_name) 44 45 def load_code_syntax_dir(self, path: str): 46 self._resources.load_code_syntax_dir(path) 47 48 def load_code_theme_dir(self, path: str): 49 self._resources.load_code_theme_dir(path) 50 51 def load_fonts_dir(self, path: str): 52 self._resources.load_fonts_dir(path) 53 54 def syntaxes(self) -> list[tuple[str, list[str]]]: 55 return self._resources.syntaxes() 56 57 def themes(self) -> list[str]: 58 return self._resources.themes()
19 def __init__( 20 self, 21 *, 22 builtin_fonts: bool = True, 23 system_fonts: bool = False, 24 system_fonts_for_svg: bool = True, 25 default_code_syntaxes: bool = True, 26 default_code_themes: bool = True, 27 ): 28 self._resources = nelsie_rs.Resources( 29 system_fonts, system_fonts_for_svg, default_code_syntaxes, default_code_themes 30 ) 31 if builtin_fonts: 32 self._resources.load_fonts_dir(BUILTIN_FONTS_DIR) 33 self._resources.set_generic_family("sans-serif", "DejaVu Sans") 34 self._resources.set_generic_family("monospace", "DejaVu Sans Mono")
7class FontStretch(IntEnum): 8 UltraCondensed = 1 9 ExtraCondensed = 2 10 Condensed = 3 11 SemiCondensed = 4 12 Normal = 5 13 SemiExpanded = 6 14 Expanded = 7 15 ExtraExpanded = 8 16 UltraExpanded = 9
An enumeration.
10@dataclass 11class Arrow: 12 """ 13 Represents an SVG arrow head. Can be attached to the start or end points of lines. 14 """ 15 16 size: float = 10 17 """Size of the arrow head in pixels.""" 18 19 angle: float = 40 20 """Angle of the arrow head.""" 21 22 color: str | None = None 23 """Color of arrow, if None color is taken from path""" 24 25 stroke_width: float | None = None 26 """If None then a filled arrow is drawn, if float then stroked arrow is drawn with the given stroke width""" 27 28 inner_point: float | None = None 29 """ Shape of the arrow head. 30 31 * < 1.0 -> Sharper arrow. 32 * = 1.0 -> Normal arrow. 33 * > 1.0 -> Diamond shape arrow. 34 """
Represents an SVG arrow head. Can be attached to the start or end points of lines.
61class BoxBuilder: 62 def get_box(self): 63 """ 64 @private 65 """ 66 raise NotImplementedError 67 68 def set_style(self, name: str, style: TextStyle | InSteps[TextStyle]): 69 """ 70 Set text style under given name. 71 """ 72 box = self.get_box() 73 deck = box.deck 74 deck._deck.set_style( 75 deck.resources._resources, 76 name, 77 style, 78 False, 79 box.slide._slide_id, 80 box._box_id, 81 ) 82 83 def update_style(self, name: str, style: TextStyle | InSteps[TextStyle]): 84 """ 85 Load a text style and merge it with the `style` and save it back. 86 Throws an exception if a style under given name does not exist. 87 """ 88 box = self.get_box() 89 deck = box.deck 90 deck._deck.set_style( 91 deck.resources._resources, 92 name, 93 style, 94 True, 95 box.slide._slide_id, 96 box._box_id, 97 ) 98 99 def get_style(self, name: str, step: int = 1) -> TextStyle: 100 """ 101 Get text style under given name. Style is returned for a given `step`. 102 Throws an exception if a style under given name does not exist. 103 104 This function returns text style even when InSteps was used to set the style, 105 as it returns text style for a given step. 106 """ 107 108 box = self.get_box() 109 return _data_to_text_style(box.deck._deck.get_style(name, step, box.slide._slide_id, box._box_id)) 110 111 def image( 112 self, 113 path_or_data: PathOrData | InSteps[PathOrData], 114 *, 115 enable_steps=True, 116 shift_steps=0, 117 **box_args, 118 ): 119 """ 120 Create a box with an image. Supported formats: SVG, PNG, JPEG, GIF, ORA 121 """ 122 assert shift_steps >= 0 123 124 def process_path(p): 125 if not isinstance(p, str): 126 return p 127 if slide.image_directory is not None: 128 p = os.path.join(slide.image_directory, p) 129 p = os.path.abspath(p) 130 watch_path(p) 131 return p 132 133 slide = self.get_box().slide 134 if isinstance(path_or_data, InSteps): 135 path_or_data = path_or_data.map(process_path) 136 else: 137 path_or_data = process_path(path_or_data) 138 image = ImageContent( 139 path_or_data=path_or_data, 140 enable_steps=enable_steps, 141 shift_steps=shift_steps, 142 ) 143 return self.box(_content=image, **box_args) 144 145 def text( 146 self, 147 text: str | InSteps[str], 148 style: str | TextStyle | InSteps[TextStyle] | None = None, 149 *, 150 parse_styles: bool = True, 151 style_delimiters: str | None = "~{}", 152 tab_width: int = 4, 153 align: TextAlign = "start", 154 strip=True, 155 parse_counters: bool = False, 156 **box_args, 157 ): 158 """ 159 Create a box with a text. 160 """ 161 box_args.setdefault("name", "text") 162 return self._text_box( 163 text, 164 style, 165 None, 166 style_delimiters if parse_styles else None, 167 tab_width, 168 box_args, 169 align, 170 None, 171 None, 172 strip, 173 parse_counters, 174 ) 175 176 def code( 177 self, 178 text: str | InSteps[str], 179 language: str | None = "default", 180 style: str | TextStyle | InSteps[TextStyle] | None = None, 181 *, 182 theme: str | None = None, 183 parse_styles: bool = False, 184 style_delimiters: str | None = "~{}", 185 tab_width: int = 4, 186 align: TextAlign = "start", 187 strip: bool = True, 188 parse_counters: bool = False, 189 **box_args, 190 ): 191 """ 192 Create a box with a syntax highlighted text. 193 """ 194 box_args.setdefault("name", "code") 195 if theme is None: 196 theme = self.get_box().deck.default_code_theme 197 if language == "default": 198 language = self.get_box().deck.default_code_language 199 return self._text_box( 200 text, 201 "code", 202 style, 203 style_delimiters if parse_styles else None, 204 tab_width, 205 box_args, 206 align, 207 language, 208 theme, 209 strip, 210 parse_counters, 211 ) 212 213 def _text_box( 214 self, 215 text, 216 style1, 217 style2, 218 delimiters, 219 tab_width, 220 box_args, 221 align, 222 language, 223 theme, 224 strip, 225 parse_counters, 226 ): 227 def text_preprocess(x): 228 if strip: 229 x = x.strip() 230 return x.replace("\t", " " * tab_width) 231 232 if isinstance(text, str): 233 text = text_preprocess(text) 234 elif isinstance(text, list): 235 text = zip_in_steps(text, "").map(lambda x: text_preprocess("".join(x))) 236 elif isinstance(text, InSteps): 237 text = text.map(text_preprocess) 238 else: 239 raise Exception("Invalid type for text") 240 241 if align == "start": 242 align = 0 243 elif align == "center": 244 align = 1 245 elif align == "end": 246 align = 2 247 else: 248 raise Exception(f"Invalid alignment: {align}") 249 text_content = TextContent( 250 text=text, 251 style1=style1, 252 style2=style2, 253 formatting_delimiters=delimiters, 254 text_align=align, 255 syntax_language=language, 256 syntax_theme=theme, 257 parse_counters=parse_counters, 258 ) 259 return self.box(_content=text_content, **box_args) 260 261 def draw(self, paths: Path | list[Path] | InSteps[Path | list[Path]]): 262 """ 263 Draw one or more paths in the slide. 264 """ 265 266 if isinstance(paths, Path): 267 paths = [paths] 268 elif isinstance(paths, InSteps): 269 paths = paths.map(lambda p: [p] if isinstance(p, Path) else p) 270 box = self.get_box() 271 box.deck._deck.draw(box.slide._slide_id, box._box_id, paths) 272 273 def video( 274 self, 275 path: str, 276 *, 277 data_type: str = "video/mp4", 278 cover_image: str | None = None, 279 show_controls: bool = False, 280 **box_args, 281 ): 282 """ 283 Insert video into slide.```` 284 """ 285 if cover_image: 286 slide = self.get_box().slide 287 if slide.image_directory is not None: 288 cover_image = os.path.join(slide.image_directory, cover_image) 289 cover_image = os.path.abspath(cover_image) 290 watch_path(cover_image) 291 path = os.path.abspath(path) 292 watch_path(path) 293 video = VideoContent(path=path, data_type=data_type, cover_image=cover_image, show_controls=show_controls) 294 return self.box(_content=video, **box_args) 295 296 def box( 297 self, 298 *, 299 active: bool | str | int | InSteps[bool] = True, 300 show: bool | str | int | InSteps[bool] = True, 301 z_level: int | InSteps[int] | None = None, 302 x: Position | InSteps[Position] = None, 303 y: Position | InSteps[Position] = None, 304 width: Size | InSteps[Size] = None, 305 height: Size | InSteps[Size] = None, 306 border_radius: float | InSteps[float] = 0, 307 p_left: Length | InSteps[Length] | None = None, 308 p_right: Length | InSteps[Length] | None = None, 309 p_top: Length | InSteps[Length] | None = None, 310 p_bottom: Length | InSteps[Length] | None = None, 311 p_x: Length | InSteps[Length] = 0, 312 p_y: Length | InSteps[Length] = 0, 313 m_left: LengthAuto | InSteps[LengthAuto] | None = None, 314 m_right: LengthAuto | InSteps[LengthAuto] | None = None, 315 m_top: LengthAuto | InSteps[LengthAuto] | None = None, 316 m_bottom: LengthAuto | InSteps[LengthAuto] | None = None, 317 m_x: LengthAuto | InSteps[LengthAuto] = 0, 318 m_y: LengthAuto | InSteps[LengthAuto] = 0, 319 row: bool | InSteps[bool] = False, 320 reverse: bool | InSteps[bool] = False, 321 flex_wrap: FlexWrap | InSteps[FlexWrap] = "nowrap", 322 flex_grow: float | InSteps[float] = 0.0, 323 flex_shrink: float | InSteps[float] = 1.0, 324 align_items: AlignItemsSteps = None, 325 align_self: AlignItemsSteps = None, 326 justify_self: AlignItemsSteps = None, 327 align_content: AlignContentSteps = None, 328 justify_content: AlignContentSteps = None, 329 gap: tuple[Length, Length] | InSteps[tuple[Length, Length]] = (0.0, 0.0), 330 grid_template_rows: GridTemplate = (), 331 grid_template_columns: GridTemplate = (), 332 grid_row: GridPosition = "auto", 333 grid_column: GridPosition = "auto", 334 bg_color: str | None | InSteps[str | None] = None, 335 url: None | str | InSteps[None | str] = None, 336 name: str = "", 337 debug_layout: bool | str | None = None, 338 replace_steps: dict[int, int] | None = None, 339 _content: NodeContent | InSteps[NodeContent] = None, 340 ): 341 """ 342 Create a new child box. See [Box reference](https://spirali.github.io/nelsie/guide/box/) for documentation 343 """ 344 parent_box = self.get_box() 345 if debug_layout is None: 346 debug_layout = parent_box.slide.debug_layout 347 else: 348 debug_layout = parse_debug_layout(debug_layout) 349 if z_level is None: 350 z_level = parent_box.z_level 351 352 if p_left is None: 353 p_left = p_x 354 if p_right is None: 355 p_right = p_x 356 if p_bottom is None: 357 p_bottom = p_y 358 if p_top is None: 359 p_top = p_y 360 361 if m_left is None: 362 m_left = m_x 363 if m_right is None: 364 m_right = m_x 365 if m_bottom is None: 366 m_bottom = m_y 367 if m_top is None: 368 m_top = m_y 369 370 deck = parent_box.deck 371 box_id, node_id = deck._deck.new_box( 372 deck.resources._resources, 373 parent_box.slide._slide_id, 374 parent_box._box_id, 375 active=active, 376 show=show, 377 z_level=z_level, 378 x=x, 379 y=y, 380 width=width, 381 height=height, 382 border_radius=border_radius, 383 p_left=p_left, 384 p_right=p_right, 385 p_top=p_top, 386 p_bottom=p_bottom, 387 m_left=m_left, 388 m_right=m_right, 389 m_top=m_top, 390 m_bottom=m_bottom, 391 row=row, 392 reverse=reverse, 393 flex_wrap=flex_wrap, 394 flex_grow=flex_grow, 395 flex_shrink=flex_shrink, 396 align_items=align_items, 397 align_self=align_self, 398 justify_self=justify_self, 399 align_content=align_content, 400 justify_content=justify_content, 401 gap=gap, 402 grid_template_rows=grid_template_rows, 403 grid_template_columns=grid_template_columns, 404 grid_row=grid_row, 405 grid_column=grid_column, 406 bg_color=bg_color, 407 url=url, 408 name=name, 409 debug_layout=debug_layout, 410 replace_steps=replace_steps, 411 content=_content, 412 ) 413 return Box(deck, parent_box.slide, box_id, node_id, name, z_level) 414 415 def overlay(self, **box_args): 416 """ 417 Create a new box that spans over the box 418 """ 419 box_args.setdefault("x", 0) 420 box_args.setdefault("y", 0) 421 box_args.setdefault("width", "100%") 422 box_args.setdefault("height", "100%") 423 return self.box(**box_args) 424 425 def line_box(self, line_idx: int, **box_args): 426 """ 427 Creates a new box over a text line in the box 428 """ 429 return self.box( 430 x=self.line_x(line_idx), 431 y=self.line_y(line_idx), 432 width=self.line_width(line_idx), 433 height=self.line_height(line_idx), 434 **box_args, 435 ) 436 437 def text_anchor_box(self, anchor_id: int, **box_args): 438 """ 439 Creates a new box over a text anchor in the box 440 """ 441 442 return self.box( 443 x=self.text_anchor_x(anchor_id), 444 y=self.text_anchor_y(anchor_id), 445 width=self.text_anchor_width(anchor_id), 446 height=self.text_anchor_height(anchor_id), 447 **box_args, 448 ) 449 450 def x(self, width_fraction: float | int | None = None) -> LayoutExpr: 451 """ 452 Get an expression with X coordinate relative to the box. 453 """ 454 455 node_id = self.get_box().node_id 456 expr = LayoutExpr.x(node_id) 457 if width_fraction is None: 458 return expr 459 return expr + LayoutExpr.width(node_id, width_fraction) 460 461 def y(self, height_fraction: float | int | None = None) -> LayoutExpr: 462 """ 463 Get an expression with Y coordinate relative to the box. 464 """ 465 node_id = self.get_box().node_id 466 expr = LayoutExpr.y(node_id) 467 if height_fraction is None: 468 return expr 469 return expr + LayoutExpr.height(node_id, height_fraction) 470 471 def width(self, fraction: float | int = 1.0) -> LayoutExpr: 472 """ 473 Get an expression with width of the parent box. 474 """ 475 node_id = self.get_box().node_id 476 return LayoutExpr.width(node_id, fraction) 477 478 def height(self, fraction: float | int = 1.0) -> LayoutExpr: 479 """ 480 Get an expression with height of the parent box. 481 """ 482 node_id = self.get_box().node_id 483 return LayoutExpr.height(node_id, fraction) 484 485 def line_x(self, line_idx: int, width_fraction: float | int | None = None) -> LayoutExpr: 486 """ 487 Get an expression with X coordinate of a given line of text in the box. 488 """ 489 node_id = self.get_box().node_id 490 expr = LayoutExpr.line_x(node_id, line_idx) 491 if width_fraction is None: 492 return expr 493 return expr + LayoutExpr.line_width(node_id, line_idx, width_fraction) 494 495 def line_y(self, line_idx: int, height_fraction: float | int | None = None) -> LayoutExpr: 496 """ 497 Get an expression with Y coordinate of a given line of text in the box. 498 """ 499 node_id = self.get_box().node_id 500 expr = LayoutExpr.line_y(node_id, line_idx) 501 if height_fraction is None: 502 return expr 503 return expr + LayoutExpr.line_height(node_id, line_idx, height_fraction) 504 505 def line_width(self, line_idx: int, fraction: float | int = 1.0) -> LayoutExpr: 506 """ 507 Get an expression with a width of a given line of text in the box. 508 """ 509 node_id = self.get_box().node_id 510 return LayoutExpr.line_width(node_id, line_idx, fraction) 511 512 def line_height(self, line_idx: int, fraction: float | int = 1.0) -> LayoutExpr: 513 """ 514 Get an expression with a height of a given line of text in the box. 515 """ 516 node_id = self.get_box().node_id 517 return LayoutExpr.line_height(node_id, line_idx, fraction) 518 519 def text_anchor_x(self, anchor_id: int, width_fraction: float | int | None = None) -> LayoutExpr: 520 """ 521 Get an expression with X coordinate of a given text anchor in the box. 522 """ 523 node_id = self.get_box().node_id 524 expr = LayoutExpr.text_anchor_x(node_id, anchor_id) 525 if width_fraction is None: 526 return expr 527 return expr + LayoutExpr.text_anchor_width(node_id, anchor_id, width_fraction) 528 529 def text_anchor_y(self, anchor_id: int, height_fraction: float | int | None = None) -> LayoutExpr: 530 """ 531 Get an expression with Y coordinate of a given text anchor in the box. 532 """ 533 node_id = self.get_box().node_id 534 expr = LayoutExpr.text_anchor_y(node_id, anchor_id) 535 if height_fraction is None: 536 return expr 537 return expr + LayoutExpr.text_anchor_height(node_id, anchor_id, height_fraction) 538 539 def text_anchor_width(self, anchor_id: int, fraction: float | int = 1.0) -> LayoutExpr: 540 """ 541 Get an expression with a height of a given text anchor in the box. 542 """ 543 node_id = self.get_box().node_id 544 return LayoutExpr.text_anchor_width(node_id, anchor_id, fraction) 545 546 def text_anchor_height(self, anchor_id: int, fraction: float | int = 1.0) -> LayoutExpr: 547 """ 548 Get an expression with a height of a given text anchor in the box. 549 """ 550 node_id = self.get_box().node_id 551 return LayoutExpr.text_anchor_height(node_id, anchor_id, fraction)
68 def set_style(self, name: str, style: TextStyle | InSteps[TextStyle]): 69 """ 70 Set text style under given name. 71 """ 72 box = self.get_box() 73 deck = box.deck 74 deck._deck.set_style( 75 deck.resources._resources, 76 name, 77 style, 78 False, 79 box.slide._slide_id, 80 box._box_id, 81 )
Set text style under given name.
83 def update_style(self, name: str, style: TextStyle | InSteps[TextStyle]): 84 """ 85 Load a text style and merge it with the `style` and save it back. 86 Throws an exception if a style under given name does not exist. 87 """ 88 box = self.get_box() 89 deck = box.deck 90 deck._deck.set_style( 91 deck.resources._resources, 92 name, 93 style, 94 True, 95 box.slide._slide_id, 96 box._box_id, 97 )
Load a text style and merge it with the style
and save it back.
Throws an exception if a style under given name does not exist.
99 def get_style(self, name: str, step: int = 1) -> TextStyle: 100 """ 101 Get text style under given name. Style is returned for a given `step`. 102 Throws an exception if a style under given name does not exist. 103 104 This function returns text style even when InSteps was used to set the style, 105 as it returns text style for a given step. 106 """ 107 108 box = self.get_box() 109 return _data_to_text_style(box.deck._deck.get_style(name, step, box.slide._slide_id, box._box_id))
Get text style under given name. Style is returned for a given step
.
Throws an exception if a style under given name does not exist.
This function returns text style even when InSteps was used to set the style, as it returns text style for a given step.
111 def image( 112 self, 113 path_or_data: PathOrData | InSteps[PathOrData], 114 *, 115 enable_steps=True, 116 shift_steps=0, 117 **box_args, 118 ): 119 """ 120 Create a box with an image. Supported formats: SVG, PNG, JPEG, GIF, ORA 121 """ 122 assert shift_steps >= 0 123 124 def process_path(p): 125 if not isinstance(p, str): 126 return p 127 if slide.image_directory is not None: 128 p = os.path.join(slide.image_directory, p) 129 p = os.path.abspath(p) 130 watch_path(p) 131 return p 132 133 slide = self.get_box().slide 134 if isinstance(path_or_data, InSteps): 135 path_or_data = path_or_data.map(process_path) 136 else: 137 path_or_data = process_path(path_or_data) 138 image = ImageContent( 139 path_or_data=path_or_data, 140 enable_steps=enable_steps, 141 shift_steps=shift_steps, 142 ) 143 return self.box(_content=image, **box_args)
Create a box with an image. Supported formats: SVG, PNG, JPEG, GIF, ORA
145 def text( 146 self, 147 text: str | InSteps[str], 148 style: str | TextStyle | InSteps[TextStyle] | None = None, 149 *, 150 parse_styles: bool = True, 151 style_delimiters: str | None = "~{}", 152 tab_width: int = 4, 153 align: TextAlign = "start", 154 strip=True, 155 parse_counters: bool = False, 156 **box_args, 157 ): 158 """ 159 Create a box with a text. 160 """ 161 box_args.setdefault("name", "text") 162 return self._text_box( 163 text, 164 style, 165 None, 166 style_delimiters if parse_styles else None, 167 tab_width, 168 box_args, 169 align, 170 None, 171 None, 172 strip, 173 parse_counters, 174 )
Create a box with a text.
176 def code( 177 self, 178 text: str | InSteps[str], 179 language: str | None = "default", 180 style: str | TextStyle | InSteps[TextStyle] | None = None, 181 *, 182 theme: str | None = None, 183 parse_styles: bool = False, 184 style_delimiters: str | None = "~{}", 185 tab_width: int = 4, 186 align: TextAlign = "start", 187 strip: bool = True, 188 parse_counters: bool = False, 189 **box_args, 190 ): 191 """ 192 Create a box with a syntax highlighted text. 193 """ 194 box_args.setdefault("name", "code") 195 if theme is None: 196 theme = self.get_box().deck.default_code_theme 197 if language == "default": 198 language = self.get_box().deck.default_code_language 199 return self._text_box( 200 text, 201 "code", 202 style, 203 style_delimiters if parse_styles else None, 204 tab_width, 205 box_args, 206 align, 207 language, 208 theme, 209 strip, 210 parse_counters, 211 )
Create a box with a syntax highlighted text.
261 def draw(self, paths: Path | list[Path] | InSteps[Path | list[Path]]): 262 """ 263 Draw one or more paths in the slide. 264 """ 265 266 if isinstance(paths, Path): 267 paths = [paths] 268 elif isinstance(paths, InSteps): 269 paths = paths.map(lambda p: [p] if isinstance(p, Path) else p) 270 box = self.get_box() 271 box.deck._deck.draw(box.slide._slide_id, box._box_id, paths)
Draw one or more paths in the slide.
273 def video( 274 self, 275 path: str, 276 *, 277 data_type: str = "video/mp4", 278 cover_image: str | None = None, 279 show_controls: bool = False, 280 **box_args, 281 ): 282 """ 283 Insert video into slide.```` 284 """ 285 if cover_image: 286 slide = self.get_box().slide 287 if slide.image_directory is not None: 288 cover_image = os.path.join(slide.image_directory, cover_image) 289 cover_image = os.path.abspath(cover_image) 290 watch_path(cover_image) 291 path = os.path.abspath(path) 292 watch_path(path) 293 video = VideoContent(path=path, data_type=data_type, cover_image=cover_image, show_controls=show_controls) 294 return self.box(_content=video, **box_args)
Insert video into slide.````
296 def box( 297 self, 298 *, 299 active: bool | str | int | InSteps[bool] = True, 300 show: bool | str | int | InSteps[bool] = True, 301 z_level: int | InSteps[int] | None = None, 302 x: Position | InSteps[Position] = None, 303 y: Position | InSteps[Position] = None, 304 width: Size | InSteps[Size] = None, 305 height: Size | InSteps[Size] = None, 306 border_radius: float | InSteps[float] = 0, 307 p_left: Length | InSteps[Length] | None = None, 308 p_right: Length | InSteps[Length] | None = None, 309 p_top: Length | InSteps[Length] | None = None, 310 p_bottom: Length | InSteps[Length] | None = None, 311 p_x: Length | InSteps[Length] = 0, 312 p_y: Length | InSteps[Length] = 0, 313 m_left: LengthAuto | InSteps[LengthAuto] | None = None, 314 m_right: LengthAuto | InSteps[LengthAuto] | None = None, 315 m_top: LengthAuto | InSteps[LengthAuto] | None = None, 316 m_bottom: LengthAuto | InSteps[LengthAuto] | None = None, 317 m_x: LengthAuto | InSteps[LengthAuto] = 0, 318 m_y: LengthAuto | InSteps[LengthAuto] = 0, 319 row: bool | InSteps[bool] = False, 320 reverse: bool | InSteps[bool] = False, 321 flex_wrap: FlexWrap | InSteps[FlexWrap] = "nowrap", 322 flex_grow: float | InSteps[float] = 0.0, 323 flex_shrink: float | InSteps[float] = 1.0, 324 align_items: AlignItemsSteps = None, 325 align_self: AlignItemsSteps = None, 326 justify_self: AlignItemsSteps = None, 327 align_content: AlignContentSteps = None, 328 justify_content: AlignContentSteps = None, 329 gap: tuple[Length, Length] | InSteps[tuple[Length, Length]] = (0.0, 0.0), 330 grid_template_rows: GridTemplate = (), 331 grid_template_columns: GridTemplate = (), 332 grid_row: GridPosition = "auto", 333 grid_column: GridPosition = "auto", 334 bg_color: str | None | InSteps[str | None] = None, 335 url: None | str | InSteps[None | str] = None, 336 name: str = "", 337 debug_layout: bool | str | None = None, 338 replace_steps: dict[int, int] | None = None, 339 _content: NodeContent | InSteps[NodeContent] = None, 340 ): 341 """ 342 Create a new child box. See [Box reference](https://spirali.github.io/nelsie/guide/box/) for documentation 343 """ 344 parent_box = self.get_box() 345 if debug_layout is None: 346 debug_layout = parent_box.slide.debug_layout 347 else: 348 debug_layout = parse_debug_layout(debug_layout) 349 if z_level is None: 350 z_level = parent_box.z_level 351 352 if p_left is None: 353 p_left = p_x 354 if p_right is None: 355 p_right = p_x 356 if p_bottom is None: 357 p_bottom = p_y 358 if p_top is None: 359 p_top = p_y 360 361 if m_left is None: 362 m_left = m_x 363 if m_right is None: 364 m_right = m_x 365 if m_bottom is None: 366 m_bottom = m_y 367 if m_top is None: 368 m_top = m_y 369 370 deck = parent_box.deck 371 box_id, node_id = deck._deck.new_box( 372 deck.resources._resources, 373 parent_box.slide._slide_id, 374 parent_box._box_id, 375 active=active, 376 show=show, 377 z_level=z_level, 378 x=x, 379 y=y, 380 width=width, 381 height=height, 382 border_radius=border_radius, 383 p_left=p_left, 384 p_right=p_right, 385 p_top=p_top, 386 p_bottom=p_bottom, 387 m_left=m_left, 388 m_right=m_right, 389 m_top=m_top, 390 m_bottom=m_bottom, 391 row=row, 392 reverse=reverse, 393 flex_wrap=flex_wrap, 394 flex_grow=flex_grow, 395 flex_shrink=flex_shrink, 396 align_items=align_items, 397 align_self=align_self, 398 justify_self=justify_self, 399 align_content=align_content, 400 justify_content=justify_content, 401 gap=gap, 402 grid_template_rows=grid_template_rows, 403 grid_template_columns=grid_template_columns, 404 grid_row=grid_row, 405 grid_column=grid_column, 406 bg_color=bg_color, 407 url=url, 408 name=name, 409 debug_layout=debug_layout, 410 replace_steps=replace_steps, 411 content=_content, 412 ) 413 return Box(deck, parent_box.slide, box_id, node_id, name, z_level)
Create a new child box. See Box reference for documentation
415 def overlay(self, **box_args): 416 """ 417 Create a new box that spans over the box 418 """ 419 box_args.setdefault("x", 0) 420 box_args.setdefault("y", 0) 421 box_args.setdefault("width", "100%") 422 box_args.setdefault("height", "100%") 423 return self.box(**box_args)
Create a new box that spans over the box
425 def line_box(self, line_idx: int, **box_args): 426 """ 427 Creates a new box over a text line in the box 428 """ 429 return self.box( 430 x=self.line_x(line_idx), 431 y=self.line_y(line_idx), 432 width=self.line_width(line_idx), 433 height=self.line_height(line_idx), 434 **box_args, 435 )
Creates a new box over a text line in the box
437 def text_anchor_box(self, anchor_id: int, **box_args): 438 """ 439 Creates a new box over a text anchor in the box 440 """ 441 442 return self.box( 443 x=self.text_anchor_x(anchor_id), 444 y=self.text_anchor_y(anchor_id), 445 width=self.text_anchor_width(anchor_id), 446 height=self.text_anchor_height(anchor_id), 447 **box_args, 448 )
Creates a new box over a text anchor in the box
450 def x(self, width_fraction: float | int | None = None) -> LayoutExpr: 451 """ 452 Get an expression with X coordinate relative to the box. 453 """ 454 455 node_id = self.get_box().node_id 456 expr = LayoutExpr.x(node_id) 457 if width_fraction is None: 458 return expr 459 return expr + LayoutExpr.width(node_id, width_fraction)
Get an expression with X coordinate relative to the box.
461 def y(self, height_fraction: float | int | None = None) -> LayoutExpr: 462 """ 463 Get an expression with Y coordinate relative to the box. 464 """ 465 node_id = self.get_box().node_id 466 expr = LayoutExpr.y(node_id) 467 if height_fraction is None: 468 return expr 469 return expr + LayoutExpr.height(node_id, height_fraction)
Get an expression with Y coordinate relative to the box.
471 def width(self, fraction: float | int = 1.0) -> LayoutExpr: 472 """ 473 Get an expression with width of the parent box. 474 """ 475 node_id = self.get_box().node_id 476 return LayoutExpr.width(node_id, fraction)
Get an expression with width of the parent box.
478 def height(self, fraction: float | int = 1.0) -> LayoutExpr: 479 """ 480 Get an expression with height of the parent box. 481 """ 482 node_id = self.get_box().node_id 483 return LayoutExpr.height(node_id, fraction)
Get an expression with height of the parent box.
485 def line_x(self, line_idx: int, width_fraction: float | int | None = None) -> LayoutExpr: 486 """ 487 Get an expression with X coordinate of a given line of text in the box. 488 """ 489 node_id = self.get_box().node_id 490 expr = LayoutExpr.line_x(node_id, line_idx) 491 if width_fraction is None: 492 return expr 493 return expr + LayoutExpr.line_width(node_id, line_idx, width_fraction)
Get an expression with X coordinate of a given line of text in the box.
495 def line_y(self, line_idx: int, height_fraction: float | int | None = None) -> LayoutExpr: 496 """ 497 Get an expression with Y coordinate of a given line of text in the box. 498 """ 499 node_id = self.get_box().node_id 500 expr = LayoutExpr.line_y(node_id, line_idx) 501 if height_fraction is None: 502 return expr 503 return expr + LayoutExpr.line_height(node_id, line_idx, height_fraction)
Get an expression with Y coordinate of a given line of text in the box.
505 def line_width(self, line_idx: int, fraction: float | int = 1.0) -> LayoutExpr: 506 """ 507 Get an expression with a width of a given line of text in the box. 508 """ 509 node_id = self.get_box().node_id 510 return LayoutExpr.line_width(node_id, line_idx, fraction)
Get an expression with a width of a given line of text in the box.
512 def line_height(self, line_idx: int, fraction: float | int = 1.0) -> LayoutExpr: 513 """ 514 Get an expression with a height of a given line of text in the box. 515 """ 516 node_id = self.get_box().node_id 517 return LayoutExpr.line_height(node_id, line_idx, fraction)
Get an expression with a height of a given line of text in the box.
519 def text_anchor_x(self, anchor_id: int, width_fraction: float | int | None = None) -> LayoutExpr: 520 """ 521 Get an expression with X coordinate of a given text anchor in the box. 522 """ 523 node_id = self.get_box().node_id 524 expr = LayoutExpr.text_anchor_x(node_id, anchor_id) 525 if width_fraction is None: 526 return expr 527 return expr + LayoutExpr.text_anchor_width(node_id, anchor_id, width_fraction)
Get an expression with X coordinate of a given text anchor in the box.
529 def text_anchor_y(self, anchor_id: int, height_fraction: float | int | None = None) -> LayoutExpr: 530 """ 531 Get an expression with Y coordinate of a given text anchor in the box. 532 """ 533 node_id = self.get_box().node_id 534 expr = LayoutExpr.text_anchor_y(node_id, anchor_id) 535 if height_fraction is None: 536 return expr 537 return expr + LayoutExpr.text_anchor_height(node_id, anchor_id, height_fraction)
Get an expression with Y coordinate of a given text anchor in the box.
539 def text_anchor_width(self, anchor_id: int, fraction: float | int = 1.0) -> LayoutExpr: 540 """ 541 Get an expression with a height of a given text anchor in the box. 542 """ 543 node_id = self.get_box().node_id 544 return LayoutExpr.text_anchor_width(node_id, anchor_id, fraction)
Get an expression with a height of a given text anchor in the box.
546 def text_anchor_height(self, anchor_id: int, fraction: float | int = 1.0) -> LayoutExpr: 547 """ 548 Get an expression with a height of a given text anchor in the box. 549 """ 550 node_id = self.get_box().node_id 551 return LayoutExpr.text_anchor_height(node_id, anchor_id, fraction)
Get an expression with a height of a given text anchor in the box.
554class Box(BoxBuilder): 555 """ 556 The box in slide layout. 557 558 It should be created via calling method `.box()` on a slide or another box. 559 Note that interesting methods came from Box's parent: BoxBuilder. 560 """ 561 562 def __init__( 563 self, 564 deck, 565 slide, 566 box_id, 567 node_id, 568 name: str, 569 z_level: int, 570 ): 571 """ 572 @private 573 """ 574 self.deck = deck 575 self.slide = slide 576 self._box_id = box_id 577 self.node_id = node_id 578 self.name = name 579 self.z_level = z_level 580 581 def get_box(self): 582 """ 583 @private 584 """ 585 return self
The box in slide layout.
It should be created via calling method .box()
on a slide or another box.
Note that interesting methods came from Box's parent: BoxBuilder.
61class Slide(BoxBuilder): 62 """ 63 Represents a slide in a slide deck. 64 65 It should be created via calling method `.new_slide()` on a deck via decorator `@deck.slide()` 66 """ 67 68 def __init__( 69 self, 70 deck: "SlideDeck", 71 slide_id: int, 72 name: str, 73 image_directory: str, 74 debug_layout: str | None, 75 ): 76 """ 77 @private 78 """ 79 self.deck = deck 80 self._slide_id = slide_id 81 self.name = name 82 self.image_directory = image_directory 83 self.debug_layout = debug_layout 84 self.root_box = Box(deck, self, [], 0, name, 0) 85 86 def get_box(self): 87 """ 88 @private 89 """ 90 return self.root_box 91 92 def insert_step(self, value: Step): 93 self.deck._deck.insert_step(self._slide_id, value) 94 95 def remove_step(self, value: Step): 96 self.deck._deck.remove_step(self._slide_id, value) 97 98 def remove_steps_below(self, value: Step): 99 self.deck._deck.remove_steps_below(self._slide_id, value) 100 101 def remove_steps_above(self, value: Step): 102 self.deck._deck.remove_steps_above(self._slide_id, value) 103 104 def set_steps(self, values: set[Step] | frozenset[Step]): 105 self.deck._deck.set_steps(self._slide_id, values) 106 107 def get_steps(self) -> list[Step]: 108 return self.deck._deck.get_steps(self._slide_id) 109 110 def new_slide_at(self, step: int, **slide_kwargs): 111 slide_kwargs["parent_slide"] = (self._slide_id, step) 112 return self.deck.new_slide(**slide_kwargs) 113 114 def slide_at(self, step: int, **slide_kwargs): 115 slide_kwargs["parent_slide"] = (self._slide_id, step) 116 return self.deck.slide(**slide_kwargs)
Represents a slide in a slide deck.
It should be created via calling method .new_slide()
on a deck via decorator @deck.slide()