Skip to content

Node Types API

This page provides detailed documentation for all node type constructors, properties, and methods.

Rect

An axis-aligned rectangle with top-left origin (y-axis points down).

from latticesvg import Rect

r = Rect(x=10, y=20, width=100, height=50)
print(r.right)   # 110.0
print(r.bottom)  # 70.0
r2 = r.copy()
Property Type Description
x float Top-left x coordinate (default 0.0)
y float Top-left y coordinate (default 0.0)
width float Width (default 0.0)
height float Height (default 0.0)
right float Read-only, equals x + width
bottom float Read-only, equals y + height
Method Returns Description
copy() Rect Returns a copy

LayoutConstraints

Constraints passed from parent to child during layout.

from latticesvg import LayoutConstraints

c = LayoutConstraints(available_width=800, available_height=600)
Property Type Default Description
available_width float \| None None Available width
available_height float \| None None Available height

Node (Base Class)

Abstract base for all layoutable elements. Subclasses must implement measure() and layout().

from latticesvg import Node

node = Node(style={"width": 100, "height": 50, "background": "#f0f0f0"})

Constructor Parameters

Parameter Type Default Description
style dict[str, Any] \| None None CSS style properties dict
parent Node \| None None Parent node (usually set automatically by add())

Properties

Property Type Description
style ComputedStyle Computed style object
parent Node \| None Parent node
children list[Node] Child node list
border_box Rect Border box rect after layout
padding_box Rect Padding box rect after layout
content_box Rect Content box rect after layout
placement PlacementHint Grid placement info

Methods

add(child, *, row=None, col=None, row_span=1, col_span=1, area=None)

Append a child node and optionally set grid placement.

Parameter Type Description
child Node Child node to add
row int \| None Row start position (1-based CSS Grid line number)
col int \| None Column start position (1-based CSS Grid line number)
row_span int Number of rows to span (default 1)
col_span int Number of columns to span (default 1)
area str \| None Named grid area

Returns: The added child node (enables chaining)

grid.add(child, row=1, col=2, row_span=1, col_span=2)
grid.add(child, area="header")

measure(constraints) → (min_w, max_w, intrinsic_h)

Returns the node's minimum content width, maximum content width, and intrinsic height. Called by the grid solver.

layout(constraints)

Computes border_box, padding_box, and content_box. Must be implemented by subclasses.


GridContainer

A CSS Grid layout container node that arranges children using grid layout.

from latticesvg import GridContainer

grid = GridContainer(style={
    "grid-template-columns": "200px 1fr",
    "grid-template-rows": "auto auto",
    "gap": 10,
    "width": 600,
    "padding": 20,
})

Constructor Parameters

Inherits from Node, accepts style and parent. display is automatically set to "grid".

Methods

layout(constraints=None, available_width=None, available_height=None)

Runs the full layout computation. As a root node, you can pass the available width directly:

grid.layout(available_width=800)

If available_width is not provided, the width style property is used, or defaults to 800.

measure(constraints) → (min_w, max_w, intrinsic_h)

Used for nested grid size calculations. Results are cached.


TextNode

Text node supporting automatic line wrapping, rich text markup, and vertical typesetting.

from latticesvg import TextNode

# Plain text
text = TextNode("Hello, World!", style={"font-size": 24, "color": "#333"})

# HTML rich text
rich = TextNode("<b>Bold</b> and <i>italic</i>", markup="html")

# Markdown rich text
md = TextNode("**Bold** and *italic*", markup="markdown")

Constructor Parameters

Parameter Type Default Description
text str required Text content
style dict \| None None Style properties
parent Node \| None None Parent node
markup str "none" Markup mode: "none", "html", "markdown"

Properties

Property Type Description
text str Raw text content
markup str Markup mode
lines list[Line] Lines after layout (plain text mode)

Key Style Properties

