nelsie

 1from .resources import Resources
 2from .slidedeck import SlideDeck, Slide
 3from .box import Box, BoxBuilderMixin, GridOptions
 4from .textstyle import FontStretch, TextStyle
 5from .shapes import Arrow, Path, Stroke, Rect, Point, Oval
 6from .steps import StepVal
 7from .stepcounter import StepCounter
 8
 9__all__ = [
10    "Resources",
11    "SlideDeck",
12    "Slide",
13    "Box",
14    "BoxBuilderMixin",
15    "FontStretch",
16    "TextStyle",
17    "Arrow",
18    "Path",
19    "Rect",
20    "Point",
21    "Oval",
22    "Stroke",
23    "StepVal",
24    "GridOptions",
25    "StepCounter",
26]
class Resources:
11class Resources:
12    def __init__(
13        self,
14        *,
15        builtin_fonts: bool = True,
16        system_fonts: bool = False,
17        system_fonts_for_svg: bool = True,
18        default_code_syntaxes: bool = True,
19        default_code_themes: bool = True,
20    ):
21        self._resources = nelsie_rs.Resources(
22            system_fonts,
23            system_fonts_for_svg,
24            default_code_syntaxes,
25            default_code_themes,
26        )
27        if builtin_fonts:
28            self._resources.load_fonts_dir(BUILTIN_FONTS_DIR)
29            self._resources.set_generic_family("sans-serif", "DejaVu Sans")
30            self._resources.set_generic_family("monospace", "DejaVu Sans Mono")
31
32    def set_sans_serif(self, font_name):
33        self._resources.set_generic_family("sans-serif", font_name)
34
35    def set_monospace(self, font_name):
36        self._resources.set_generic_family("monospace", font_name)
37
38    def set_serif(self, font_name):
39        self._resources.set_generic_family("serif", font_name)
40
41    def load_code_syntax_dir(self, path: str):
42        self._resources.load_code_syntax_dir(path)
43
44    def load_code_theme_dir(self, path: str):
45        self._resources.load_code_theme_dir(path)
46
47    def load_fonts_dir(self, path: str):
48        self._resources.load_fonts_dir(path)
49
50    def syntaxes(self) -> list[tuple[str, list[str]]]:
51        return self._resources.syntaxes()
52
53    def themes(self) -> list[str]:
54        return self._resources.themes()
Resources( *, builtin_fonts: bool = True, system_fonts: bool = False, system_fonts_for_svg: bool = True, default_code_syntaxes: bool = True, default_code_themes: bool = True)
12    def __init__(
13        self,
14        *,
15        builtin_fonts: bool = True,
16        system_fonts: bool = False,
17        system_fonts_for_svg: bool = True,
18        default_code_syntaxes: bool = True,
19        default_code_themes: bool = True,
20    ):
21        self._resources = nelsie_rs.Resources(
22            system_fonts,
23            system_fonts_for_svg,
24            default_code_syntaxes,
25            default_code_themes,
26        )
27        if builtin_fonts:
28            self._resources.load_fonts_dir(BUILTIN_FONTS_DIR)
29            self._resources.set_generic_family("sans-serif", "DejaVu Sans")
30            self._resources.set_generic_family("monospace", "DejaVu Sans Mono")
def set_sans_serif(self, font_name):
32    def set_sans_serif(self, font_name):
33        self._resources.set_generic_family("sans-serif", font_name)
def set_monospace(self, font_name):
35    def set_monospace(self, font_name):
36        self._resources.set_generic_family("monospace", font_name)
def set_serif(self, font_name):
38    def set_serif(self, font_name):
39        self._resources.set_generic_family("serif", font_name)
def load_code_syntax_dir(self, path: str):
41    def load_code_syntax_dir(self, path: str):
42        self._resources.load_code_syntax_dir(path)
def load_code_theme_dir(self, path: str):
44    def load_code_theme_dir(self, path: str):
45        self._resources.load_code_theme_dir(path)
def load_fonts_dir(self, path: str):
47    def load_fonts_dir(self, path: str):
48        self._resources.load_fonts_dir(path)
def syntaxes(self) -> list[tuple[str, list[str]]]:
50    def syntaxes(self) -> list[tuple[str, list[str]]]:
51        return self._resources.syntaxes()
def themes(self) -> list[str]:
53    def themes(self) -> list[str]:
54        return self._resources.themes()
class SlideDeck:
138class SlideDeck:
139    def __init__(
140        self,
141        *,
142        width: float = 1024,
143        height: float = 768,
144        bg_color: str = "white",
145        text_style: TextStyle | None = None,
146        code_style: TextStyle = DEFAULT_CODE_STYLE,
147        resources: Resources | None = None,
148        default_code_theme: str = "InspiredGitHub",
149        default_code_language: str | None = None,
150    ):
151        """
152        A top-level class of Nelsie. It represents a set of slides.
153
154        Arguments:
155        * width - default width of a slide (could be overridden for each slide)
156        * height - default width of a slide (could be overridden for each slide)
157        * bg_color - default background color a slide (could be overridden for each slide)
158        * resource - Resource instance, if None a new instance is created
159        * default_code_theme - Name of default theme for syntax highlighting (.code() method):
160            Available themes:
161            * "base16-ocean.dark"
162            * "base16-eighties.dark"
163            * "base16-mocha.dark"
164            * "base16-ocean.light"
165            * "InspiredGitHub"
166            * "Solarized (dark)"
167            * "Solarized (light)"
168        * default_code_language - Default language to use for syntax highlighting (.code() method)
169        """
170        if resources is None:
171            resources = Resources()
172        else:
173            check_is_type(resources, Resources)
174
175        nelsie_rs.check_color(bg_color)
176        check_is_int_or_float(width)
177        check_is_int_or_float(height)
178
179        if text_style is not None:
180            check_is_text_style(text_style)
181            text_style = DEFAULT_TEXT_STYLE.merge(text_style)
182        else:
183            text_style = DEFAULT_TEXT_STYLE
184
185        if code_style is not None:
186            check_is_text_style(code_style)
187
188        self.width = width
189        self.height = height
190        self.bg_color = bg_color
191        self.resources = resources
192        self.default_code_theme = default_code_theme
193        self.default_code_language = default_code_language
194        self._text_styles = {
195            "default": text_style,
196            "code": code_style,
197        }
198        self.slides = []
199
200    def get_style(self, name: str) -> TextStyle | None:
201        return self._text_styles.get(name)
202
203    def set_style(self, name: str, style: TextStyle):
204        if name == "default":
205            self.update_style(name, style)
206            return
207        check_is_str(name)
208        check_is_text_style(style)
209        self._text_styles[name] = style
210
211    def update_style(self, name: str, style: TextStyle):
212        check_is_str(name)
213        check_is_text_style(style)
214        old_style = self._text_styles.get(name)
215        if style is None:
216            self._text_styles[name] = style
217        else:
218            self._text_styles[name] = old_style.merge(style)
219
220    def new_slide(
221        self,
222        *,
223        width: Sv[float | None] = None,
224        height: Sv[float | None] = None,
225        name: str = "",
226        bg_color: Sv[str | None] = None,
227        init_steps: Iterable[Step] = (1,),
228        counters=(),
229        postprocess_fn: SlideCallback | None = None,
230        debug_steps: bool = False,
231        debug_layout: bool | str = False,
232    ):
233        if width is None:
234            width = self.width
235        if height is None:
236            height = self.height
237        if bg_color is None:
238            bg_color = self.bg_color
239        slide = Slide(
240            width,
241            height,
242            bg_color,
243            name,
244            init_steps,
245            counters,
246            postprocess_fn,
247            debug_steps,
248            debug_layout,
249        )
250        self.slides.append(slide)
251        return slide
252
253    def slide(
254        self,
255        *,
256        width: float | None = None,
257        height: float | None = None,
258        name: str = "",
259        bg_color: str | None = None,
260        init_steps: Iterable[Step] = (1,),
261        ignore: bool = False,
262        counters: Sequence[str] = (),
263        postprocess_fn: SlideCallback | None = None,
264        debug_steps: bool = False,
265        debug_layout: bool = False,
266    ):
267        """
268        Decorator for creating new slide.
269        It immediately calls the decorated function that should define content of the slide.
270        Slide is automatically added into the deck.
271
272        Example:
273        ```python
274        deck = SlideDeck()
275
276        @deck.slide()
277        def my_first_slide(slide):
278            slide.text("Hello!")
279        ```
280        """
281
282        def helper(fn):
283            if ignore:
284                return None
285            slide = self.new_slide(
286                width=width,
287                height=height,
288                bg_color=bg_color,
289                name=name,
290                init_steps=init_steps,
291                counters=counters,
292                postprocess_fn=postprocess_fn,
293                debug_steps=debug_steps,
294                debug_layout=debug_layout,
295            )
296            fn(slide)
297            return slide
298
299        return helper
300
301    def _create_doc(self):
302        from .toraw import Document, slide_to_raw
303        from .steps_extract import extract_steps
304
305        shared_data = {}
306        raw_pages = []
307        total_counter = CounterStorage()
308
309        slide_steps = {}
310
311        def gather_steps(slide):
312            if slide.subslides is not None:
313                for v in slide.subslides.values():
314                    for s in v:
315                        gather_steps(s)
316            steps = set(slide.init_steps)
317            slide.traverse_tree(shared_data, steps)
318            extract_steps(slide, steps)
319            if slide.subslides is not None:
320                steps.update(slide.subslides.keys())
321            if slide._extra_steps:
322                steps.update(slide._extra_steps)
323            steps = [s for s in steps if is_visible(s, slide._ignore_steps)]
324            steps.sort(key=step_compare_key)
325            slide_steps[slide] = steps
326            total_counter.increment_page(slide.counters, len(steps))
327            total_counter.increment_slide(slide.counters)
328
329        def process_slide(slide):
330            steps = slide_steps[slide]
331            current_counter.increment_slide(slide.counters)
332            for step in steps:
333                if slide.subslides is not None:
334                    parent_inserted = False
335                    subslides = slide.subslides.get(step)
336                    if subslides is not None:
337                        for s in subslides:
338                            if not parent_inserted:
339                                parent_inserted = True
340                                current_counter.increment_page(slide.counters)
341                                page = slide_to_raw(
342                                    self.resources,
343                                    slide,
344                                    step,
345                                    self,
346                                    shared_data,
347                                    current_counter,
348                                    total_counter,
349                                )
350                                raw_pages.append(page)
351                            process_slide(s)
352                current_counter.increment_page(slide.counters)
353                page = slide_to_raw(
354                    self.resources,
355                    slide,
356                    step,
357                    self,
358                    shared_data,
359                    current_counter,
360                    total_counter,
361                )
362                raw_pages.append(page)
363
364        for slide in self.slides:
365            gather_steps(slide)
366
367        current_counter = CounterStorage()
368
369        for slide in self.slides:
370            process_slide(slide)
371
372        return Document(self.resources, raw_pages)
373
374    def render(
375        self,
376        path: str | None,
377        format: Literal["pdf", "png", "svg"] = "pdf",
378        *,
379        compression_level: int = 1,
380        n_threads: int | None = None,
381        progressbar: bool = True,
382    ):
383        """
384        Render slides
385
386        If format is "pdf" then a single PDF file is created. If format is "svg" or "png" then
387        `path` specifies a directory where the slides are created as an individual files.
388
389        If `path` is None then objects are not written to the file system, and they are returned as python objects
390        from the method call.
391
392        `compression_level` defines the level of compression for PDF, allowed ranges are 0-10
393        (0 = no compression, 1 = fast compression, 10 = maximal compression)
394        """
395        doc = self._create_doc()
396        return doc.render(path, format, compression_level, n_threads, progressbar)
SlideDeck( *, width: float = 1024, height: float = 768, bg_color: str = 'white', text_style: TextStyle | None = None, code_style: TextStyle = TextStyle(font='monospace', color=None, size=None, line_spacing=None, italic=None, stretch=None, underline=None, line_through=None, weight=None, bold=None), resources: Resources | None = None, default_code_theme: str = 'InspiredGitHub', default_code_language: str | None = None)
139    def __init__(
140        self,
141        *,
142        width: float = 1024,
143        height: float = 768,
144        bg_color: str = "white",
145        text_style: TextStyle | None = None,
146        code_style: TextStyle = DEFAULT_CODE_STYLE,
147        resources: Resources | None = None,
148        default_code_theme: str = "InspiredGitHub",
149        default_code_language: str | None = None,
150    ):
151        """
152        A top-level class of Nelsie. It represents a set of slides.
153
154        Arguments:
155        * width - default width of a slide (could be overridden for each slide)
156        * height - default width of a slide (could be overridden for each slide)
157        * bg_color - default background color a slide (could be overridden for each slide)
158        * resource - Resource instance, if None a new instance is created
159        * default_code_theme - Name of default theme for syntax highlighting (.code() method):
160            Available themes:
161            * "base16-ocean.dark"
162            * "base16-eighties.dark"
163            * "base16-mocha.dark"
164            * "base16-ocean.light"
165            * "InspiredGitHub"
166            * "Solarized (dark)"
167            * "Solarized (light)"
168        * default_code_language - Default language to use for syntax highlighting (.code() method)
169        """
170        if resources is None:
171            resources = Resources()
172        else:
173            check_is_type(resources, Resources)
174
175        nelsie_rs.check_color(bg_color)
176        check_is_int_or_float(width)
177        check_is_int_or_float(height)
178
179        if text_style is not None:
180            check_is_text_style(text_style)
181            text_style = DEFAULT_TEXT_STYLE.merge(text_style)
182        else:
183            text_style = DEFAULT_TEXT_STYLE
184
185        if code_style is not None:
186            check_is_text_style(code_style)
187
188        self.width = width
189        self.height = height
190        self.bg_color = bg_color
191        self.resources = resources
192        self.default_code_theme = default_code_theme
193        self.default_code_language = default_code_language
194        self._text_styles = {
195            "default": text_style,
196            "code": code_style,
197        }
198        self.slides = []

