Skip to content

Cairo

momapy.rendering.cairo

Class for rendering with Cairo.

Note

SVG filter effects (e.g. drop shadow, Gaussian blur) are not supported by this backend. Cairo has no native filter primitives, so any filter attribute on drawing elements is ignored. Use the skia or svg-native backend if you need filter effects.

Classes:

Name Description
CairoRenderer

Renderer implementation using the Cairo graphics library.

CairoRenderer dataclass

CairoRenderer(_current_state: dict[str, Any] = dict(), _states: list[dict[str, Any]] = list(), *, context: Context, _config: dict[str, Any] = dict(), _pango_font_descriptions: dict[tuple, Any] = dict())

Bases: StatefulRenderer, SupportsFileOutput

Renderer implementation using the Cairo graphics library.

This renderer supports multiple output formats including PDF, SVG, PNG, and PostScript. It uses Pango for text rendering.

SVG filter effects (drop shadow, Gaussian blur, etc.) are not supported: Cairo has no native filter primitives, so the filter attribute on drawing elements is ignored.

Attributes:

Name Type Description
context Context

The Cairo context used for rendering

Examples:

from momapy.meta.nodes import Rectangle
from momapy.geometry import Point

# Create a layout element to render
node = Rectangle(
    position=Point(100.0, 100.0),
    width=200.0,
    height=100.0
)

# Create renderer and render the element
renderer = CairoRenderer.from_file("output.pdf", 800, 600, "pdf")
renderer.begin_session()
renderer.render_layout_element(node)
renderer.end_session()

Methods:

Name Description
__post_init__

Initialize the renderer's current state after initialization.

begin_session

Begin a rendering session.

end_session

End the rendering session and save the output.

from_file

Create a CairoRenderer instance from a file path.

get_bolder_font_weight

Return the lightest font weight bolder than the given font weight.

get_current_state

Return the current state.

get_current_value

Return the current value for an attribute.

get_initial_value

Return the initial value for an attribute.

get_lighter_font_weight

Return the boldest font weight lighter than the given font weight.

new_page

Create a new page in the output document.

render_drawing_element

Render a drawing element to the output.

render_layout_element

Render a layout element to the output.

render_map

Render a map.

restore

Set the current state to the last saved state.

save

Save the current state.

self_restore

Restore the Cairo context state.

self_save

Save the Cairo context state.

set_current_state

Set the current state to the given state.

set_current_state_from_drawing_element

Set the current state to a state given by a drawing element.

set_current_value

Set the current value for an attribute.

__post_init__

__post_init__() -> None

Initialize the renderer's current state after initialization.

Source code in src/momapy/rendering/core.py
def __post_init__(self) -> None:
    """Initialize the renderer's current state after initialization."""
    self._initialize_current_state()

begin_session

begin_session() -> None

Begin a rendering session.

This method initializes the rendering context. For CairoRenderer, no explicit initialization is needed beyond the context setup.

Source code in src/momapy/rendering/cairo.py
def begin_session(self) -> None:
    """Begin a rendering session.

    This method initializes the rendering context. For CairoRenderer,
    no explicit initialization is needed beyond the context setup.
    """
    pass

default_format class-attribute

default_format: str | None = 'pdf'

The format used when from_file is called with format_=None.

Subclasses set this to one of their :attr:supported_formats.

end_session

end_session() -> None

End the rendering session and save the output.

This method finalizes the rendering and saves the output to the file. For PNG format, it writes the image data. For other formats, it finishes and flushes the surface.

Source code in src/momapy/rendering/cairo.py
def end_session(self) -> None:
    """End the rendering session and save the output.

    This method finalizes the rendering and saves the output to the file.
    For PNG format, it writes the image data. For other formats, it
    finishes and flushes the surface.
    """
    surface = self.context.get_target()
    format_ = self._config.get("format")
    if format_ == "png":
        surface.write_to_png(self._config["file_path"])
    surface.finish()
    surface.flush()

