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

Sets box's padding.

def draw_line(self, p1: Point, p2: Point, **path_args):
643    def draw_line(self, p1: Point, p2: Point, **path_args):
644        """
645        Shortcut for drawing a simple line
646        """
647        path = Path(**path_args)
648        path.move_to(p1)
649        path.line_to(p2)
650        return self.add(path)

Shortcut for drawing a simple line

def add(self, item):
652    def add(self, item):
653        """
654        Adds Box or a geometry item into the box
655        """
656        self._children.append(item)

Adds Box or a geometry item into the box

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

Create a new child box. See Box reference for documentation

def overlay(self, **box_args):
149    def overlay(self, **box_args):
150        """
151        Create a new box that spans over the box
152        """
153        box_args.setdefault("x", 0)
154        box_args.setdefault("y", 0)
155        box_args.setdefault("width", "100%")
156        box_args.setdefault("height", "100%")
157        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):
159    def text(
160        self,
161        text: Sv[str],
162        style: Sn[TextStyle] = None,
163        *,
164        align: Sv[TextAlign] = "start",
165        strip: bool = True,
166        parse_styles: bool = True,
167        style_delimiters: str = "~{}",
168        parse_steps: bool | str = False,
169        **box_args,
170    ):
171        if strip and isinstance(text, str):
172            text = text.strip()
173        if parse_steps:
174            text = parse_steps_helper(text, parse_steps, strip)
175        sv_check(text, check_is_str)
176        sv_check(align, check_text_align)
177        sn_check(style, check_is_str_or_text_style)
178        box = self.box(**box_args)
179        box._content = TextContent(
180            text=text,
181            style=style,
182            align=align,
183            is_code=False,
184            parse_styles=parse_styles,
185            style_delimiters=style_delimiters,
186            syntax_language=None,
187            syntax_theme=None,
188        )
189        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):
191    def code(
192        self,
193        text: Sv[str],
194        language: Sn[str] = None,
195        style: Sn[TextStyle] = None,
196        *,
197        align: Sv[TextAlign] = "start",
198        strip: bool = True,
199        theme: Sn[str] = None,
200        parse_styles: bool = False,
201        style_delimiters: str = "~{}",
202        parse_steps: bool | str = False,
203        **box_args,
204    ):
205        if strip and isinstance(text, str):
206            text = text.strip()
207        if parse_steps:
208            text = parse_steps_helper(text, parse_steps, strip)
209        sv_check(text, check_is_str)
210        sv_check(align, check_text_align)
211        sn_check(style, check_is_str_or_text_style)
212        sn_check(language, check_is_str)
213        sn_check(theme, check_is_str)
214        box = self.box(**box_args)
215        box._content = TextContent(
216            text=text,
217            style=style,
218            align=align,
219            is_code=True,
220            syntax_language=language,
221            syntax_theme=theme,
222            parse_styles=parse_styles,
223            style_delimiters=style_delimiters,
224        )
225        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, watch=True, **box_args):
227    def image(
228        self,
229        path_or_data: Sn[PathOrImageData],
230        *,
231        enable_steps: Sv[bool] = True,
232        shift_steps: int = 0,
233        watch=True,
234        **box_args,
235    ):
236        sn_check(path_or_data, check_image_path_or_data)
237        sv_check(enable_steps, check_is_bool)
238        sv_check(shift_steps, check_is_int)
239        if watch:
240            path_or_data = sn_map(path_or_data, normalize_and_watch_image_path)
241        else:
242            path_or_data = sn_map(path_or_data, normalize_image_path)
243        box = self.box(**box_args)
244        box._content = ImageContent(path_or_data, enable_steps, shift_steps)
245        return box
def set_style(self, name: str, style: Sn[TextStyle]):
247    def set_style(self, name: str, style: Sn[TextStyle]):
248        if name == "default":
249            self.update_style(name, style)
250            return
251        check_is_str(name)
252        sn_check(style, check_is_text_style)
253        self._set_style(name, style)
def update_style(self, name: str, style: TextStyle):
255    def update_style(self, name: str, style: TextStyle):
256        check_is_str(name)
257        check_is_text_style(style)
258        old_style = self._get_style(name)
259        if old_style is None:
260            self._set_style(name, style)
261        elif isinstance(old_style, TextStyle):
262            self._set_style(name, old_style.merge(style))
263        else:
264            raise Exception(
265                "Non-primitive style cannot be updated; use set_style instead"
266            )
def x(self, width_fraction: IntOrFloat = 0) -> nelsie.layoutexpr.LayoutExpr:
268    def x(self, width_fraction: IntOrFloat = 0) -> LayoutExpr:
269        """
270        Get an expression with X coordinate relative to the box.
271        """
272        check_is_int_or_float(width_fraction)
273        node_id = id(self)
274        expr = LayoutExpr.x(node_id)
275        if width_fraction == 0:
276            return expr
277        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:
279    def y(self, height_fraction: IntOrFloat = 0) -> LayoutExpr:
280        """
281        Get an expression with Y coordinate relative to the box.
282        """
283        check_is_int_or_float(height_fraction)
284        node_id = id(self)
285        expr = LayoutExpr.y(node_id)
286        if height_fraction == 0:
287            return expr
288        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:
290    def p(self, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
291        """
292        Get an expression with X and Y padding relative to the box.
293        """
294        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:
296    def width(self, fraction: IntOrFloat = 1.0) -> LayoutExpr:
297        """
298        Get an expression with width of the parent box.
299        """
300        check_is_int_or_float(fraction)
301        node_id = id(self)
302        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:
304    def height(self, fraction: IntOrFloat = 1.0) -> LayoutExpr:
305        """
306        Get an expression with height of the parent box.
307        """
308        check_is_int_or_float(fraction)
309        node_id = id(self)
310        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:
312    def line_x(self, line_idx: int, width_fraction: IntOrFloat = 0) -> LayoutExpr:
313        """
314        Get an expression with X coordinate of a given line of text in the box.
315        """
316        check_is_int_or_float(width_fraction)
317        check_is_int(line_idx)
318        node_id = id(self)
319        expr = LayoutExpr.line_x(node_id, line_idx)
320        if width_fraction == 0:
321            return expr
322        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:
324    def line_y(self, line_idx: int, height_fraction: IntOrFloat = 0) -> LayoutExpr:
325        """
326        Get an expression with Y coordinate of a given line of text in the box.
327        """
328        check_is_int_or_float(height_fraction)
329        check_is_int(line_idx)
330        node_id = id(self)
331        expr = LayoutExpr.line_y(node_id, line_idx)
332        if height_fraction == 0:
333            return expr
334        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:
336    def line_p(self, line_idx: int, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
337        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:
339    def line_width(self, line_idx: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
340        """
341        Get an expression with a width of a given line of text in the box.
342        """
343        check_is_int_or_float(fraction)
344        check_is_int(line_idx)
345        node_id = id(self)
346        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:
348    def line_height(self, line_idx: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
349        """
350        Get an expression with a height of a given line of text in the box.
351        """
352        check_is_int_or_float(fraction)
353        check_is_int(line_idx)
354        node_id = id(self)
355        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:
357    def inline_x(self, anchor_id: int, width_fraction: IntOrFloat = 0) -> LayoutExpr:
358        """
359        Get an expression with X coordinate of a given text anchor in the box.
360        """
361        check_is_int_or_float(width_fraction)
362        check_is_int(anchor_id)
363        node_id = id(self)
364        expr = LayoutExpr.inline_x(node_id, anchor_id)
365        if width_fraction == 0:
366            return expr
367        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:
369    def inline_y(self, anchor_id: int, height_fraction: IntOrFloat = 0) -> LayoutExpr:
370        """
371        Get an expression with Y coordinate of a given text anchor in the box.
372        """
373        check_is_int_or_float(height_fraction)
374        check_is_int(anchor_id)
375        node_id = id(self)
376        expr = LayoutExpr.inline_y(node_id, anchor_id)
377        if height_fraction == 0:
378            return expr
379        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:
381    def inline_p(self, anchor_id: int, x: IntOrFloat = 0, y: IntOrFloat = 0) -> Point:
382        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:
384    def inline_width(self, anchor_id: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
385        """
386        Get an expression with a height of a given text anchor in the box.
387        """
388        check_is_int_or_float(fraction)
389        check_is_int(anchor_id)
390        node_id = id(self)
391        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:
393    def inline_height(self, anchor_id: int, fraction: IntOrFloat = 1.0) -> LayoutExpr:
394        """
395        Get an expression with a height of a given text anchor in the box.
396        """
397        check_is_int_or_float(fraction)
398        check_is_int(anchor_id)
399        node_id = id(self)
400        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):
402    def line_box(self, line_idx: int, n_lines: int = 1, **box_args):
403        """
404        Creates a new box over a text line in the box
405        """
406        height = self.line_height(line_idx)
407        if n_lines != 1:
408            check_is_int(line_idx)
409            height = height * n_lines
410            width = LayoutExpr.max(
411                [self.line_width(line_idx + i) for i in range(n_lines)]
412            )
413        else:
414            width = self.line_width(line_idx)
415        return self.box(
416            x=self.line_x(line_idx),
417            y=self.line_y(line_idx),
418            width=width,
419            height=height,
420            **box_args,
421        )

Creates a new box over a text line in the box

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

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:
52@dataclass
53class GridOptions:
54    template_rows: Sv[GridTemplate] = ()
55    template_columns: Sv[GridTemplate] = ()
56    row: Sn[GridPosition] = None
57    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()