TextNode responds to these style properties (see CSS Properties Reference):

  • Font: font-family, font-size, font-weight, font-style
  • Typography: text-align, line-height, letter-spacing, word-spacing
  • Wrapping: white-space, overflow-wrap, hyphens, lang
  • Overflow: overflow, text-overflow
  • Vertical: writing-mode, text-orientation, text-combine-upright

ImageNode

Image node supporting multiple image sources and object-fit scaling.

from latticesvg import ImageNode

# File path
img = ImageNode("photo.png", style={"width": 200, "height": 150})

# URL
img = ImageNode("https://example.com/image.png")

# bytes
img = ImageNode(raw_bytes, object_fit="contain")

# PIL Image
from PIL import Image
img = ImageNode(Image.open("photo.png"), object_fit="cover")

Constructor Parameters

Parameter Type Default Description
src str \| bytes \| PIL.Image required Image source (path/URL/bytes/PIL)
style dict \| None None Style properties
parent Node \| None None Parent node
object_fit str \| None None Scaling mode

object-fit Modes

Value Description
"fill" Stretch to fill (default, may distort)
"contain" Scale proportionally, fully contained
"cover" Scale proportionally, fully covering
"none" Original size, no scaling

Properties

Property Type Description
intrinsic_width float Image intrinsic width (read-only, lazily loaded)
intrinsic_height float Image intrinsic height (read-only, lazily loaded)

SVGNode

A node for embedding external SVG content.

from latticesvg import SVGNode

# SVG string
svg = SVGNode('<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40"/></svg>')

# File
svg = SVGNode("icon.svg", is_file=True)

# URL
svg = SVGNode("https://example.com/icon.svg")

Constructor Parameters

Parameter Type Default Description
svg str required SVG content (string/path/URL)
style dict \| None None Style properties (keyword-only)
parent Node \| None None Parent node (keyword-only)
is_file bool False Whether to treat as file path (keyword-only)

Intrinsic size is parsed from the viewBox or width/height attributes.


MplNode

Matplotlib figure embedding node.

import matplotlib.pyplot as plt
from latticesvg import MplNode

fig, ax = plt.subplots(figsize=(4, 3))
ax.plot([1, 2, 3], [1, 4, 9])

node = MplNode(fig, style={"padding": 10})

Constructor Parameters

Parameter Type Default Description
figure matplotlib.figure.Figure required Matplotlib figure object
style dict \| None None Style properties
parent Node \| None None Parent node

Important Notes

  • All Matplotlib customization should be done before creating the node
  • The figure is automatically resized during layout to match allocated space
  • SVG coordinates use 72 DPI uniformly

MathNode

LaTeX formula rendering node based on QuickJax (MathJax v4).

from latticesvg import MathNode

# display mode (standalone formula)
formula = MathNode(r"E = mc^2", style={"font-size": 24})

# inline mode
inline = MathNode(r"\alpha + \beta", display=False)

Constructor Parameters

Parameter Type Default Description
latex str required LaTeX math expression
style dict \| None None Style properties (keyword-only)
backend str \| None None Backend name, None uses default (keyword-only)
display bool True Whether to use display mode (keyword-only)
parent Node \| None None Parent node (keyword-only)

Properties

Property Type Description
latex str LaTeX source
display bool display/inline mode
scale_x float Horizontal scale factor after layout
scale_y float Vertical scale factor after layout

Auto-generated API Docs

nodes

Node

Node(style: Optional[Dict[str, Any]] = None, parent: Optional['Node'] = None)

Abstract base for all layoutable elements.

Subclasses must implement :meth:measure and :meth:layout.

Source code in src/latticesvg/nodes/base.py
def __init__(self, style: Optional[Dict[str, Any]] = None, parent: Optional["Node"] = None) -> None:
    parent_computed = parent.style if parent else None
    self.style: ComputedStyle = ComputedStyle(style, parent_style=parent_computed)
    self.parent: Optional[Node] = parent
    self.children: List[Node] = []

    # Populated after layout
    self.border_box: Rect = Rect()
    self.padding_box: Rect = Rect()
    self.content_box: Rect = Rect()

    # Grid placement (set via ``add()``)
    self.placement: PlacementHint = PlacementHint()