from_file classmethod

from_file(file_path: str | PathLike, width: float, height: float, format_: str | None = None) -> Self

Create a CairoRenderer instance from a file path.

Parameters:

Name Type Description Default
file_path str | PathLike

The output file path

required
width float

The width of the canvas

required
height float

The height of the canvas

required
format_ str | None

The output format (pdf, svg, png, or ps). None selects the backend's :attr:default_format ("pdf").

None

Returns:

Type Description
Self

A new CairoRenderer instance

Raises:

Type Description
ValueError

If the format is not supported

Examples:

renderer = CairoRenderer.from_file("output.pdf", 800, 600, "pdf")
Source code in src/momapy/rendering/cairo.py
@classmethod
def from_file(
    cls,
    file_path: str | os.PathLike,
    width: float,
    height: float,
    format_: str | None = None,
) -> typing_extensions.Self:
    """Create a CairoRenderer instance from a file path.

    Args:
        file_path: The output file path
        width: The width of the canvas
        height: The height of the canvas
        format_: The output format (pdf, svg, png, or ps). ``None`` selects
            the backend's :attr:`default_format` ("pdf").

    Returns:
        A new CairoRenderer instance

    Raises:
        ValueError: If the format is not supported

    Examples:
        ```python
        renderer = CairoRenderer.from_file("output.pdf", 800, 600, "pdf")
        ```
    """
    if format_ is None:
        format_ = cls.default_format
    if format_ not in cls.supported_formats:
        raise ValueError(
            f"unsupported format {format_!r}: expected one of "
            f"{', '.join(cls.supported_formats)}"
        )
    check_parent_dir_exists(file_path)
    config = {}
    if format_ == "pdf":
        surface = cairo.PDFSurface(file_path, width, height)
    elif format_ == "ps":
        surface = cairo.PSSurface(file_path, width, height)
    elif format_ == "svg":
        surface = cairo.SVGSurface(file_path, width, height)
    elif format_ == "png":
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height))
    config["surface"] = surface
    config["file_path"] = file_path
    config["width"] = width
    config["height"] = height
    config["format"] = format_
    context = cairo.Context(surface)
    return cls(context=context, _config=config)

get_bolder_font_weight classmethod

get_bolder_font_weight(font_weight: FontWeight | float) -> float

Return the lightest font weight bolder than the given font weight.

Source code in src/momapy/rendering/core.py
@classmethod
def get_bolder_font_weight(cls, font_weight: FontWeight | float) -> float:
    """Return the lightest font weight bolder than the given font weight."""
    if isinstance(font_weight, FontWeight):
        font_weight = cls.font_weight_value_mapping.get(font_weight)
        if font_weight is None:
            raise ValueError(
                f"font weight must be a float, {FontWeight.NORMAL}, or {FontWeight.BOLD}"
            )
    if font_weight < 400:
        new_font_weight = 400.0
    elif font_weight < 600:
        new_font_weight = 700.0
    else:
        new_font_weight = 900.0
    return new_font_weight

get_current_state

get_current_state() -> dict[str, Any]

Return the current state.

Source code in src/momapy/rendering/core.py
def get_current_state(self) -> dict[str, typing.Any]:
    """Return the current state."""
    return self._current_state

get_current_value

get_current_value(attr_name: str) -> Any

Return the current value for an attribute.

Source code in src/momapy/rendering/core.py
def get_current_value(self, attr_name: str) -> typing.Any:
    """Return the current value for an attribute."""
    return self.get_current_state()[attr_name]

get_initial_value

get_initial_value(attr_name: str) -> Any

Return the initial value for an attribute.

Source code in src/momapy/rendering/core.py
def get_initial_value(self, attr_name: str) -> typing.Any:
    """Return the initial value for an attribute."""
    attr_value = self.initial_values.get(attr_name)
    if attr_value is None:
        attr_d = PRESENTATION_ATTRIBUTES[attr_name]
        attr_value = attr_d["initial"]
        if attr_value is None:
            attr_value = INITIAL_VALUES[attr_name]
    return attr_value