A top-level class of Nelsie. It represents a set of slides.

Arguments:

  • width - default width of a slide (could be overridden for each slide)
  • height - default width of a slide (could be overridden for each slide)
  • bg_color - default background color a slide (could be overridden for each slide)
  • resource - Resource instance, if None a new instance is created
  • 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)
width
height
bg_color
resources
default_code_theme
default_code_language
slides
def get_style(self, name: str) -> TextStyle | None:
200    def get_style(self, name: str) -> TextStyle | None:
201        return self._text_styles.get(name)
def set_style(self, name: str, style: TextStyle):
203    def set_style(self, name: str, style: TextStyle):
204        if name == "default":
205            self.update_style(name, style)
206            return
207        check_is_str(name)
208        check_is_text_style(style)
209        self._text_styles[name] = style
def update_style(self, name: str, style: TextStyle):
211    def update_style(self, name: str, style: TextStyle):
212        check_is_str(name)
213        check_is_text_style(style)
214        old_style = self._text_styles.get(name)
215        if style is None:
216            self._text_styles[name] = style
217        else:
218            self._text_styles[name] = old_style.merge(style)
def new_slide( self, *, width: Sv[float | None] = None, height: Sv[float | None] = None, name: str = '', bg_color: Sv[str | None] = None, init_steps: Iterable[int | tuple[int]] = (1,), counters=(), postprocess_fn: SlideCallback | None = None, debug_steps: bool = False, debug_layout: bool | str = False):
220    def new_slide(
221        self,
222        *,
223        width: Sv[float | None] = None,
224        height: Sv[float | None] = None,
225        name: str = "",
226        bg_color: Sv[str | None] = None,
227        init_steps: Iterable[Step] = (1,),
228        counters=(),
229        postprocess_fn: SlideCallback | None = None,
230        debug_steps: bool = False,
231        debug_layout: bool | str = False,
232    ):
233        if width is None:
234            width = self.width
235        if height is None:
236            height = self.height
237        if bg_color is None:
238            bg_color = self.bg_color
239        slide = Slide(
240            width,
241            height,
242            bg_color,
243            name,
244            init_steps,
245            counters,
246            postprocess_fn,
247            debug_steps,
248            debug_layout,
249        )
250        self.slides.append(slide)
251        return slide
def slide( self, *, width: float | None = None, height: float | None = None, name: str = '', bg_color: str | None = None, init_steps: Iterable[int | tuple[int]] = (1,), ignore: bool = False, counters: Sequence[str] = (), postprocess_fn: SlideCallback | None = None, debug_steps: bool = False, debug_layout: bool = False):
253    def slide(
254        self,
255        *,
256        width: float | None = None,
257        height: float | None = None,
258        name: str = "",
259        bg_color: str | None = None,
260        init_steps: Iterable[Step] = (1,),
261        ignore: bool = False,
262        counters: Sequence[str] = (),
263        postprocess_fn: SlideCallback | None = None,
264        debug_steps: bool = False,
265        debug_layout: bool = False,
266    ):
267        """
268        Decorator for creating new slide.
269        It immediately calls the decorated function that should define content of the slide.
270        Slide is automatically added into the deck.
271
272        Example:
273        ```python
274        deck = SlideDeck()
275
276        @deck.slide()
277        def my_first_slide(slide):
278            slide.text("Hello!")
279        ```
280        """
281
282        def helper(fn):
283            if ignore:
284                return None
285            slide = self.new_slide(
286                width=width,
287                height=height,
288                bg_color=bg_color,
289                name=name,
290                init_steps=init_steps,
291                counters=counters,
292                postprocess_fn=postprocess_fn,
293                debug_steps=debug_steps,
294                debug_layout=debug_layout,
295            )
296            fn(slide)
297            return slide
298
299        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!")
def render( self, path: str | None, format: Literal['pdf', 'png', 'svg'] = 'pdf', *, compression_level: int = 1, n_threads: int | None = None, progressbar: bool = True):
374    def render(
375        self,
376        path: str | None,
377        format: Literal["pdf", "png", "svg"] = "pdf",
378        *,
379        compression_level: int = 1,
380        n_threads: int | None = None,
381        progressbar: bool = True,
382    ):
383        """
384        Render slides
385
386        If format is "pdf" then a single PDF file is created. If format is "svg" or "png" then
387        `path` specifies a directory where the slides are created as an individual files.
388
389        If `path` is None then objects are not written to the file system, and they are returned as python objects
390        from the method call.
391
392        `compression_level` defines the level of compression for PDF, allowed ranges are 0-10
393        (0 = no compression, 1 = fast compression, 10 = maximal compression)
394        """
395        doc = self._create_doc()
396        return doc.render(path, format, compression_level, n_threads, progressbar)

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)

class Slide(nelsie.BoxBuilderMixin):
 30class Slide(BoxBuilderMixin):
 31    def __init__(
 32        self,
 33        width: Sv[float],
 34        height: Sv[float],
 35        bg_color: Sv[str],
 36        name: str,
 37        init_steps: Iterable[Step],
 38        counters: Sequence[str],
 39        postprocess_fn: SlideCallback | None = None,
 40        debug_steps: bool = False,
 41        debug_layout: bool | str = False,
 42    ):
 43        self.width = width
 44        self.height = height
 45        self.bg_color = bg_color
 46        self.name = name
 47        self.children = []
 48        self.init_steps = init_steps
 49        self.counters = counters
 50        self.postprocess_fn = postprocess_fn
 51        self.subslides = None
 52        self.debug_steps = debug_steps
 53        self.debug_layout = debug_layout
 54
 55        self._text_styles = None
 56        self._extra_steps = None
 57        self._ignore_steps = None
 58
 59    def add(self, box):
 60        self.children.append(box)
 61
 62    def slide_at(self, step, **kwargs):
 63        def helper(fn):
 64            slide = self.new_slide_at(step, **kwargs)
 65            fn(slide)
 66            return slide
 67
 68        return helper
 69
 70    def new_slide_at(
 71        self,
 72        step: Step,
 73        *,
 74        width: Sv[float | None] = None,
 75        height: Sv[float | None] = None,
 76        name: str = "",
 77        bg_color: Sv[str | None] = None,
 78        init_steps: Iterable[Step] = (1,),
 79        counters=(),
 80        postprocess_fn: SlideCallback | None = None,
 81        debug_steps: bool = False,
 82        debug_layout: bool | str = False,
 83    ):
 84        if width is None:
 85            width = self.width
 86        if height is None:
 87            height = self.height
 88        if bg_color is None:
 89            bg_color = self.bg_color
 90        slide = Slide(
 91            width,
 92            height,
 93            bg_color,
 94            name,
 95            init_steps,
 96            counters,
 97            postprocess_fn,
 98            debug_steps,
 99            debug_layout,
100        )
101        if self.subslides is None:
102            self.subslides = {}
103        if step not in self.subslides:
104            self.subslides[step] = [slide]
105        else:
106            self.subslides[step].append(slide)
107        return slide
108
109    def insert_step(self, step: Step):
110        if self._extra_steps is None:
111            self._extra_steps = set()
112        self._extra_steps.add(step)
113
114    def ignore_steps(self, ignored_steps: BoolStepDef):
115        self._ignore_steps = parse_bool_steps(ignored_steps)
116
117    def _set_style(self, name: str, style: Sn[TextStyle]):
118        if self._text_styles is None:
119            self._text_styles = {}
120        self._text_styles[name] = style
121
122    def _get_style(self, name: str) -> Sn[TextStyle] | None:
123        if self._text_styles is not None:
124            return self._text_styles.get(name)
125
126    def traverse_tree(self, shared_data, steps: set[Step]):
127        traverse_children(self.children, shared_data, steps)
128
129    def copy(self):
130        """
131        Return copy of slide that is safe to add new boxes into it
132        """
133        slide = copy(self)
134        slide.children = slide.children[:]
135        return slide
Slide( width: Sv[float], height: Sv[float], bg_color: Sv[str], name: str, init_steps: Iterable[int | tuple[int]], counters: Sequence[str], postprocess_fn: SlideCallback | None = None, debug_steps: bool = False, debug_layout: bool | str = False)
31    def __init__(
32        self,
33        width: Sv[float],
34        height: Sv[float],
35        bg_color: Sv[str],
36        name: str,
37        init_steps: Iterable[Step],
38        counters: Sequence[str],
39        postprocess_fn: SlideCallback | None = None,
40        debug_steps: bool = False,
41        debug_layout: bool | str = False,
42    ):
43        self.width = width
44        self.height = height
45        self.bg_color = bg_color
46        self.name = name
47        self.children = []
48        self.init_steps = init_steps
49        self.counters = counters
50        self.postprocess_fn = postprocess_fn
51        self.subslides = None
52        self.debug_steps = debug_steps
53        self.debug_layout = debug_layout
54
55        self._text_styles = None
56        self._extra_steps = None
57        self._ignore_steps = None
def width(self, fraction: IntOrFloat = 1.0) -> nelsie.layoutexpr.LayoutExpr:
291    def width(self, fraction: IntOrFloat = 1.0) -> LayoutExpr:
292        """
293        Get an expression with width of the parent box.
294        """
295        check_is_int_or_float(fraction)
296        node_id = id(self)
297        return LayoutExpr.width(node_id, fraction)

Get an expression with width of the parent box.

def height(self, fraction: IntOrFloat = 1.0) -> nelsie.layoutexpr.LayoutExpr:
299    def height(self, fraction: IntOrFloat = 1.0) -> LayoutExpr:
300        """
301        Get an expression with height of the parent box.
302        """
303        check_is_int_or_float(fraction)
304        node_id = id(self)
305        return LayoutExpr.height(node_id, fraction)

Get an expression with height of the parent box.

bg_color
name
children
init_steps
counters
postprocess_fn
subslides
debug_steps
debug_layout
def add(self, box):
59    def add(self, box):
60        self.children.append(box)

Adds Box or a geometry item into the box