add
add(child: 'Node', *, row: Optional[int] = None, col: Optional[int] = None, row_span: int = 1, col_span: int = 1, area: Optional[str] = None) -> 'Node'

Append child to this node and optionally set grid placement.

row and col use 1-based line numbers consistent with CSS Grid. area places the child in a named grid area defined by grid-template-areas on the container.

Source code in src/latticesvg/nodes/base.py
def add(self, child: "Node", *, row: Optional[int] = None, col: Optional[int] = None,
        row_span: int = 1, col_span: int = 1, area: Optional[str] = None) -> "Node":
    """Append *child* to this node and optionally set grid placement.

    ``row`` and ``col`` use 1-based line numbers consistent with CSS Grid.
    ``area`` places the child in a named grid area defined by
    ``grid-template-areas`` on the container.
    """
    child.parent = self
    # Re-compute style with parent inheritance
    if isinstance(child.style, ComputedStyle):
        child.style._rebind_parent(self.style)
    else:
        child.style = ComputedStyle(None, parent_style=self.style)

    child.placement = PlacementHint(
        row_start=row,
        row_span=row_span,
        col_start=col,
        col_span=col_span,
        area=area,
    )

    # Also honour grid-row / grid-column / grid-area from the child's own style
    gr = child.style.get("grid-row")
    gc = child.style.get("grid-column")
    ga = child.style.get("grid-area")
    if gr is not None and isinstance(gr, tuple):
        child.placement.row_start = gr[0]
        child.placement.row_span = gr[1]
    if gc is not None and isinstance(gc, tuple):
        child.placement.col_start = gc[0]
        child.placement.col_span = gc[1]
    if ga is not None and isinstance(ga, str) and ga != "auto":
        child.placement.area = ga

    self.children.append(child)
    return child
measure
measure(constraints: LayoutConstraints) -> Tuple[float, float, float]

Return (min_content_width, max_content_width, intrinsic_height).

Called by the grid solver to determine how much space this node needs. Default implementation returns zero sizes.

Source code in src/latticesvg/nodes/base.py
def measure(self, constraints: LayoutConstraints) -> Tuple[float, float, float]:
    """Return ``(min_content_width, max_content_width, intrinsic_height)``.

    Called by the grid solver to determine how much space this node needs.
    Default implementation returns zero sizes.
    """
    return (0.0, 0.0, 0.0)
layout
layout(constraints: LayoutConstraints) -> None

Compute border_box, padding_box, and content_box.

Must be implemented by subclasses.

Source code in src/latticesvg/nodes/base.py
def layout(self, constraints: LayoutConstraints) -> None:
    """Compute ``border_box``, ``padding_box``, and ``content_box``.

    Must be implemented by subclasses.
    """
    pass

Rect dataclass

Rect(x: float = 0.0, y: float = 0.0, width: float = 0.0, height: float = 0.0)

An axis-aligned rectangle (top-left origin, y-axis points down).

LayoutConstraints dataclass

LayoutConstraints(available_width: Optional[float] = None, available_height: Optional[float] = None)

Constraints passed from parent to child during layout.

GridContainer

GridContainer(style: Optional[Dict[str, Any]] = None, parent: Optional[Node] = None)

Bases: Node

A container node that arranges its children using CSS Grid layout.

Delegates the heavy-lifting (track sizing, placement, alignment) to :class:~latticesvg.layout.grid_solver.GridSolver.

Source code in src/latticesvg/nodes/grid.py
def __init__(
    self,
    style: Optional[Dict[str, Any]] = None,
    parent: Optional[Node] = None,
) -> None:
    if style is None:
        style = {}
    # Ensure display is grid
    style.setdefault("display", "grid")
    super().__init__(style=style, parent=parent)
layout
layout(constraints: Optional[LayoutConstraints] = None, available_width: Optional[float] = None, available_height: Optional[float] = None) -> None