get_lighter_font_weight classmethod

get_lighter_font_weight(font_weight: FontWeight | float) -> float

Return the boldest font weight lighter than the given font weight.

Source code in src/momapy/rendering/core.py
@classmethod
def get_lighter_font_weight(cls, font_weight: FontWeight | float) -> float:
    """Return the boldest font weight lighter than the given font weight."""
    if isinstance(font_weight, FontWeight):
        font_weight = cls.font_weight_value_mapping.get(font_weight)
        if font_weight is None:
            raise ValueError(
                f"font weight must be a float, {FontWeight.NORMAL}, or {FontWeight.BOLD}"
            )
    if font_weight > 700:
        new_font_weight = 700.0
    elif font_weight > 500:
        new_font_weight = 400.0
    else:
        new_font_weight = 100.0
    return new_font_weight

new_page

new_page(width: float, height: float) -> None

Create a new page in the output document.

Parameters:

Name Type Description Default
width float

The width of the new page

required
height float

The height of the new page

required
Note

Only PDF and PostScript formats support multiple pages. Other formats will ignore this call.

Source code in src/momapy/rendering/cairo.py
def new_page(self, width: float, height: float) -> None:
    """Create a new page in the output document.

    Args:
        width: The width of the new page
        height: The height of the new page

    Note:
        Only PDF and PostScript formats support multiple pages.
        Other formats will ignore this call.
    """
    format_ = self._config.get("format")
    if format_ == "pdf" or format_ == "ps":
        self.context.show_page()
        surface = self.context.get_target()
        surface.set_size(width, height)

render_drawing_element

render_drawing_element(drawing_element: DrawingElement) -> None

Render a drawing element to the output.

Parameters:

Name Type Description Default
drawing_element DrawingElement

The drawing element to render

required

This method handles transformations and delegates to the appropriate rendering method based on the drawing element type.

Source code in src/momapy/rendering/cairo.py
def render_drawing_element(self, drawing_element: DrawingElement) -> None:
    """Render a drawing element to the output.

    Args:
        drawing_element: The drawing element to render

    This method handles transformations and delegates to
    the appropriate rendering method based on the drawing element type.
    """
    self.save()
    self.set_current_state_from_drawing_element(drawing_element)
    self._add_transform_from_drawing_element(drawing_element)
    class_ = type(drawing_element)
    if issubclass(class_, Builder):
        class_ = class_._cls_to_build
    de_func = getattr(self, self._de_class_func_mapping[class_])
    de_func(drawing_element)
    self.restore()

render_layout_element

render_layout_element(layout_element: LayoutElement) -> None

Render a layout element to the output.

Parameters:

Name Type Description Default
layout_element LayoutElement

The layout element to render

required
Source code in src/momapy/rendering/cairo.py
def render_layout_element(self, layout_element: LayoutElement) -> None:
    """Render a layout element to the output.

    Args:
        layout_element: The layout element to render
    """
    drawing_elements = layout_element.drawing_elements()
    for drawing_element in drawing_elements:
        self.render_drawing_element(drawing_element)

render_map

render_map(map_: Map) -> None

Render a map.

This is a convenience method, not part of the abstract contract: the default implementation renders the map's layout via :meth:render_layout_element, which is what every built-in backend needs. Subclasses may override it if a backend requires map-specific handling, but they are not obliged to. The file pipeline (:func:render_map/:func:render_maps) does not call this method; it renders each page through :meth:render_layout_element.

Parameters:

Name Type Description Default
map_ Map

The map to render.

required

Raises:

Type Description
ValueError

If the map has no layout to render.