def slide_at(self, step, **kwargs):
62    def slide_at(self, step, **kwargs):
63        def helper(fn):
64            slide = self.new_slide_at(step, **kwargs)
65            fn(slide)
66            return slide
67
68        return helper
def new_slide_at( self, step: int | tuple[int], *, width: Sv[float | None] = None, height: Sv[float | None] = None, name: str = '', bg_color: Sv[str | None] = None, init_steps: Iterable[int | tuple[int]] = (1,), counters=(), postprocess_fn: SlideCallback | None = None, debug_steps: bool = False, debug_layout: bool | str = False):
 70    def new_slide_at(
 71        self,
 72        step: Step,
 73        *,
 74        width: Sv[float | None] = None,
 75        height: Sv[float | None] = None,
 76        name: str = "",
 77        bg_color: Sv[str | None] = None,
 78        init_steps: Iterable[Step] = (1,),
 79        counters=(),
 80        postprocess_fn: SlideCallback | None = None,
 81        debug_steps: bool = False,
 82        debug_layout: bool | str = False,
 83    ):
 84        if width is None:
 85            width = self.width
 86        if height is None:
 87            height = self.height
 88        if bg_color is None:
 89            bg_color = self.bg_color
 90        slide = Slide(
 91            width,
 92            height,
 93            bg_color,
 94            name,
 95            init_steps,
 96            counters,
 97            postprocess_fn,
 98            debug_steps,
 99            debug_layout,
100        )
101        if self.subslides is None:
102            self.subslides = {}
103        if step not in self.subslides:
104            self.subslides[step] = [slide]
105        else:
106            self.subslides[step].append(slide)
107        return slide
def insert_step(self, step: int | tuple[int]):
109    def insert_step(self, step: Step):
110        if self._extra_steps is None:
111            self._extra_steps = set()
112        self._extra_steps.add(step)
def ignore_steps(self, ignored_steps: BoolStepDef):
114    def ignore_steps(self, ignored_steps: BoolStepDef):
115        self._ignore_steps = parse_bool_steps(ignored_steps)
def traverse_tree(self, shared_data, steps: set[int | tuple[int]]):
126    def traverse_tree(self, shared_data, steps: set[Step]):
127        traverse_children(self.children, shared_data, steps)
def copy(self):
129    def copy(self):
130        """
131        Return copy of slide that is safe to add new boxes into it
132        """
133        slide = copy(self)
134        slide.children = slide.children[:]
135        return slide

Return copy of slide that is safe to add new boxes into it

class Box(nelsie.BoxBuilderMixin):
432class Box(BoxBuilderMixin):
433    def __init__(
434        self,
435        *,
436        x: Sn[Position] = None,
437        y: Sn[Position] = None,
438        show: BoolStepDef = True,
439        active: BoolStepDef = True,
440        z_level: Sn[int] = None,
441        width: Sn[Size] = None,
442        height: Sn[Size] = None,
443        bg_color: Sn[str] = None,
444        row: Sv[bool] = False,
445        reverse: Sv[bool] = False,
446        p_left: Sv[Length] = 0,
447        p_right: Sv[Length] = 0,
448        p_top: Sv[Length] = 0,
449        p_bottom: Sv[Length] = 0,
450        m_left: Sv[LengthAuto] = 0,
451        m_right: Sv[LengthAuto] = 0,
452        m_top: Sv[LengthAuto] = 0,
453        m_bottom: Sv[LengthAuto] = 0,
454        flex_grow: Sv[float] = 0.0,
455        flex_shrink: Sv[float] = 1.0,
456        align_items: Sn[AlignItems] = None,
457        align_self: Sn[AlignItems] = None,
458        justify_self: Sn[AlignItems] = None,
459        align_content: Sn[AlignContent] = None,
460        justify_content: Sn[AlignContent] = None,
461        gap_x: Sv[Length] = 0,
462        gap_y: Sv[Length] = 0,
463        grid: Sn[GridOptions] = None,
464        border_radius: Sv[IntOrFloat] = 0,
465        url: Sn[str] = None,
466        name: str = "",
467        debug_layout: bool | str | None = None,
468    ):
469        """
470        Create a new box. See [Box reference](https://spirali.github.io/nelsie/guide/box/) for documentation.
471        """
472        sn_check(x, check_position)
473        sn_check(y, check_position)
474        sn_check(z_level, check_is_int)
475        sn_check(width, check_size)
476        sn_check(height, check_size)
477        sn_check(bg_color, check_color)
478        sv_check(row, check_is_bool)
479        sv_check(reverse, check_is_bool)
480        sv_check(p_left, check_length)
481        sv_check(p_right, check_length)
482        sv_check(p_top, check_length)
483        sv_check(p_bottom, check_length)
484        sv_check(m_left, check_length_auto)
485        sv_check(m_right, check_length_auto)
486        sv_check(m_top, check_length_auto)
487        sv_check(m_bottom, check_length_auto)
488
489        sv_check(flex_grow, check_is_int_or_float)
490        sv_check(flex_shrink, check_is_int_or_float)
491        sv_check(gap_x, check_length)
492        sv_check(gap_y, check_length)
493        sn_check(align_items, check_align_items)
494        sn_check(align_self, check_align_items)
495        sn_check(justify_self, check_align_items)
496        sn_check(align_content, check_align_content)
497        sn_check(justify_content, check_align_content)
498        sv_check(border_radius, check_is_int_or_float)
499        sn_check(url, check_is_str)
500        check_is_str(name)
501
502        if isinstance(debug_layout, str):
503            check_color(debug_layout)
504
505        self._show = parse_bool_steps(show)
506        self._active = parse_bool_steps(active)
507        self._x = x
508        self._y = y
509        self._z_level = z_level
510        self._width = width
511        self._height = height
512        self._bg_color = bg_color
513        self._content = None
514        self._children = []
515        self._row = row
516        self._reverse = reverse
517        self._p_left = p_left
518        self._p_right = p_right
519        self._p_top = p_top
520        self._p_bottom = p_bottom
521        self._m_left = m_left
522        self._m_right = m_right
523        self._m_top = m_top
524        self._m_bottom = m_bottom
525
526        self._flex_grow = flex_grow
527        self._flex_shrink = flex_shrink
528        self._align_items = align_items
529        self._align_self = align_self
530        self._justify_self = justify_self
531        self._align_content = align_content
532        self._justify_content = justify_content
533        self._gap_x = gap_x
534        self._gap_y = gap_y
535        self._grid = grid
536        self._debug_layout = debug_layout
537        self._border_radius = border_radius
538        self._url = url
539        self.name = name
540        self._text_styles: dict[str, Sn[TextStyle]] | None = None
541
542    def margin(
543        self,
544        all: Sn[LengthAuto] = None,
545        *,
546        x: Sn[LengthAuto] = None,
547        y: Sn[LengthAuto] = None,
548        left: Sn[LengthAuto] = None,
549        right: Sn[LengthAuto] = None,
550        top: Sn[LengthAuto] = None,
551        bottom: Sn[LengthAuto] = None,
552    ):
553        """
554        Sets box's margin
555        """
556        if all is not None:
557            sn_check(all, check_length_auto)
558            self._m_top = all
559            self._m_bottom = all
560            self._m_left = all
561            self._m_right = all
562
563        if x is not None:
564            sn_check(x, check_length_auto)
565            self._m_left = x
566            self._m_right = x
567
568        if y is not None:
569            sn_check(y, check_length_auto)
570            self._m_top = y
571            self._m_bottom = y
572
573        if left is not None:
574            sn_check(left, check_length_auto)
575            self._m_left = left
576
577        if right is not None:
578            sn_check(right, check_length_auto)
579            self._m_right = right
580
581        if top is not None:
582            sn_check(top, check_length_auto)
583            self._m_top = top
584
585        if bottom is not None:
586            sn_check(bottom, check_length_auto)
587            self._m_bottom = bottom
588        return self
589
590    def padding(
591        self,
592        all: Sn[LengthAuto] = None,
593        *,
594        x: Sn[LengthAuto] = None,
595        y: Sn[LengthAuto] = None,
596        left: Sn[LengthAuto] = None,
597        right: Sn[LengthAuto] = None,
598        top: Sn[LengthAuto] = None,
599        bottom: Sn[LengthAuto] = None,
600    ):
601        """
602        Sets box's padding.
603        """
604        if all is not None:
605            sn_check(all, check_length)
606            self._p_top = all
607            self._p_bottom = all
608            self._p_left = all
609            self._p_right = all
610
611        if x is not None:
612            sn_check(x, check_length)
613            self._p_left = x
614            self._p_right = x
615
616        if y is not None:
617            sn_check(y, check_length)
618            self._p_top = y
619            self._p_bottom = y
620
621        if left is not None:
622            sn_check(left, check_length)
623            self._p_left = left
624
625        if right is not None:
626            sn_check(right, check_length)
627            self._p_right = right
628
629        if top is not None:
630            sn_check(top, check_length)
631            self._p_top = top
632
633        if bottom is not None:
634            sn_check(bottom, check_length)
635            self._p_bottom = bottom
636        return self
637
638    def draw_line(self, p1: Point, p2: Point, **path_args):
639        """
640        Shortcut for drawing a simple line
641        """
642        path = Path(**path_args)
643        path.move_to(p1)
644        path.line_to(p2)
645        return self.add(path)
646
647    def add(self, item):
648        """
649        Adds Box or a geometry item into the box
650        """
651        self._children.append(item)
652
653    def traverse_tree(self, shared_data, steps):
654        if self._content is not None:
655            self._content.traverse_tree(shared_data, steps)
656        traverse_children(self._children, shared_data, steps)
657
658    def _set_style(self, name: str, style: Sn[TextStyle]):
659        if self._text_styles is None:
660            self._text_styles = {}
661        self._text_styles[name] = style
662
663    def _get_style(self, name: str) -> Sn[TextStyle] | None:
664        if self._text_styles is None:
665            return None
666        return self._text_styles.get(name)
Box( *, x: Sn[Position] = None, y: Sn[Position] = None, show: BoolStepDef = True, active: BoolStepDef = True, z_level: Sn[int] = None, width: Sn[Size] = None, height: Sn[Size] = None, bg_color: Sn[str] = None, row: Sv[bool] = False, reverse: Sv[bool] = False, p_left: Sv[Length] = 0, p_right: Sv[Length] = 0, p_top: Sv[Length] = 0, p_bottom: Sv[Length] = 0, m_left: Sv[LengthAuto] = 0, m_right: Sv[LengthAuto] = 0, m_top: Sv[LengthAuto] = 0, m_bottom: Sv[LengthAuto] = 0, flex_grow: Sv[float] = 0.0, flex_shrink: Sv[float] = 1.0, align_items: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline']] = None, align_self: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline']] = None, justify_self: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline']] = None, align_content: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-evenly', 'space-around']] = None, justify_content: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-evenly', 'space-around']] = None, gap_x: Sv[Length] = 0, gap_y: Sv[Length] = 0, grid: Sn[GridOptions] = None, border_radius: Sv[IntOrFloat] = 0, url: Sn[str] = None, name: str = '', debug_layout: bool | str | None = None)
433    def __init__(
434        self,
435        *,
436        x: Sn[Position] = None,
437        y: Sn[Position] = None,
438        show: BoolStepDef = True,
439        active: BoolStepDef = True,
440        z_level: Sn[int] = None,
441        width: Sn[Size] = None,
442        height: Sn[Size] = None,
443        bg_color: Sn[str] = None,
444        row: Sv[bool] = False,
445        reverse: Sv[bool] = False,
446        p_left: Sv[Length] = 0,
447        p_right: Sv[Length] = 0,
448        p_top: Sv[Length] = 0,
449        p_bottom: Sv[Length] = 0,
450        m_left: Sv[LengthAuto] = 0,
451        m_right: Sv[LengthAuto] = 0,
452        m_top: Sv[LengthAuto] = 0,
453        m_bottom: Sv[LengthAuto] = 0,
454        flex_grow: Sv[float] = 0.0,
455        flex_shrink: Sv[float] = 1.0,
456        align_items: Sn[AlignItems] = None,
457        align_self: Sn[AlignItems] = None,
458        justify_self: Sn[AlignItems] = None,
459        align_content: Sn[AlignContent] = None,
460        justify_content: Sn[AlignContent] = None,
461        gap_x: Sv[Length] = 0,
462        gap_y: Sv[Length] = 0,
463        grid: Sn[GridOptions] = None,
464        border_radius: Sv[IntOrFloat] = 0,
465        url: Sn[str] = None,
466        name: str = "",
467        debug_layout: bool | str | None = None,
468    ):
469        """
470        Create a new box. See [Box reference](https://spirali.github.io/nelsie/guide/box/) for documentation.
471        """
472        sn_check(x, check_position)
473        sn_check(y, check_position)
474        sn_check(z_level, check_is_int)
475        sn_check(width, check_size)
476        sn_check(height, check_size)
477        sn_check(bg_color, check_color)
478        sv_check(row, check_is_bool)
479        sv_check(reverse, check_is_bool)
480        sv_check(p_left, check_length)
481        sv_check(p_right, check_length)
482        sv_check(p_top, check_length)
483        sv_check(p_bottom, check_length)
484        sv_check(m_left, check_length_auto)
485        sv_check(m_right, check_length_auto)
486        sv_check(m_top, check_length_auto)
487        sv_check(m_bottom, check_length_auto)
488
489        sv_check(flex_grow, check_is_int_or_float)
490        sv_check(flex_shrink, check_is_int_or_float)
491        sv_check(gap_x, check_length)
492        sv_check(gap_y, check_length)
493        sn_check(align_items, check_align_items)
494        sn_check(align_self, check_align_items)
495        sn_check(justify_self, check_align_items)
496        sn_check(align_content, check_align_content)
497        sn_check(justify_content, check_align_content)
498        sv_check(border_radius, check_is_int_or_float)
499        sn_check(url, check_is_str)
500        check_is_str(name)
501
502        if isinstance(debug_layout, str):
503            check_color(debug_layout)
504
505        self._show = parse_bool_steps(show)
506        self._active = parse_bool_steps(active)
507        self._x = x
508        self._y = y
509        self._z_level = z_level
510        self._width = width
511        self._height = height
512        self._bg_color = bg_color
513        self._content = None
514        self._children = []
515        self._row = row
516        self._reverse = reverse
517        self._p_left = p_left
518        self._p_right = p_right
519        self._p_top = p_top
520        self._p_bottom = p_bottom
521        self._m_left = m_left
522        self._m_right = m_right
523        self._m_top = m_top
524        self._m_bottom = m_bottom
525
526        self._flex_grow = flex_grow
527        self._flex_shrink = flex_shrink
528        self._align_items = align_items
529        self._align_self = align_self
530        self._justify_self = justify_self
531        self._align_content = align_content
532        self._justify_content = justify_content
533        self._gap_x = gap_x
534        self._gap_y = gap_y
535        self._grid = grid
536        self._debug_layout = debug_layout
537        self._border_radius = border_radius
538        self._url = url
539        self.name = name
540        self._text_styles: dict[str, Sn[TextStyle]] | None = None