Run the full layout pass for this container and all descendants.

Can be called directly as grid.layout(available_width=800).

Source code in src/latticesvg/nodes/grid.py
def layout(self, constraints: Optional[LayoutConstraints] = None,
           available_width: Optional[float] = None,
           available_height: Optional[float] = None) -> None:
    """Run the full layout pass for this container and all descendants.

    Can be called directly as ``grid.layout(available_width=800)``.
    """
    if constraints is None:
        # Derive from explicit width or arguments
        aw = available_width
        if aw is None:
            explicit_w = self._resolve_width(LayoutConstraints())
            aw = explicit_w if explicit_w is not None else 800.0
        constraints = LayoutConstraints(
            available_width=aw,
            available_height=available_height,
        )

    self._layout_grid(constraints)

TextNode

TextNode(text: str, style: Optional[Dict[str, Any]] = None, parent: Optional[Node] = None, markup: str = 'none')

Bases: Node

A node that displays text content.

The text is measured using FreeType (or Pillow fallback) and automatically wrapped according to the white-space property.

When markup is "html" or "markdown", inline tags/syntax are parsed to produce styled spans (bold, italic, colour, …).

Source code in src/latticesvg/nodes/text.py
def __init__(
    self,
    text: str,
    style: Optional[Dict[str, Any]] = None,
    parent: Optional[Node] = None,
    markup: str = "none",
) -> None:
    super().__init__(style=style, parent=parent)
    self.text: str = text
    self.markup: str = markup
    self.lines: List[Line] = []  # populated after layout (plain mode)

    # Rich-text fields (populated when markup != "none")
    self._spans: Optional[list] = None
    self._rich_lines: Optional[List[RichLine]] = None

    if markup in ("html", "markdown"):
        from ..markup.parser import parse_markup
        self._spans = parse_markup(text, markup)
measure
measure(constraints: LayoutConstraints) -> Tuple[float, float, float]

Return (min_content_width, max_content_width, intrinsic_height).

For vertical/sideways writing modes the axes are swapped internally so that the grid solver always receives values in the physical (horizontal/vertical) coordinate system.

The result is cached because it depends only on text content and style properties, not on constraints.