Source code in src/momapy/rendering/core.py
def render_map(self, map_: Map) -> None:
    """Render a map.

    This is a convenience method, **not** part of the abstract
    contract: the default implementation renders the map's layout via
    :meth:`render_layout_element`, which is what every built-in backend
    needs. Subclasses may override it if a backend requires
    map-specific handling, but they are not obliged to. The file
    pipeline (:func:`render_map`/:func:`render_maps`) does not call this
    method; it renders each page through :meth:`render_layout_element`.

    Args:
        map_: The map to render.

    Raises:
        ValueError: If the map has no layout to render.
    """
    if map_.layout is None:
        raise ValueError(
            "map has no layout to render (its layout is None); "
            "a layout-less map (e.g. an SBML map) cannot be rendered"
        )
    self.render_layout_element(map_.layout)

restore

restore() -> None

Set the current state to the last saved state.

Source code in src/momapy/rendering/core.py
def restore(self) -> None:
    """Set the current state to the last saved state."""
    if len(self._states) > 0:
        state = self._states.pop()
        self.set_current_state(state)
        self.self_restore()
    else:
        raise RuntimeError(
            "restore() called with an empty state stack: no matching "
            "save() to restore from"
        )

save

save() -> None

Save the current state.

Source code in src/momapy/rendering/core.py
def save(self) -> None:
    """Save the current state."""
    self._states.append(copy.deepcopy(self.get_current_state()))
    self.self_save()

self_restore

self_restore() -> None

Restore the Cairo context state.

This method restores the Cairo context to the state saved by the most recent call to self_save().

Source code in src/momapy/rendering/cairo.py
def self_restore(self) -> None:
    """Restore the Cairo context state.

    This method restores the Cairo context to the state saved by the
    most recent call to self_save().
    """
    self.context.restore()
    self.context.new_path()

self_save

self_save() -> None

Save the Cairo context state.

This method saves the current state of the Cairo context, including transformations, clipping regions, and drawing parameters.

Source code in src/momapy/rendering/cairo.py
def self_save(self) -> None:
    """Save the Cairo context state.

    This method saves the current state of the Cairo context, including
    transformations, clipping regions, and drawing parameters.
    """
    self.context.save()

set_current_state

set_current_state(state: dict[str, Any]) -> None

Set the current state to the given state.

Source code in src/momapy/rendering/core.py
def set_current_state(self, state: dict[str, typing.Any]) -> None:
    """Set the current state to the given state."""
    for attr_name, attr_value in state.items():
        self.set_current_value(attr_name, attr_value)

set_current_state_from_drawing_element

set_current_state_from_drawing_element(drawing_element: DrawingElement) -> None

Set the current state to a state given by a drawing element.

Source code in src/momapy/rendering/core.py
def set_current_state_from_drawing_element(
    self, drawing_element: DrawingElement
) -> None:
    """Set the current state to a state given by a drawing element."""
    state = self._get_state_from_drawing_element(drawing_element)
    self.set_current_state(state)

set_current_value

set_current_value(attr_name: str, attr_value: Any) -> None

Set the current value for an attribute.

Source code in src/momapy/rendering/core.py
def set_current_value(self, attr_name: str, attr_value: typing.Any) -> None:
    """Set the current value for an attribute."""
    if attr_value is None:
        attr_d = PRESENTATION_ATTRIBUTES[attr_name]
        if not attr_d["inherited"]:
            attr_value = self.initial_values.get(attr_name)
            if attr_value is None:
                attr_value = attr_d["initial"]
            if attr_value is None:
                attr_value = INITIAL_VALUES[attr_name]
    if attr_name == "font_weight":
        if isinstance(attr_value, FontWeight):
            if attr_value == FontWeight.NORMAL or attr_value == FontWeight.BOLD:
                attr_value = self.font_weight_value_mapping[attr_value]
            elif attr_value == FontWeight.BOLDER:
                attr_value = self.get_bolder_font_weight(
                    self.get_current_value("font_weight")
                )
            elif attr_value == FontWeight.LIGHTER:
                attr_value = self.get_lighter_font_weight(
                    self.get_current_value("font_weight")
                )
    if attr_value is not None:
        self._current_state[attr_name] = attr_value