Create a new box. See Box reference for documentation.

name
def margin( self, all: Sn[LengthAuto] = None, *, x: Sn[LengthAuto] = None, y: Sn[LengthAuto] = None, left: Sn[LengthAuto] = None, right: Sn[LengthAuto] = None, top: Sn[LengthAuto] = None, bottom: Sn[LengthAuto] = None):
542    def margin(
543        self,
544        all: Sn[LengthAuto] = None,
545        *,
546        x: Sn[LengthAuto] = None,
547        y: Sn[LengthAuto] = None,
548        left: Sn[LengthAuto] = None,
549        right: Sn[LengthAuto] = None,
550        top: Sn[LengthAuto] = None,
551        bottom: Sn[LengthAuto] = None,
552    ):
553        """
554        Sets box's margin
555        """
556        if all is not None:
557            sn_check(all, check_length_auto)
558            self._m_top = all
559            self._m_bottom = all
560            self._m_left = all
561            self._m_right = all
562
563        if x is not None:
564            sn_check(x, check_length_auto)
565            self._m_left = x
566            self._m_right = x
567
568        if y is not None:
569            sn_check(y, check_length_auto)
570            self._m_top = y
571            self._m_bottom = y
572
573        if left is not None:
574            sn_check(left, check_length_auto)
575            self._m_left = left
576
577        if right is not None:
578            sn_check(right, check_length_auto)
579            self._m_right = right
580
581        if top is not None:
582            sn_check(top, check_length_auto)
583            self._m_top = top
584
585        if bottom is not None:
586            sn_check(bottom, check_length_auto)
587            self._m_bottom = bottom
588        return self

Sets box's margin

def padding( self, all: Sn[LengthAuto] = None, *, x: Sn[LengthAuto] = None, y: Sn[LengthAuto] = None, left: Sn[LengthAuto] = None, right: Sn[LengthAuto] = None, top: Sn[LengthAuto] = None, bottom: Sn[LengthAuto] = None):
590    def padding(
591        self,
592        all: Sn[LengthAuto] = None,
593        *,
594        x: Sn[LengthAuto] = None,
595        y: Sn[LengthAuto] = None,
596        left: Sn[LengthAuto] = None,
597        right: Sn[LengthAuto] = None,
598        top: Sn[LengthAuto] = None,
599        bottom: Sn[LengthAuto] = None,
600    ):
601        """
602        Sets box's padding.
603        """
604        if all is not None:
605            sn_check(all, check_length)
606            self._p_top = all
607            self._p_bottom = all
608            self._p_left = all
609            self._p_right = all
610
611        if x is not None:
612            sn_check(x, check_length)
613            self._p_left = x
614            self._p_right = x
615
616        if y is not None:
617            sn_check(y, check_length)
618            self._p_top = y
619            self._p_bottom = y
620
621        if left is not None:
622            sn_check(left, check_length)
623            self._p_left = left
624
625        if right is not None:
626            sn_check(right, check_length)
627            self._p_right = right
628
629        if top is not None:
630            sn_check(top, check_length)
631            self._p_top = top
632
633        if bottom is not None:
634            sn_check(bottom, check_length)
635            self._p_bottom = bottom
636        return self

Sets box's padding.

def draw_line(self, p1: Point, p2: Point, **path_args):
638    def draw_line(self, p1: Point, p2: Point, **path_args):
639        """
640        Shortcut for drawing a simple line
641        """
642        path = Path(**path_args)
643        path.move_to(p1)
644        path.line_to(p2)
645        return self.add(path)

Shortcut for drawing a simple line

def add(self, item):
647    def add(self, item):
648        """
649        Adds Box or a geometry item into the box
650        """
651        self._children.append(item)

Adds Box or a geometry item into the box