Source code in src/latticesvg/nodes/text.py
def measure(self, constraints: LayoutConstraints) -> Tuple[float, float, float]:
    """Return ``(min_content_width, max_content_width, intrinsic_height)``.

    For vertical/sideways writing modes the axes are swapped
    internally so that the grid solver always receives values in
    the physical (horizontal/vertical) coordinate system.

    The result is cached because it depends only on text content
    and style properties, not on *constraints*.
    """
    cached = getattr(self, '_measure_cache', None)
    if cached is not None:
        return cached
    font_chain = self._font_chain()
    if not font_chain:
        return (0.0, 0.0, 0.0)

    size = self._font_size_int()
    ws = self.style.get("white-space") or "normal"
    ow = self.style.get("overflow-wrap") or "normal"
    fm = FontManager.instance()
    ls_val, ws_val = self._spacing_values()
    hyph = self._hyphens_value()
    lang_v = self._lang_value()
    wm = self._writing_mode()

    # Add padding + border
    ph = self.style.padding_horizontal + self.style.border_horizontal
    pv = self.style.padding_vertical + self.style.border_vertical

    # -- sideways-rl / sideways-lr --
    # Shape horizontally, then swap width ↔ height.
    if wm in ("sideways-rl", "sideways-lr"):
        if self._spans is not None:
            math_be = self._math_backend()
            min_w = get_min_content_width_rich(self._spans, font_chain, size, ws, fm=fm, math_backend=math_be,
                                               letter_spacing=ls_val, word_spacing=ws_val,
                                               hyphens=hyph, lang=lang_v)
            max_w = get_max_content_width_rich(self._spans, font_chain, size, ws, fm=fm, math_backend=math_be,
                                               letter_spacing=ls_val, word_spacing=ws_val)
            lines = break_lines_rich(self._spans, max_w + 1, font_chain, size, ws, ow, fm=fm, math_backend=math_be,
                                     letter_spacing=ls_val, word_spacing=ws_val,
                                     hyphens=hyph, lang=lang_v)
            lh = self._line_height()
            text_w, text_h = compute_rich_block_size(lines, lh, float(size))
        else:
            min_w = get_min_content_width(self.text, font_chain, size, ws, fm=fm,
                                          letter_spacing=ls_val, word_spacing=ws_val,
                                          hyphens=hyph, lang=lang_v)
            max_w = get_max_content_width(self.text, font_chain, size, ws, fm=fm,
                                          letter_spacing=ls_val, word_spacing=ws_val)
            lines = break_lines(self.text, max_w + 1, font_chain, size, ws, ow, fm=fm,
                                letter_spacing=ls_val, word_spacing=ws_val,
                                hyphens=hyph, lang=lang_v)
            lh = self._line_height()
            text_w, text_h = compute_text_block_size(lines, lh, float(size))
        # Swap axes: horizontal text_h becomes physical width,
        # horizontal text_w becomes physical height.
        result = (text_h + ph, text_h + ph, max_w + pv)
        self._measure_cache = result
        return result

    # -- vertical-rl / vertical-lr --
    if wm in ("vertical-rl", "vertical-lr"):
        orient = self._text_orientation()
        min_h = get_min_content_height(self.text, font_chain, size, ws, fm=fm,
                                       orientation=orient,
                                       letter_spacing=ls_val,
                                       word_spacing=ws_val)
        max_h = get_max_content_height(self.text, font_chain, size, ws, fm=fm,
                                       orientation=orient,
                                       letter_spacing=ls_val,
                                       word_spacing=ws_val)
        # Intrinsic: one column at max height
        cols = break_columns(self.text, max_h + 1, font_chain, size, ws, ow, fm=fm,
                             orientation=orient, letter_spacing=ls_val,
                             word_spacing=ws_val)
        lh = self._line_height()
        block_w, block_h = compute_vertical_block_size(cols, lh, float(size))
        # In physical coords: width = block_w, height = block_h (= max_h)
        result = (block_w + ph, block_w + ph, max_h + pv)
        self._measure_cache = result
        return result

    # -- horizontal-tb (default) --
    if self._spans is not None:
        math_be = self._math_backend()
        min_w = get_min_content_width_rich(self._spans, font_chain, size, ws, fm=fm, math_backend=math_be,
                                           letter_spacing=ls_val, word_spacing=ws_val,
                                           hyphens=hyph, lang=lang_v)
        max_w = get_max_content_width_rich(self._spans, font_chain, size, ws, fm=fm, math_backend=math_be,
                                           letter_spacing=ls_val, word_spacing=ws_val)
        lines = break_lines_rich(self._spans, max_w + 1, font_chain, size, ws, ow, fm=fm, math_backend=math_be,
                                 letter_spacing=ls_val, word_spacing=ws_val,
                                 hyphens=hyph, lang=lang_v)
        lh = self._line_height()
        _, h = compute_rich_block_size(lines, lh, float(size))
    else:
        min_w = get_min_content_width(self.text, font_chain, size, ws, fm=fm,
                                      letter_spacing=ls_val, word_spacing=ws_val,
                                      hyphens=hyph, lang=lang_v)
        max_w = get_max_content_width(self.text, font_chain, size, ws, fm=fm,
                                      letter_spacing=ls_val, word_spacing=ws_val)
        lines = break_lines(self.text, max_w + 1, font_chain, size, ws, ow, fm=fm,
                            letter_spacing=ls_val, word_spacing=ws_val,
                            hyphens=hyph, lang=lang_v)
        lh = self._line_height()
        _, h = compute_text_block_size(lines, lh, float(size))

    result = (min_w + ph, max_w + ph, h + pv)
    self._measure_cache = result
    return result

ImageNode

ImageNode(src: Union[str, bytes, Any], style: Optional[Dict[str, Any]] = None, parent: Optional[Node] = None, object_fit: Optional[str] = None)

Bases: Node

Node that displays a raster image (PNG, JPEG, etc.).

The image's intrinsic size is read lazily via Pillow. The object-fit property controls how the image is scaled within the allocated space.

PARAMETER DESCRIPTION
src

Image source: - File path (str) - URL starting with 'http://' or 'https://' (str) - Raw image bytes (bytes) - PIL Image object

TYPE: str, bytes, or PIL.Image

style

Style properties

TYPE: dict DEFAULT: None

parent

Parent node

TYPE: Node DEFAULT: None

object_fit

Object-fit mode ('fill', 'contain', 'cover', 'none')

TYPE: str DEFAULT: None

Source code in src/latticesvg/nodes/image.py
def __init__(
    self,
    src: Union[str, bytes, Any],
    style: Optional[Dict[str, Any]] = None,
    parent: Optional[Node] = None,
    object_fit: Optional[str] = None,
) -> None:
    super().__init__(style=style, parent=parent)
    self.src: Union[str, bytes, Any] = src
    if object_fit:
        self.style.set("object-fit", object_fit)
    self._intrinsic_width: Optional[float] = None
    self._intrinsic_height: Optional[float] = None
    self._base64_data: Optional[str] = None
    self._image_bytes: Optional[bytes] = None
    self._pil_image: Optional[Any] = None
get_base64
get_base64() -> str

Return the image as a base64-encoded data URI.

Source code in src/latticesvg/nodes/image.py
def get_base64(self) -> str:
    """Return the image as a base64-encoded data URI."""
    if self._base64_data is not None:
        return self._base64_data

    # Determine MIME type
    mime = "image/png"
    if isinstance(self.src, str):
        if self.src.startswith('http://') or self.src.startswith('https://'):
            # Guess from URL
            lower = self.src.lower()
            if '.jpg' in lower or '.jpeg' in lower:
                mime = "image/jpeg"
            elif '.gif' in lower:
                mime = "image/gif"
            elif '.webp' in lower:
                mime = "image/webp"
            elif '.svg' in lower:
                mime = "image/svg+xml"
        else:
            # File path
            ext = os.path.splitext(self.src)[1].lower()
            mime = {
                ".png": "image/png",
                ".jpg": "image/jpeg",
                ".jpeg": "image/jpeg",
                ".gif": "image/gif",
                ".webp": "image/webp",
                ".svg": "image/svg+xml",
            }.get(ext, "image/png")

    # Get bytes
    img_bytes: bytes
    if isinstance(self.src, bytes):
        img_bytes = self.src
    elif hasattr(self.src, 'size') and hasattr(self.src, 'mode'):
        # PIL Image — save to bytes
        from PIL import Image  # type: ignore
        buf = io.BytesIO()
        # Determine format from mime
        fmt = "PNG"
        if "jpeg" in mime:
            fmt = "JPEG"
        elif "gif" in mime:
            fmt = "GIF"
        elif "webp" in mime:
            fmt = "WEBP"
        self.src.save(buf, format=fmt)
        img_bytes = buf.getvalue()
    elif isinstance(self.src, str) and (self.src.startswith('http://') or self.src.startswith('https://')):
        # URL — use cached bytes if available
        if self._image_bytes is None:
            import urllib.request
            with urllib.request.urlopen(self.src) as response:
                self._image_bytes = response.read()
        img_bytes = self._image_bytes
    else:
        # File path
        with open(self.src, "rb") as f:
            img_bytes = f.read()

    data = base64.b64encode(img_bytes).decode("ascii")
    self._base64_data = f"data:{mime};base64,{data}"
    return self._base64_data

MplNode

MplNode(figure: Any, style: Optional[Dict[str, Any]] = None, parent: Optional[Node] = None, auto_mpl_font: bool = True, tight_layout: bool = True)

Bases: Node