def traverse_tree(self, shared_data, steps):
653    def traverse_tree(self, shared_data, steps):
654        if self._content is not None:
655            self._content.traverse_tree(shared_data, steps)
656        traverse_children(self._children, shared_data, steps)
class BoxBuilderMixin:
 59class BoxBuilderMixin:
 60    def add(self, box: Union[Path, Rect, Oval, "Box"]):
 61        """
 62        Adds Box or a geometry item into the box
 63        """
 64        raise NotImplementedError
 65
 66    def _set_style(self, name: str, style: Sn[TextStyle]):
 67        raise NotImplementedError
 68
 69    def _get_style(self, name: str) -> Sn[TextStyle] | None:
 70        NotImplementedError
 71
 72    def box(
 73        self,
 74        *,
 75        x: Sn[Position] = None,
 76        y: Sn[Position] = None,
 77        z_level: Sn[int] = None,
 78        show: BoolStepDef = True,
 79        active: BoolStepDef = True,
 80        width: Sn[Size] = None,
 81        height: Sn[Size] = None,
 82        bg_color: Sn[str] = None,
 83        row: Sv[bool] = False,
 84        reverse: Sv[bool] = False,
 85        p_left: Sv[Length] = 0,
 86        p_right: Sv[Length] = 0,
 87        p_top: Sv[Length] = 0,
 88        p_bottom: Sv[Length] = 0,
 89        m_left: Sv[LengthAuto] = 0,
 90        m_right: Sv[LengthAuto] = 0,
 91        m_top: Sv[LengthAuto] = 0,
 92        m_bottom: Sv[LengthAuto] = 0,
 93        flex_grow: Sv[float] = 0.0,
 94        flex_shrink: Sv[float] = 1.0,
 95        align_items: Sn[AlignItems] = None,
 96        align_self: Sn[AlignItems] = None,
 97        justify_self: Sn[AlignItems] = None,
 98        align_content: Sn[AlignContent] = None,
 99        justify_content: Sn[AlignContent] = None,
100        gap_x: Sv[Length] = 0,
101        gap_y: Sv[Length] = 0,
102        grid: Sn[GridOptions] = None,
103        border_radius: Sv[IntOrFloat] = 0,
104        url: Sn[str] = None,
105        name: str = "",
106        debug_layout: bool | str | None = None,
107    ):
108        """
109        Create a new child box. See [Box reference](https://spirali.github.io/nelsie/guide/box/) for documentation
110        """
111        box = Box(
112            x=x,
113            y=y,
114            z_level=z_level,
115            show=show,
116            active=active,
117            width=width,
118            height=height,
119            bg_color=bg_color,
120            row=row,
121            reverse=reverse,
122            p_left=p_left,
123            p_right=p_right,
124            p_top=p_top,
125            p_bottom=p_bottom,
126            m_left=m_left,
127            m_right=m_right,
128            m_top=m_top,
129            m_bottom=m_bottom,
130            flex_grow=flex_grow,
131            flex_shrink=flex_shrink,
132            align_items=align_items,
133            align_self=align_self,
134            justify_self=justify_self,
135            align_content=align_content,
136            justify_content=justify_content,
137            gap_x=gap_x,
138            gap_y=gap_y,
139            grid=grid,
140            name=name,
141            border_radius=border_radius,
142            debug_layout=debug_layout,
143            url=url,
144        )
145        self.add(box)
146        return box
147
148    def overlay(self, **box_args):
149        """
150        Create a new box that spans over the box
151        """
152        box_args.setdefault("x", 0)
153        box_args.setdefault("y", 0)
154        box_args.setdefault("width", "100%")
155        box_args.setdefault("height", "100%")
156        return self.box(**box_args)
157
158    def text(
159        self,
160        text: Sv[str],
161        style: Sn[TextStyle] = None,
162        *,
163        align: Sv[TextAlign] = "start",
164        strip: bool = True,
165        parse_styles: bool = True,
166        style_delimiters: str = "~{}",
167        parse_steps: bool | str = False,
168        **box_args,
169    ):
170        if strip and isinstance(text, str):
171            text = text.strip()
172        if parse_steps:
173            text = parse_steps_helper(text, parse_steps, strip)
174        sv_check(text, check_is_str)
175        sv_check(align, check_text_align)
176        sn_check(style, check_is_str_or_text_style)
177        box = self.box(**box_args)
178        box._content = TextContent(
179            text=text,
180            style=style,
181            align=align,
182            is_code=False,
183            parse_styles=parse_styles,
184            style_delimiters=style_delimiters,
185            syntax_language=None,
186            syntax_theme=None,
187        )
188        return box
189
190    def code(
191        self,
192        text: Sv[str],
193        language: Sn[str] = None,
194        style: Sn[TextStyle] = None,
195        *,
196        align: Sv[TextAlign] = "start",
197        strip: bool = True,
198        theme: Sn[str] = None,
199        parse_styles: bool = False,
200        style_delimiters: str = "~{}",
201        parse_steps: bool | str = False,
202        **box_args,
203    ):
204        if strip and isinstance(text, str):
205            text = text.strip()
206        if parse_steps:
207            text = parse_steps_helper(text, parse_steps, strip)
208        sv_check(text, check_is_str)
209        sv_check(align, check_text_align)
210        sn_check(style, check_is_str_or_text_style)
211        sn_check(language, check_is_str)
212        sn_check(theme, check_is_str)
213        box = self.box(**box_args)
214        box._content = TextContent(
215            text=text,
216            style=style,
217            align=align,
218            is_code=True,
219            syntax_language=language,
220            syntax_theme=theme,
221            parse_styles=parse_styles,
222            style_delimiters=style_delimiters,
223        )
224        return box
225
226    def image(
227        self,
228        path_or_data: Sn[PathOrImageData],
229        *,
230        enable_steps: Sv[bool] = True,
231        shift_steps: int = 0,
232        **box_args,
233    ):
234        sn_check(path_or_data, check_image_path_or_data)
235        sv_check(enable_steps, check_is_bool)
236        sv_check(shift_steps, check_is_int)
237        path_or_data = sn_map(path_or_data, normalize_and_watch_image_path)
238        box = self.box(**box_args)
239        box._content = ImageContent(path_or_data, enable_steps, shift_steps)
240        return box
241
242    def set_style(self, name: str, style: Sn[TextStyle]):
243        if name == "default":
244            self.update_style(name, style)
245            return
246        check_is_str(name)
247        sn_check(style, check_is_text_style)
248        self._set_style(name, style)
249
250    def update_style(self, name: str, style: TextStyle):
251        check_is_str(name)
252        check_is_text_style(style)
253        old_style = self._get_style(name)
254        if old_style is None:
255            self._set_style(name, style)
256        elif isinstance(old_style, TextStyle):
257            self._set_style(name, old_style.merge(style))
258        else:
259            raise Exception(
260                "Non-primitive style cannot be updated; use set_style instead"
261            )
262
263    def x(self, width_fraction: IntOrFloat = 0) -> LayoutExpr:
264        """
265        Get an expression with X coordinate relative to the box.
266        """
267        check_is_int_or_float(width_fraction)
268        node_id = id(self)
269        expr = LayoutExpr.x(node_id)
270        if width_fraction == 0:
271            return expr
272        return expr + LayoutExpr.width(node_id, width_fraction)
273
274    def y(self, height_fraction: IntOrFloat = 0) -> LayoutExpr:
275        """
276        Get an expression with Y coordinate relative to the box.
277        """
278        check_is_int_or_float(height_fraction)
279        node_id = id(self)
280        expr = LayoutExpr.y(node_id)
281        if height_fraction == 0:
282            return expr
283        return expr + LayoutExpr.height(node_id, height_fraction)
284
285    def p(self, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
286        """
287        Get an expression with X and Y padding relative to the box.
288        """
289        return Point(self.x(x), self.y(y))
290
291    def width(self, fraction: IntOrFloat = 1.0) -> LayoutExpr:
292        """
293        Get an expression with width of the parent box.
294        """
295        check_is_int_or_float(fraction)
296        node_id = id(self)
297        return LayoutExpr.width(node_id, fraction)
298
299    def height(self, fraction: IntOrFloat = 1.0) -> LayoutExpr:
300        """
301        Get an expression with height of the parent box.
302        """
303        check_is_int_or_float(fraction)
304        node_id = id(self)
305        return LayoutExpr.height(node_id, fraction)
306
307    def line_x(self, line_idx: int, width_fraction: IntOrFloat = 0) -> LayoutExpr:
308        """
309        Get an expression with X coordinate of a given line of text in the box.
310        """
311        check_is_int_or_float(width_fraction)
312        check_is_int(line_idx)
313        node_id = id(self)
314        expr = LayoutExpr.line_x(node_id, line_idx)
315        if width_fraction == 0:
316            return expr
317        return expr + LayoutExpr.line_width(node_id, line_idx, width_fraction)
318
319    def line_y(self, line_idx: int, height_fraction: IntOrFloat = 0) -> LayoutExpr:
320        """
321        Get an expression with Y coordinate of a given line of text in the box.
322        """
323        check_is_int_or_float(height_fraction)
324        check_is_int(line_idx)
325        node_id = id(self)
326        expr = LayoutExpr.line_y(node_id, line_idx)
327        if height_fraction == 0:
328            return expr
329        return expr + LayoutExpr.line_height(node_id, line_idx, height_fraction)
330
331    def line_p(self, line_idx: int, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
332        return Point(self.line_x(line_idx, x), self.line_y(line_idx, y))
333
334    def line_width(self, line_idx: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
335        """
336        Get an expression with a width of a given line of text in the box.
337        """
338        check_is_int_or_float(fraction)
339        check_is_int(line_idx)
340        node_id = id(self)
341        return LayoutExpr.line_width(node_id, line_idx, fraction)
342
343    def line_height(self, line_idx: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
344        """
345        Get an expression with a height of a given line of text in the box.
346        """
347        check_is_int_or_float(fraction)
348        check_is_int(line_idx)
349        node_id = id(self)
350        return LayoutExpr.line_height(node_id, line_idx, fraction)
351
352    def inline_x(self, anchor_id: int, width_fraction: IntOrFloat = 0) -> LayoutExpr:
353        """
354        Get an expression with X coordinate of a given text anchor in the box.
355        """
356        check_is_int_or_float(width_fraction)
357        check_is_int(anchor_id)
358        node_id = id(self)
359        expr = LayoutExpr.inline_x(node_id, anchor_id)
360        if width_fraction == 0:
361            return expr
362        return expr + LayoutExpr.inline_width(node_id, anchor_id, width_fraction)
363
364    def inline_y(self, anchor_id: int, height_fraction: IntOrFloat = 0) -> LayoutExpr:
365        """
366        Get an expression with Y coordinate of a given text anchor in the box.
367        """
368        check_is_int_or_float(height_fraction)
369        check_is_int(anchor_id)
370        node_id = id(self)
371        expr = LayoutExpr.inline_y(node_id, anchor_id)
372        if height_fraction == 0:
373            return expr
374        return expr + LayoutExpr.inline_height(node_id, anchor_id, height_fraction)
375
376    def inline_p(self, anchor_id: int, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
377        return Point(self.inline_x(anchor_id, x), self.inline_y(anchor_id, y))
378
379    def inline_width(self, anchor_id: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
380        """
381        Get an expression with a height of a given text anchor in the box.
382        """
383        check_is_int_or_float(fraction)
384        check_is_int(anchor_id)
385        node_id = id(self)
386        return LayoutExpr.inline_width(node_id, anchor_id, fraction)
387
388    def inline_height(self, anchor_id: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
389        """
390        Get an expression with a height of a given text anchor in the box.
391        """
392        check_is_int_or_float(fraction)
393        check_is_int(anchor_id)
394        node_id = id(self)
395        return LayoutExpr.inline_height(node_id, anchor_id, fraction)
396
397    def line_box(self, line_idx: int, n_lines: int = 1, **box_args):
398        """
399        Creates a new box over a text line in the box
400        """
401        height = self.line_height(line_idx)
402        if n_lines != 1:
403            check_is_int(line_idx)
404            height = height * n_lines
405            width = LayoutExpr.max(
406                [self.line_width(line_idx + i) for i in range(n_lines)]
407            )
408        else:
409            width = self.line_width(line_idx)
410        return self.box(
411            x=self.line_x(line_idx),
412            y=self.line_y(line_idx),
413            width=width,
414            height=height,
415            **box_args,
416        )
417
418    def inline_box(self, anchor_id: int, **box_args):
419        """
420        Creates a new box over a inline text anchor in the box
421        """
422
423        return self.box(
424            x=self.inline_x(anchor_id),
425            y=self.inline_y(anchor_id),
426            width=self.inline_width(anchor_id),
427            height=self.inline_height(anchor_id),
428            **box_args,
429        )
def add( self, box: Union[Path, Rect, Oval, Box]):
60    def add(self, box: Union[Path, Rect, Oval, "Box"]):
61        """
62        Adds Box or a geometry item into the box
63        """
64        raise NotImplementedError

Adds Box or a geometry item into the box

def box( self, *, x: Sn[Position] = None, y: Sn[Position] = None, z_level: Sn[int] = None, show: BoolStepDef = True, active: BoolStepDef = True, width: Sn[Size] = None, height: Sn[Size] = None, bg_color: Sn[str] = None, row: Sv[bool] = False, reverse: Sv[bool] = False, p_left: Sv[Length] = 0, p_right: Sv[Length] = 0, p_top: Sv[Length] = 0, p_bottom: Sv[Length] = 0, m_left: Sv[LengthAuto] = 0, m_right: Sv[LengthAuto] = 0, m_top: Sv[LengthAuto] = 0, m_bottom: Sv[LengthAuto] = 0, flex_grow: Sv[float] = 0.0, flex_shrink: Sv[float] = 1.0, align_items: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline']] = None, align_self: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline']] = None, justify_self: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline']] = None, align_content: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-evenly', 'space-around']] = None, justify_content: Sn[typing.Literal['start', 'end', 'flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-evenly', 'space-around']] = None, gap_x: Sv[Length] = 0, gap_y: Sv[Length] = 0, grid: Sn[GridOptions] = None, border_radius: Sv[IntOrFloat] = 0, url: Sn[str] = None, name: str = '', debug_layout: bool | str | None = None):
 72    def box(
 73        self,
 74        *,
 75        x: Sn[Position] = None,
 76        y: Sn[Position] = None,
 77        z_level: Sn[int] = None,
 78        show: BoolStepDef = True,
 79        active: BoolStepDef = True,
 80        width: Sn[Size] = None,
 81        height: Sn[Size] = None,
 82        bg_color: Sn[str] = None,
 83        row: Sv[bool] = False,
 84        reverse: Sv[bool] = False,
 85        p_left: Sv[Length] = 0,
 86        p_right: Sv[Length] = 0,
 87        p_top: Sv[Length] = 0,
 88        p_bottom: Sv[Length] = 0,
 89        m_left: Sv[LengthAuto] = 0,
 90        m_right: Sv[LengthAuto] = 0,
 91        m_top: Sv[LengthAuto] = 0,
 92        m_bottom: Sv[LengthAuto] = 0,
 93        flex_grow: Sv[float] = 0.0,
 94        flex_shrink: Sv[float] = 1.0,
 95        align_items: Sn[AlignItems] = None,
 96        align_self: Sn[AlignItems] = None,
 97        justify_self: Sn[AlignItems] = None,
 98        align_content: Sn[AlignContent] = None,
 99        justify_content: Sn[AlignContent] = None,
100        gap_x: Sv[Length] = 0,
101        gap_y: Sv[Length] = 0,
102        grid: Sn[GridOptions] = None,
103        border_radius: Sv[IntOrFloat] = 0,
104        url: Sn[str] = None,
105        name: str = "",
106        debug_layout: bool | str | None = None,
107    ):
108        """
109        Create a new child box. See [Box reference](https://spirali.github.io/nelsie/guide/box/) for documentation
110        """
111        box = Box(
112            x=x,
113            y=y,
114            z_level=z_level,
115            show=show,
116            active=active,
117            width=width,
118            height=height,
119            bg_color=bg_color,
120            row=row,
121            reverse=reverse,
122            p_left=p_left,
123            p_right=p_right,
124            p_top=p_top,
125            p_bottom=p_bottom,
126            m_left=m_left,
127            m_right=m_right,
128            m_top=m_top,
129            m_bottom=m_bottom,
130            flex_grow=flex_grow,
131            flex_shrink=flex_shrink,
132            align_items=align_items,
133            align_self=align_self,
134            justify_self=justify_self,
135            align_content=align_content,
136            justify_content=justify_content,
137            gap_x=gap_x,
138            gap_y=gap_y,
139            grid=grid,
140            name=name,
141            border_radius=border_radius,
142            debug_layout=debug_layout,
143            url=url,
144        )
145        self.add(box)
146        return box

Create a new child box. See Box reference for documentation

def overlay(self, **box_args):
148    def overlay(self, **box_args):
149        """
150        Create a new box that spans over the box
151        """
152        box_args.setdefault("x", 0)
153        box_args.setdefault("y", 0)
154        box_args.setdefault("width", "100%")
155        box_args.setdefault("height", "100%")
156        return self.box(**box_args)

Create a new box that spans over the box

def text( self, text: Sv[str], style: Sn[TextStyle] = None, *, align: Sv[typing.Literal['start', 'center', 'end']] = 'start', strip: bool = True, parse_styles: bool = True, style_delimiters: str = '~{}', parse_steps: bool | str = False, **box_args):
158    def text(
159        self,
160        text: Sv[str],
161        style: Sn[TextStyle] = None,
162        *,
163        align: Sv[TextAlign] = "start",
164        strip: bool = True,
165        parse_styles: bool = True,
166        style_delimiters: str = "~{}",
167        parse_steps: bool | str = False,
168        **box_args,
169    ):
170        if strip and isinstance(text, str):
171            text = text.strip()
172        if parse_steps:
173            text = parse_steps_helper(text, parse_steps, strip)
174        sv_check(text, check_is_str)
175        sv_check(align, check_text_align)
176        sn_check(style, check_is_str_or_text_style)
177        box = self.box(**box_args)
178        box._content = TextContent(
179            text=text,
180            style=style,
181            align=align,
182            is_code=False,
183            parse_styles=parse_styles,
184            style_delimiters=style_delimiters,
185            syntax_language=None,
186            syntax_theme=None,
187        )
188        return box
def code( self, text: Sv[str], language: Sn[str] = None, style: Sn[TextStyle] = None, *, align: Sv[typing.Literal['start', 'center', 'end']] = 'start', strip: bool = True, theme: Sn[str] = None, parse_styles: bool = False, style_delimiters: str = '~{}', parse_steps: bool | str = False, **box_args):
190    def code(
191        self,
192        text: Sv[str],
193        language: Sn[str] = None,
194        style: Sn[TextStyle] = None,
195        *,
196        align: Sv[TextAlign] = "start",
197        strip: bool = True,
198        theme: Sn[str] = None,
199        parse_styles: bool = False,
200        style_delimiters: str = "~{}",
201        parse_steps: bool | str = False,
202        **box_args,
203    ):
204        if strip and isinstance(text, str):
205            text = text.strip()
206        if parse_steps:
207            text = parse_steps_helper(text, parse_steps, strip)
208        sv_check(text, check_is_str)
209        sv_check(align, check_text_align)
210        sn_check(style, check_is_str_or_text_style)
211        sn_check(language, check_is_str)
212        sn_check(theme, check_is_str)
213        box = self.box(**box_args)
214        box._content = TextContent(
215            text=text,
216            style=style,
217            align=align,
218            is_code=True,
219            syntax_language=language,
220            syntax_theme=theme,
221            parse_styles=parse_styles,
222            style_delimiters=style_delimiters,
223        )
224        return box
def image( self, path_or_data: Sn[str | tuple[bytes | str, typing.Literal['png', 'svg', 'jpeg', 'ora']]], *, enable_steps: Sv[bool] = True, shift_steps: int = 0, **box_args):
226    def image(
227        self,
228        path_or_data: Sn[PathOrImageData],
229        *,
230        enable_steps: Sv[bool] = True,
231        shift_steps: int = 0,
232        **box_args,
233    ):
234        sn_check(path_or_data, check_image_path_or_data)
235        sv_check(enable_steps, check_is_bool)
236        sv_check(shift_steps, check_is_int)
237        path_or_data = sn_map(path_or_data, normalize_and_watch_image_path)
238        box = self.box(**box_args)
239        box._content = ImageContent(path_or_data, enable_steps, shift_steps)
240        return box
def set_style(self, name: str, style: Sn[TextStyle]):
242    def set_style(self, name: str, style: Sn[TextStyle]):
243        if name == "default":
244            self.update_style(name, style)
245            return
246        check_is_str(name)
247        sn_check(style, check_is_text_style)
248        self._set_style(name, style)
def update_style(self, name: str, style: TextStyle):
250    def update_style(self, name: str, style: TextStyle):
251        check_is_str(name)
252        check_is_text_style(style)
253        old_style = self._get_style(name)
254        if old_style is None:
255            self._set_style(name, style)
256        elif isinstance(old_style, TextStyle):
257            self._set_style(name, old_style.merge(style))
258        else:
259            raise Exception(
260                "Non-primitive style cannot be updated; use set_style instead"
261            )
def x(self, width_fraction: IntOrFloat = 0) -> nelsie.layoutexpr.LayoutExpr:
263    def x(self, width_fraction: IntOrFloat = 0) -> LayoutExpr:
264        """
265        Get an expression with X coordinate relative to the box.
266        """
267        check_is_int_or_float(width_fraction)
268        node_id = id(self)
269        expr = LayoutExpr.x(node_id)
270        if width_fraction == 0:
271            return expr
272        return expr + LayoutExpr.width(node_id, width_fraction)

Get an expression with X coordinate relative to the box.

def y(self, height_fraction: IntOrFloat = 0) -> nelsie.layoutexpr.LayoutExpr:
274    def y(self, height_fraction: IntOrFloat = 0) -> LayoutExpr:
275        """
276        Get an expression with Y coordinate relative to the box.
277        """
278        check_is_int_or_float(height_fraction)
279        node_id = id(self)
280        expr = LayoutExpr.y(node_id)
281        if height_fraction == 0:
282            return expr
283        return expr + LayoutExpr.height(node_id, height_fraction)

Get an expression with Y coordinate relative to the box.

def p(self, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
285    def p(self, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
286        """
287        Get an expression with X and Y padding relative to the box.
288        """
289        return Point(self.x(x), self.y(y))

Get an expression with X and Y padding relative to the box.

def width(self, fraction: IntOrFloat = 1.0) -> nelsie.layoutexpr.LayoutExpr:
291    def width(self, fraction: IntOrFloat = 1.0) -> LayoutExpr:
292        """
293        Get an expression with width of the parent box.
294        """
295        check_is_int_or_float(fraction)
296        node_id = id(self)
297        return LayoutExpr.width(node_id, fraction)

Get an expression with width of the parent box.

def height(self, fraction: IntOrFloat = 1.0) -> nelsie.layoutexpr.LayoutExpr:
299    def height(self, fraction: IntOrFloat = 1.0) -> LayoutExpr:
300        """
301        Get an expression with height of the parent box.
302        """
303        check_is_int_or_float(fraction)
304        node_id = id(self)
305        return LayoutExpr.height(node_id, fraction)

Get an expression with height of the parent box.

def line_x( self, line_idx: int, width_fraction: IntOrFloat = 0) -> nelsie.layoutexpr.LayoutExpr:
307    def line_x(self, line_idx: int, width_fraction: IntOrFloat = 0) -> LayoutExpr:
308        """
309        Get an expression with X coordinate of a given line of text in the box.
310        """
311        check_is_int_or_float(width_fraction)
312        check_is_int(line_idx)
313        node_id = id(self)
314        expr = LayoutExpr.line_x(node_id, line_idx)
315        if width_fraction == 0:
316            return expr
317        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.

def line_y( self, line_idx: int, height_fraction: IntOrFloat = 0) -> nelsie.layoutexpr.LayoutExpr:
319    def line_y(self, line_idx: int, height_fraction: IntOrFloat = 0) -> LayoutExpr:
320        """
321        Get an expression with Y coordinate of a given line of text in the box.
322        """
323        check_is_int_or_float(height_fraction)
324        check_is_int(line_idx)
325        node_id = id(self)
326        expr = LayoutExpr.line_y(node_id, line_idx)
327        if height_fraction == 0:
328            return expr
329        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.

def line_p( self, line_idx: int, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
331    def line_p(self, line_idx: int, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
332        return Point(self.line_x(line_idx, x), self.line_y(line_idx, y))
def line_width( self, line_idx: int, fraction: IntOrFloat = 1.0) -> nelsie.layoutexpr.LayoutExpr:
334    def line_width(self, line_idx: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
335        """
336        Get an expression with a width of a given line of text in the box.
337        """
338        check_is_int_or_float(fraction)
339        check_is_int(line_idx)
340        node_id = id(self)
341        return LayoutExpr.line_width(node_id, line_idx, fraction)

Get an expression with a width of a given line of text in the box.

def line_height( self, line_idx: int, fraction: IntOrFloat = 1.0) -> nelsie.layoutexpr.LayoutExpr:
343    def line_height(self, line_idx: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
344        """
345        Get an expression with a height of a given line of text in the box.
346        """
347        check_is_int_or_float(fraction)
348        check_is_int(line_idx)
349        node_id = id(self)
350        return LayoutExpr.line_height(node_id, line_idx, fraction)

Get an expression with a height of a given line of text in the box.

def inline_x( self, anchor_id: int, width_fraction: IntOrFloat = 0) -> nelsie.layoutexpr.LayoutExpr:
352    def inline_x(self, anchor_id: int, width_fraction: IntOrFloat = 0) -> LayoutExpr:
353        """
354        Get an expression with X coordinate of a given text anchor in the box.
355        """
356        check_is_int_or_float(width_fraction)
357        check_is_int(anchor_id)
358        node_id = id(self)
359        expr = LayoutExpr.inline_x(node_id, anchor_id)
360        if width_fraction == 0:
361            return expr
362        return expr + LayoutExpr.inline_width(node_id, anchor_id, width_fraction)

Get an expression with X coordinate of a given text anchor in the box.

def inline_y( self, anchor_id: int, height_fraction: IntOrFloat = 0) -> nelsie.layoutexpr.LayoutExpr:
364    def inline_y(self, anchor_id: int, height_fraction: IntOrFloat = 0) -> LayoutExpr:
365        """
366        Get an expression with Y coordinate of a given text anchor in the box.
367        """
368        check_is_int_or_float(height_fraction)
369        check_is_int(anchor_id)
370        node_id = id(self)
371        expr = LayoutExpr.inline_y(node_id, anchor_id)
372        if height_fraction == 0:
373            return expr
374        return expr + LayoutExpr.inline_height(node_id, anchor_id, height_fraction)

Get an expression with Y coordinate of a given text anchor in the box.

def inline_p( self, anchor_id: int, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
376    def inline_p(self, anchor_id: int, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
377        return Point(self.inline_x(anchor_id, x), self.inline_y(anchor_id, y))
def inline_width( self, anchor_id: int, fraction: IntOrFloat = 1.0) -> nelsie.layoutexpr.LayoutExpr:
379    def inline_width(self, anchor_id: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
380        """
381        Get an expression with a height of a given text anchor in the box.
382        """
383        check_is_int_or_float(fraction)
384        check_is_int(anchor_id)
385        node_id = id(self)
386        return LayoutExpr.inline_width(node_id, anchor_id, fraction)

Get an expression with a height of a given text anchor in the box.

def inline_height( self, anchor_id: int, fraction: IntOrFloat = 1.0) -> nelsie.layoutexpr.LayoutExpr:
388    def inline_height(self, anchor_id: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
389        """
390        Get an expression with a height of a given text anchor in the box.
391        """
392        check_is_int_or_float(fraction)
393        check_is_int(anchor_id)
394        node_id = id(self)
395        return LayoutExpr.inline_height(node_id, anchor_id, fraction)

Get an expression with a height of a given text anchor in the box.

def line_box(self, line_idx: int, n_lines: int = 1, **box_args):
397    def line_box(self, line_idx: int, n_lines: int = 1, **box_args):
398        """
399        Creates a new box over a text line in the box
400        """
401        height = self.line_height(line_idx)
402        if n_lines != 1:
403            check_is_int(line_idx)
404            height = height * n_lines
405            width = LayoutExpr.max(
406                [self.line_width(line_idx + i) for i in range(n_lines)]
407            )
408        else:
409            width = self.line_width(line_idx)
410        return self.box(
411            x=self.line_x(line_idx),
412            y=self.line_y(line_idx),
413            width=width,
414            height=height,
415            **box_args,
416        )

Creates a new box over a text line in the box

def inline_box(self, anchor_id: int, **box_args):
418    def inline_box(self, anchor_id: int, **box_args):
419        """
420        Creates a new box over a inline text anchor in the box
421        """
422
423        return self.box(
424            x=self.inline_x(anchor_id),
425            y=self.inline_y(anchor_id),
426            width=self.inline_width(anchor_id),
427            height=self.inline_height(anchor_id),
428            **box_args,
429        )

Creates a new box over a inline text anchor in the box

class FontStretch(enum.IntEnum):
10class FontStretch(IntEnum):
11    UltraCondensed = 1
12    ExtraCondensed = 2
13    Condensed = 3
14    SemiCondensed = 4
15    Normal = 5
16    SemiExpanded = 6
17    Expanded = 7
18    ExtraExpanded = 8
19    UltraExpanded = 9
UltraCondensed = <FontStretch.UltraCondensed: 1>
ExtraCondensed = <FontStretch.ExtraCondensed: 2>
Condensed = <FontStretch.Condensed: 3>
SemiCondensed = <FontStretch.SemiCondensed: 4>
Normal = <FontStretch.Normal: 5>
SemiExpanded = <FontStretch.SemiExpanded: 6>
Expanded = <FontStretch.Expanded: 7>
ExtraExpanded = <FontStretch.ExtraExpanded: 8>
UltraExpanded = <FontStretch.UltraExpanded: 9>
@dataclass(frozen=True)
class TextStyle:
34@dataclass(frozen=True)
35class TextStyle:
36    font: Sn[str] = None
37    color: Sn[str] = None
38    size: Sn[float] = None
39    line_spacing: Sn[float] = None
40    italic: Sn[bool] = None
41    stretch: Sn[FontStretch] = None
42    underline: Sn[bool] = None
43    line_through: Sn[bool] = None
44
45    # 1-1000; 400 = Normal, 700 = Bold
46    weight: Sn[int] = None
47
48    # If True, ignores weight value and forces weight 700
49    bold: Sn[bool] = None
50
51    def __post_init__(self):
52        sn_check(self.color, check_color)
53        sn_check(self.size, check_is_non_negative_int_or_float)
54        sn_check(self.line_spacing, check_is_non_negative_int_or_float)
55        sn_check(self.weight, check_is_weight)
56
57    def merge(self, other: "TextStyle") -> "TextStyle":
58        check_is_text_style(other)
59        return TextStyle(
60            *[
61                b if b is not None else a
62                for (a, b) in zip(unpack_dataclass(self), unpack_dataclass(other))
63            ]
64        )
65
66    def get_step(self, step: Step) -> "TextStyle":
67        return TextStyle(
68            font=get_step(self.font, step),
69            color=get_step(self.color, step),
70            size=get_step(self.size, step),
71            line_spacing=get_step(self.line_spacing, step),
72            italic=get_step(self.italic, step),
73            stretch=get_step(self.stretch, step),
74            underline=get_step(self.underline, step),
75            line_through=get_step(self.line_through, step),
76            weight=get_step(self.weight, step),
77            bold=get_step(self.bold, step),
78        )
TextStyle( font: Sn[str] = None, color: Sn[str] = None, size: Sn[float] = None, line_spacing: Sn[float] = None, italic: Sn[bool] = None, stretch: Sn[FontStretch] = None, underline: Sn[bool] = None, line_through: Sn[bool] = None, weight: Sn[int] = None, bold: Sn[bool] = None)
font: Sn[str] = None
color: Sn[str] = None
size: Sn[float] = None
line_spacing: Sn[float] = None
italic: Sn[bool] = None
stretch: Sn[FontStretch] = None
underline: Sn[bool] = None
line_through: Sn[bool] = None
weight: Sn[int] = None
bold: Sn[bool] = None
def merge(self, other: TextStyle) -> TextStyle:
57    def merge(self, other: "TextStyle") -> "TextStyle":
58        check_is_text_style(other)
59        return TextStyle(
60            *[
61                b if b is not None else a
62                for (a, b) in zip(unpack_dataclass(self), unpack_dataclass(other))
63            ]
64        )
def get_step(self, step: int | tuple[int]) -> TextStyle:
66    def get_step(self, step: Step) -> "TextStyle":
67        return TextStyle(
68            font=get_step(self.font, step),
69            color=get_step(self.color, step),
70            size=get_step(self.size, step),
71            line_spacing=get_step(self.line_spacing, step),
72            italic=get_step(self.italic, step),
73            stretch=get_step(self.stretch, step),
74            underline=get_step(self.underline, step),
75            line_through=get_step(self.line_through, step),
76            weight=get_step(self.weight, step),
77            bold=get_step(self.bold, step),
78        )
@dataclass
class Arrow:
126@dataclass
127class Arrow:
128    """
129    Represents an SVG arrow head. Can be attached to the start or end points of lines.
130    """
131
132    size: Sv[float] = 10
133    """Size of the arrow head in pixels."""
134
135    angle: Sv[float] = 40
136    """Angle of the arrow head."""
137
138    color: Sn[str] = None
139    """Color of arrow, if None color is taken from path"""
140
141    stroke_width: Sn[float] = None
142    """If None then a filled arrow is drawn, if float then stroked arrow is drawn with the given stroke width"""
143
144    inner_point: Sn[float] = None
145    """ Shape of the arrow head.
146
147        * < 1.0 -> Sharper arrow.
148        * = 1.0 -> Normal arrow.
149        * > 1.0 -> Diamond shape arrow.
150    """
151
152    def __post_init__(self):
153        sv_check(self.size, check_is_int_or_float)
154        sv_check(self.angle, check_is_int_or_float)
155        sn_check(self.color, check_color)
156        sn_check(self.stroke_width, check_is_int_or_float)
157        sn_check(self.inner_point, check_is_int_or_float)
158
159    def at_step(self, step: Step):
160        return Arrow(
161            get_step(self.size, step),
162            get_step(self.angle, step),
163            get_step(self.color, step),
164            get_step(self.stroke_width, step),
165            get_step(self.inner_point, step),
166        )

Represents an SVG arrow head. Can be attached to the start or end points of lines.

Arrow( size: Sv[float] = 10, angle: Sv[float] = 40, color: Sn[str] = None, stroke_width: Sn[float] = None, inner_point: Sn[float] = None)
size: Sv[float] = 10

Size of the arrow head in pixels.

angle: Sv[float] = 40

Angle of the arrow head.

color: Sn[str] = None

Color of arrow, if None color is taken from path

stroke_width: Sn[float] = None

If None then a filled arrow is drawn, if float then stroked arrow is drawn with the given stroke width

inner_point: Sn[float] = None

Shape of the arrow head.

  • < 1.0 -> Sharper arrow.
  • = 1.0 -> Normal arrow.
  • > 1.0 -> Diamond shape arrow.
def at_step(self, step: int | tuple[int]):
159    def at_step(self, step: Step):
160        return Arrow(
161            get_step(self.size, step),
162            get_step(self.angle, step),
163            get_step(self.color, step),
164            get_step(self.stroke_width, step),
165            get_step(self.inner_point, step),
166        )
class Path:
184class Path:
185    def __init__(
186        self,
187        *,
188        stroke: Sn[Stroke] = None,
189        fill_color: Sn[str] = None,
190        arrow_start: Sn[Arrow] = None,
191        arrow_end: Sn[Arrow] = None,
192        z_level: Sn[int] = None,
193        show: BoolStepDef = True,
194    ):
195        sn_check(stroke, check_is_stroke)
196        sn_check(fill_color, check_color)
197        sn_check(arrow_start, check_is_arrow)
198        sn_check(arrow_end, check_is_arrow)
199        sn_check(z_level, check_is_int)
200        self.show = parse_bool_steps(show)
201        self.stroke = stroke
202        self.fill_color = fill_color
203        self.commands = []
204        self.points = []
205        self.arrow_start = arrow_start
206        self.arrow_end = arrow_end
207        self.z_level = z_level
208
209    @property
210    def last_point(self):
211        """
212        Returns a last point in the path, if path is empty, returns 0,0
213        :return: Point
214        """
215        if len(self.points) < 1:
216            return Point(0, 0)
217        else:
218            return self.points[-1]
219
220    def close(self):
221        self.commands.append("close")
222        return self
223
224    def move_to(self, point: Point):
225        check_is_point(point)
226        self.commands.append("move")
227        self.points.append(point)
228        return self
229
230    def move_by(self, x: PathValue, y: PathValue) -> "Path":
231        """
232        Perform a move relative to the last point of the path.
233        If path is empty, it starts from 0,0
234        """
235        point = self.last_point
236
237        return self.move_to(Point(point.x + x, point.y + y))
238
239    def line_to(self, point: Point):
240        check_is_point(point)
241        self.commands.append("line")
242        self.points.append(point)
243        return self
244
245    def line_by(self, x: PathValue, y: PathValue):
246        """
247        Draw a line relative to the last point of the path.
248        If path is empty, it starts from 0,0
249        """
250        point = self.last_point
251        return self.line_to(Point(point.x + x, point.y + y))
252
253    def quad_to(self, point1: Point, point: Point):
254        check_is_point(point1)
255        check_is_point(point)
256        self.commands.append("quad")
257        self.points.append(point1)
258        self.points.append(point)
259        return self
260
261    def cubic_to(
262        self,
263        point1: Point,
264        point2: Point,
265        point: Point,
266    ):
267        check_is_point(point1)
268        check_is_point(point2)
269        check_is_point(point)
270
271        self.commands.append("cubic")
272        self.points.append(point1)
273        self.points.append(point2)
274        self.points.append(point)
275        return self
276
277    def to_raw(self, step, ctx):
278        if not get_step(self.show, step, False):
279            return None
280        stroke = get_step(self.stroke, step)
281        if stroke is not None:
282            stroke = stroke.to_raw(step)
283        fill_color = get_step(self.fill_color, step)
284
285        arrow_start = get_step(self.arrow_start, step)
286        if arrow_start is not None:
287            arrow_start = arrow_start.at_step(step)
288
289        arrow_end = get_step(self.arrow_end, step)
290        if arrow_end is not None:
291            arrow_end = arrow_end.at_step(step)
292
293        points = []
294        for p in self.points:
295            points.append(get_step(p.x, step))
296            points.append(get_step(p.y, step))
297
298        return RawPath(
299            stroke=stroke,
300            fill_color=fill_color,
301            arrow_start=arrow_start,
302            arrow_end=arrow_end,
303            commands=self.commands,
304            points=points,
305            z_level=get_step(self.z_level, step, ctx.z_level),
306        )
Path( *, stroke: Sn[Stroke] = None, fill_color: Sn[str] = None, arrow_start: Sn[Arrow] = None, arrow_end: Sn[Arrow] = None, z_level: Sn[int] = None, show: BoolStepDef = True)
185    def __init__(
186        self,
187        *,
188        stroke: Sn[Stroke] = None,
189        fill_color: Sn[str] = None,
190        arrow_start: Sn[Arrow] = None,
191        arrow_end: Sn[Arrow] = None,
192        z_level: Sn[int] = None,
193        show: BoolStepDef = True,
194    ):
195        sn_check(stroke, check_is_stroke)
196        sn_check(fill_color, check_color)
197        sn_check(arrow_start, check_is_arrow)
198        sn_check(arrow_end, check_is_arrow)
199        sn_check(z_level, check_is_int)
200        self.show = parse_bool_steps(show)
201        self.stroke = stroke
202        self.fill_color = fill_color
203        self.commands = []
204        self.points = []
205        self.arrow_start = arrow_start
206        self.arrow_end = arrow_end
207        self.z_level = z_level
show
stroke
fill_color
commands
points
arrow_start
arrow_end
z_level
last_point
209    @property
210    def last_point(self):
211        """
212        Returns a last point in the path, if path is empty, returns 0,0
213        :return: Point
214        """
215        if len(self.points) < 1:
216            return Point(0, 0)
217        else:
218            return self.points[-1]

Returns a last point in the path, if path is empty, returns 0,0

Returns

Point

def close(self):
220    def close(self):
221        self.commands.append("close")
222        return self
def move_to(self, point: Point):
224    def move_to(self, point: Point):
225        check_is_point(point)
226        self.commands.append("move")
227        self.points.append(point)
228        return self
def move_by( self, x: int | float | nelsie.layoutexpr.LayoutExpr, y: int | float | nelsie.layoutexpr.LayoutExpr) -> Path:
230    def move_by(self, x: PathValue, y: PathValue) -> "Path":
231        """
232        Perform a move relative to the last point of the path.
233        If path is empty, it starts from 0,0
234        """
235        point = self.last_point
236
237        return self.move_to(Point(point.x + x, point.y + y))

Perform a move relative to the last point of the path. If path is empty, it starts from 0,0

def line_to(self, point: Point):
239    def line_to(self, point: Point):
240        check_is_point(point)
241        self.commands.append("line")
242        self.points.append(point)
243        return self
def line_by( self, x: int | float | nelsie.layoutexpr.LayoutExpr, y: int | float | nelsie.layoutexpr.LayoutExpr):
245    def line_by(self, x: PathValue, y: PathValue):
246        """
247        Draw a line relative to the last point of the path.
248        If path is empty, it starts from 0,0
249        """
250        point = self.last_point
251        return self.line_to(Point(point.x + x, point.y + y))

Draw a line relative to the last point of the path. If path is empty, it starts from 0,0

def quad_to(self, point1: Point, point: Point):
253    def quad_to(self, point1: Point, point: Point):
254        check_is_point(point1)
255        check_is_point(point)
256        self.commands.append("quad")
257        self.points.append(point1)
258        self.points.append(point)
259        return self
def cubic_to( self, point1: Point, point2: Point, point: Point):
261    def cubic_to(
262        self,
263        point1: Point,
264        point2: Point,
265        point: Point,
266    ):
267        check_is_point(point1)
268        check_is_point(point2)
269        check_is_point(point)
270
271        self.commands.append("cubic")
272        self.points.append(point1)
273        self.points.append(point2)
274        self.points.append(point)
275        return self
def to_raw(self, step, ctx):
277    def to_raw(self, step, ctx):
278        if not get_step(self.show, step, False):
279            return None
280        stroke = get_step(self.stroke, step)
281        if stroke is not None:
282            stroke = stroke.to_raw(step)
283        fill_color = get_step(self.fill_color, step)
284
285        arrow_start = get_step(self.arrow_start, step)
286        if arrow_start is not None:
287            arrow_start = arrow_start.at_step(step)
288
289        arrow_end = get_step(self.arrow_end, step)
290        if arrow_end is not None:
291            arrow_end = arrow_end.at_step(step)
292
293        points = []
294        for p in self.points:
295            points.append(get_step(p.x, step))
296            points.append(get_step(p.y, step))
297
298        return RawPath(
299            stroke=stroke,
300            fill_color=fill_color,
301            arrow_start=arrow_start,
302            arrow_end=arrow_end,
303            commands=self.commands,
304            points=points,
305            z_level=get_step(self.z_level, step, ctx.z_level),
306        )
class Rect(nelsie.shapes.BaseRect):
118class Rect(BaseRect):
119    shape = 0
shape = 0
@dataclass(frozen=True)
class Point:
23@dataclass(frozen=True)
24class Point:
25    x: Sv[IntOrFloatOrLayoutExpr]
26    y: Sv[IntOrFloatOrLayoutExpr]
27
28    def __post_init__(self):
29        sv_check(self.x, check_position)
30        sv_check(self.y, check_position)
31
32    def move_by(self, x: IntOrFloatOrLayoutExpr, y: IntOrFloatOrLayoutExpr) -> "Point":
33        return Point(self.x + x, self.y + y)
Point( x: Sv[typing.Union[int, float, nelsie.layoutexpr.LayoutExpr]], y: Sv[typing.Union[int, float, nelsie.layoutexpr.LayoutExpr]])
x: Sv[typing.Union[int, float, nelsie.layoutexpr.LayoutExpr]]
y: Sv[typing.Union[int, float, nelsie.layoutexpr.LayoutExpr]]
def move_by( self, x: Union[int, float, nelsie.layoutexpr.LayoutExpr], y: Union[int, float, nelsie.layoutexpr.LayoutExpr]) -> Point:
32    def move_by(self, x: IntOrFloatOrLayoutExpr, y: IntOrFloatOrLayoutExpr) -> "Point":
33        return Point(self.x + x, self.y + y)
class Oval(nelsie.shapes.BaseRect):
122class Oval(BaseRect):
123    shape = 1
shape = 1
@dataclass(frozen=True)
class Stroke:
40@dataclass(frozen=True)
41class Stroke:
42    color: Sv[str]
43    width: Sv[float] = 1.0
44    dash_array: Sn[list[float]] = None
45    dash_offset: Sv[float] = 0.0
46
47    def to_raw(self, step: Step):
48        return Stroke(
49            get_step(self.color, step),
50            get_step(self.width, step),
51            get_step(self.dash_array, step),
52            get_step(self.dash_offset, step),
53        )
54
55    def __post_init__(self):
56        sv_check(self.color, check_color)
57        sv_check(self.width, check_is_int_or_float)
58        sv_check(self.dash_offset, check_is_int_or_float)
Stroke( color: Sv[str], width: Sv[float] = 1.0, dash_array: Sn[list[float]] = None, dash_offset: Sv[float] = 0.0)
color: Sv[str]
width: Sv[float] = 1.0
dash_array: Sn[list[float]] = None
dash_offset: Sv[float] = 0.0
def to_raw(self, step: int | tuple[int]):
47    def to_raw(self, step: Step):
48        return Stroke(
49            get_step(self.color, step),
50            get_step(self.width, step),
51            get_step(self.dash_array, step),
52            get_step(self.dash_offset, step),
53        )
class StepVal(typing.Generic[~T]):
 50class StepVal(Generic[T]):
 51    def __init__(
 52        self,
 53        init_value: T | None = None,
 54        named_steps: list[Step] | None = None,
 55        init_values=None,
 56    ):
 57        if init_values is not None:
 58            self.values = init_values
 59        elif init_value is not None:
 60            self.values = {1: init_value}
 61        else:
 62            self.values = {}
 63        self.named_steps = named_steps
 64
 65    def at(self, step: Step, value: T) -> "StepVal[T]":
 66        check_step(step)
 67        self.values[step] = value
 68        return self
 69
 70    def call(self, fn: callable(T)):
 71        for value in self.values.values():
 72            fn(value)
 73
 74    def call_if_not_none(self, fn: callable(T)):
 75        for value in self.values.values():
 76            if value is not None:
 77                fn(value)
 78
 79    def map(self, fn: callable(T)) -> "StepVal[T]":
 80        values = {}
 81        for step, value in self.values.items():
 82            values[step] = fn(value)
 83        return StepVal(values, self.named_steps)
 84
 85    def copy(self):
 86        return StepVal(
 87            init_values=self.values.copy(), named_steps=copy(self.named_steps)
 88        )
 89
 90    def get_step(self, step: Step, default_value: T | None = None) -> T | None:
 91        if step in self.values:
 92            return self.values[step]
 93        result = default_value
 94        current_step = 0
 95        for i in self.values:
 96            if step_lte(i, step) and step_lte(current_step, i):
 97                result = self.values[i]
 98                current_step = i
 99        return result
100
101    def is_defined_for(self, step: Step) -> bool:
102        for i in self.values:
103            if step_lte(i, step):
104                return True
105        return False
106
107    def bool_inverse(self):
108        """Negate all values. Has meaningful output only when values are boolean."""
109        self.values[1] = self.values.get(1, False)
110        for step in self.values:
111            self.values[step] = not self.values[step]
112
113    def __repr__(self):
114        v = "<StepVal "
115        for step, value in sorted(self.values.items(), key=step_compare_key):
116            v += f"{step}={value!r}, "
117        v += f"ns={self.named_steps!r}>"
118        return v

Abstract base class for generic types.

On Python 3.12 and newer, generic classes implicitly inherit from Generic when they declare a parameter list after the class's name::

class Mapping[KT, VT]:
    def __getitem__(self, key: KT) -> VT:
        ...
    # Etc.

On older versions of Python, however, generic classes have to explicitly inherit from Generic.

After a class has been declared to be generic, it can then be used as follows::

def lookup_name[KT, VT](mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
    try:
        return mapping[key]
    except KeyError:
        return default
StepVal( init_value: Optional[~T] = None, named_steps: list[int | tuple[int]] | None = None, init_values=None)
51    def __init__(
52        self,
53        init_value: T | None = None,
54        named_steps: list[Step] | None = None,
55        init_values=None,
56    ):
57        if init_values is not None:
58            self.values = init_values
59        elif init_value is not None:
60            self.values = {1: init_value}
61        else:
62            self.values = {}
63        self.named_steps = named_steps
named_steps
def at(self, step: int | tuple[int], value: ~T) -> StepVal[~T]:
65    def at(self, step: Step, value: T) -> "StepVal[T]":
66        check_step(step)
67        self.values[step] = value
68        return self
def call(self, fn: False):
70    def call(self, fn: callable(T)):
71        for value in self.values.values():
72            fn(value)
def call_if_not_none(self, fn: False):
74    def call_if_not_none(self, fn: callable(T)):
75        for value in self.values.values():
76            if value is not None:
77                fn(value)
def map(self, fn: False) -> StepVal[~T]:
79    def map(self, fn: callable(T)) -> "StepVal[T]":
80        values = {}
81        for step, value in self.values.items():
82            values[step] = fn(value)
83        return StepVal(values, self.named_steps)
def copy(self):
85    def copy(self):
86        return StepVal(
87            init_values=self.values.copy(), named_steps=copy(self.named_steps)
88        )
def get_step( self, step: int | tuple[int], default_value: Optional[~T] = None) -> Optional[~T]:
90    def get_step(self, step: Step, default_value: T | None = None) -> T | None:
91        if step in self.values:
92            return self.values[step]
93        result = default_value
94        current_step = 0
95        for i in self.values:
96            if step_lte(i, step) and step_lte(current_step, i):
97                result = self.values[i]
98                current_step = i
99        return result
def is_defined_for(self, step: int | tuple[int]) -> bool:
101    def is_defined_for(self, step: Step) -> bool:
102        for i in self.values:
103            if step_lte(i, step):
104                return True
105        return False
def bool_inverse(self):
107    def bool_inverse(self):
108        """Negate all values. Has meaningful output only when values are boolean."""
109        self.values[1] = self.values.get(1, False)
110        for step in self.values:
111            self.values[step] = not self.values[step]

Negate all values. Has meaningful output only when values are boolean.

@dataclass
class GridOptions:
51@dataclass
52class GridOptions:
53    template_rows: Sv[GridTemplate] = ()
54    template_columns: Sv[GridTemplate] = ()
55    row: Sn[GridPosition] = None
56    column: Sn[GridPosition] = None
GridOptions( template_rows: Sv[typing.Sequence[IntOrFloat | str]] = (), template_columns: Sv[typing.Sequence[IntOrFloat | str]] = (), row: Sn[int | str | tuple[int | str]] = None, column: Sn[int | str | tuple[int | str]] = None)
template_rows: Sv[typing.Sequence[IntOrFloat | str]] = ()
template_columns: Sv[typing.Sequence[IntOrFloat | str]] = ()
row: Sn[int | str | tuple[int | str]] = None
column: Sn[int | str | tuple[int | str]] = None
class StepCounter:
 2class StepCounter:
 3    def __init__(self, init_value=1):
 4        self.step = init_value
 5
 6    def increment(self, count=1):
 7        self.step += count
 8
 9    def set(self, step):
10        self.step = step
11
12    def last(self):
13        return self.step
14
15    def last_p(self):
16        return f"{self.step}+"
17
18    def next(self):
19        self.increment()
20        return self.step
21
22    def next_p(self):
23        self.increment()
24        return self.last_p()
StepCounter(init_value=1)
3    def __init__(self, init_value=1):
4        self.step = init_value
step
def increment(self, count=1):
6    def increment(self, count=1):
7        self.step += count
def set(self, step):
 9    def set(self, step):
10        self.step = step
def last(self):
12    def last(self):
13        return self.step
def last_p(self):
15    def last_p(self):
16        return f"{self.step}+"
def next(self):
18    def next(self):
19        self.increment()
20        return self.step
def next_p(self):
22    def next_p(self):
23        self.increment()
24        return self.last_p()