Node that embeds a Matplotlib figure as an SVG fragment.

The figure is rendered to an in-memory SVG buffer during the render phase, so all Matplotlib customisation should be done before creating this node.

PARAMETER DESCRIPTION
figure

A matplotlib.figure.Figure instance.

TYPE: Any

style

Optional CSS-like style dictionary.

TYPE: Optional[Dict[str, Any]] DEFAULT: None

parent

Optional parent node.

TYPE: Optional[Node] DEFAULT: None

auto_mpl_font

When True (default), the node reads the inherited font-family CSS property and configures matplotlib's font settings via rc_context so that text rendered inside the figure uses the same font family as surrounding :class:TextNode elements.

TYPE: bool DEFAULT: True

tight_layout

When True (default), figure.tight_layout() is called inside the font rc_context before exporting, so that text metrics match the final font. Set to False if you manage layout yourself or use constrained_layout.

TYPE: bool DEFAULT: True

Source code in src/latticesvg/nodes/mpl.py
def __init__(
    self,
    figure: Any,  # matplotlib.figure.Figure
    style: Optional[Dict[str, Any]] = None,
    parent: Optional[Node] = None,
    auto_mpl_font: bool = True,
    tight_layout: bool = True,
) -> None:
    super().__init__(style=style, parent=parent)
    self.figure = figure
    self._auto_mpl_font = auto_mpl_font
    self._tight_layout = tight_layout
    self._svg_cache: Optional[str] = None
get_svg_fragment
get_svg_fragment() -> str

Export the figure to an SVG string (cached).

When auto_mpl_font is enabled the inherited font-family CSS property is translated into matplotlib rcParams so that text inside the figure uses matching fonts. svg.fonttype is always set to "path" so that glyphs are converted to vector paths for cross-platform consistency.

Source code in src/latticesvg/nodes/mpl.py
def get_svg_fragment(self) -> str:
    """Export the figure to an SVG string (cached).

    When *auto_mpl_font* is enabled the inherited ``font-family``
    CSS property is translated into matplotlib ``rcParams`` so that
    text inside the figure uses matching fonts.  ``svg.fonttype`` is
    always set to ``"path"`` so that glyphs are converted to vector
    paths for cross-platform consistency.
    """
    if self._svg_cache is None:
        import matplotlib as mpl

        rc_overrides: Dict[str, Any] = {"svg.fonttype": "path"}
        if self._auto_mpl_font:
            font_rc, font_paths = self._resolve_mpl_font_rc()
            self._register_fonts_with_mpl(font_paths)
            rc_overrides.update(font_rc)

        buf = io.BytesIO()
        with mpl.rc_context(rc_overrides):
            if self._tight_layout:
                try:
                    self.figure.tight_layout()
                except Exception:
                    pass
            self.figure.savefig(buf, format="svg", transparent=True)
        buf.seek(0)
        svg = buf.read().decode("utf-8")
        # Strip the XML declaration and outer <svg> tags for embedding
        self._svg_cache = self._strip_svg_wrapper(svg)
    return self._svg_cache

SVGNode

SVGNode(svg: str, *, style: Optional[Dict[str, Any]] = None, parent: Optional[Node] = None, is_file: bool = False)

Bases: Node

Node that embeds raw SVG content (from a string or file).

The SVG's viewBox or width/height attributes are used to determine its intrinsic size. During rendering the content is scaled to fit the allocated space.

PARAMETER DESCRIPTION
svg

SVG source: - File path (when is_file=True) - URL starting with 'http://' or 'https://' - Raw SVG string

TYPE: str

style

Style properties

TYPE: dict DEFAULT: None

parent

Parent node

TYPE: Node DEFAULT: None

is_file

If True, treat svg as a file path. Default False.

TYPE: bool DEFAULT: False

Source code in src/latticesvg/nodes/svg.py
def __init__(
    self,
    svg: str,
    *,
    style: Optional[Dict[str, Any]] = None,
    parent: Optional[Node] = None,
    is_file: bool = False,
) -> None:
    super().__init__(style=style, parent=parent)

    # Load SVG content from various sources
    if is_file:
        with open(svg, "r", encoding="utf-8") as f:
            self.svg_content: str = f.read()
    elif svg.startswith('http://') or svg.startswith('https://'):
        # Load from URL
        import urllib.request
        with urllib.request.urlopen(svg) as response:
            self.svg_content = response.read().decode('utf-8')
    else:
        self.svg_content = svg

    self._intrinsic: Optional[Tuple[float, float]] = None
    self._vb_min_x: float = 0.0
    self._vb_min_y: float = 0.0
get_inner_svg
get_inner_svg() -> str

Return SVG content with the outer <svg> wrapper stripped.

This is used for embedding inside another SVG document. Presentation attributes (fill, stroke, etc.) from the outer <svg> element are preserved by wrapping inner content in a <g> that carries those attributes forward.

Source code in src/latticesvg/nodes/svg.py
def get_inner_svg(self) -> str:
    """Return SVG content with the outer ``<svg>`` wrapper stripped.

    This is used for embedding inside another SVG document.
    Presentation attributes (fill, stroke, etc.) from the outer
    ``<svg>`` element are preserved by wrapping inner content in
    a ``<g>`` that carries those attributes forward.
    """
    content = self.svg_content
    # Remove XML declaration
    content = re.sub(r'<\?xml[^?]*\?>\s*', '', content)
    # Remove DOCTYPE
    content = re.sub(r'<!DOCTYPE[^>]*>\s*', '', content)
    # Remove HTML comments (license headers etc.)
    content = re.sub(r'<!--.*?-->', '', content, flags=re.DOTALL)
    # Extract content inside <svg ...>...</svg>
    m = re.search(r'<svg([^>]*)>(.*)</svg>', content, re.DOTALL)
    if m:
        svg_attrs = m.group(1)
        inner = m.group(2).strip()
        # Extract presentation attributes from <svg> to carry forward
        pres_attrs = []
        for attr in ('fill', 'stroke', 'stroke-width', 'stroke-linecap',
                     'stroke-linejoin', 'opacity', 'color'):
            match = re.search(rf'\b{attr}\s*=\s*"([^"]*)"', svg_attrs)
            if match:
                val = match.group(1)
                # Replace currentColor with a usable default
                if val == 'currentColor':
                    val = '#000000'
                pres_attrs.append(f'{attr}="{val}"')
        if pres_attrs:
            attrs_str = ' '.join(pres_attrs)
            return f'<g {attrs_str}>{inner}</g>'
        return inner
    return content.strip()

MathNode

MathNode(latex: str, *, style: Optional[Dict[str, Any]] = None, backend: Optional[str] = None, display: bool = True, parent: Optional[Node] = None)

Bases: Node

Node that renders a LaTeX math expression to SVG.

By default the QuickJax backend (in-process MathJax v4) is used. A different backend can be selected per-node or globally via :func:latticesvg.math.set_default_backend.

Usage::

formula = MathNode(r"E = mc^2", style={"font-size": "20px"})
Source code in src/latticesvg/nodes/math.py
def __init__(
    self,
    latex: str,
    *,
    style: Optional[Dict[str, Any]] = None,
    backend: Optional[str] = None,
    display: bool = True,
    parent: Optional[Node] = None,
) -> None:
    super().__init__(style=style or {}, parent=parent)
    self.latex: str = latex
    self.display: bool = display
    self._backend_name: Optional[str] = backend
    self._svg_cache: Optional[object] = None  # SVGFragment
    self.scale_x: float = 1.0
    self.scale_y: float = 1.0
get_svg_fragment
get_svg_fragment() -> str

Return the rendered SVG content for embedding.

Source code in src/latticesvg/nodes/math.py
def get_svg_fragment(self) -> str:
    """Return the rendered SVG content for embedding."""
    frag = self._get_fragment()
    return frag.svg_content