Skip to content

Skia

momapy.rendering.skia

Class for rendering with Skia

Classes:

Name Description
SkiaRenderer

Renderer implementation using the Skia graphics library.

SkiaRenderer dataclass

SkiaRenderer(_current_state: dict = dict(), _states: list[dict] = list(), *, canvas: Canvas, _config: dict = dict(), _skia_typefaces: dict = dict(), _skia_fonts: dict = dict())

Bases: StatefulRenderer

Renderer implementation using the Skia graphics library.

This renderer supports multiple output formats including PDF, SVG, PNG, JPEG, and WebP. It provides hardware-accelerated rendering capabilities and advanced features like filters and effects.

Attributes:

Name Type Description
canvas Canvas

The Skia canvas used for rendering

Example

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

Create a layout element to render

node = Rectangle( ... position=momapy.geometry.Point(100, 100), ... width=200, ... height=100 ... )

Create renderer and render the element

renderer = SkiaRenderer.from_file("output.pdf", 800, 600, "pdf") renderer.begin_session() renderer.render_layout_element(node) renderer.end_session()

Methods:

Name Description
begin_session

Begin a rendering session.

end_session

End the rendering session and save the output.

from_file

Create a SkiaRenderer 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 to the output.

restore

Set the current state to the last saved state

save

Save the current state

self_restore

Restore the Skia canvas state.

self_save

Save the Skia canvas 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

begin_session

begin_session()

Begin a rendering session.

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

Source code in src/momapy/rendering/skia.py
def begin_session(self):
    """Begin a rendering session.

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

end_session

end_session()

End the rendering session and save the output.

This method finalizes the rendering, flushes the canvas, and saves the output to the file. The specific actions depend on the output format: - PDF: Ends the page and closes the document - PNG/JPEG/WebP: Takes a snapshot and saves the image - SVG: Flushes the stream

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

    This method finalizes the rendering, flushes the canvas, and saves
    the output to the file. The specific actions depend on the output format:
    - PDF: Ends the page and closes the document
    - PNG/JPEG/WebP: Takes a snapshot and saves the image
    - SVG: Flushes the stream
    """
    self.canvas.flush()
    format_ = self._config.get("format")
    if format_ == "pdf":
        self._config["document"].endPage()
        self._config["document"].close()
    elif format_ == "png":
        image = self._config["surface"].makeImageSnapshot()
        image.save(self._config["file_path"], skia.kPNG)
    elif format_ == "jpeg":
        image = self._config["surface"].makeImageSnapshot()
        image.save(self._config["file_path"], skia.kJPEG)
    elif format_ == "webp":
        image = self._config["surface"].makeImageSnapshot()
        image.save(self._config["file_path"], skia.kWEBP)
    elif format_ == "svg":
        del self.canvas
        self._config["stream"].flush()

from_file classmethod

from_file(file_path: str | PathLike, width: float, height: float, format_: Literal['pdf', 'svg', 'png', 'jpeg', 'webp'] = 'pdf') -> Self

Create a SkiaRenderer 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_ Literal['pdf', 'svg', 'png', 'jpeg', 'webp']

The output format (pdf, svg, png, jpeg, or webp)

'pdf'

Returns:

Type Description
Self

A new SkiaRenderer instance

Raises:

Type Description
ValueError

If the format is not supported

Example

renderer = SkiaRenderer.from_file("output.pdf", 800, 600, "pdf")

Source code in src/momapy/rendering/skia.py
@classmethod
def from_file(
    cls,
    file_path: str | os.PathLike,
    width: float,
    height: float,
    format_: typing.Literal["pdf", "svg", "png", "jpeg", "webp"] = "pdf",
) -> typing_extensions.Self:
    """Create a SkiaRenderer 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, jpeg, or webp)

    Returns:
        A new SkiaRenderer instance

    Raises:
        ValueError: If the format is not supported

    Example:
        >>> renderer = SkiaRenderer.from_file("output.pdf", 800, 600, "pdf")
    """
    if format_ not in cls.supported_formats:
        raise ValueError(f"Unsupported format: {format_}")
    config = {}
    canvas = None
    if format_ == "pdf":
        stream = skia.FILEWStream(file_path)
        document = skia.PDF.MakeDocument(stream)
        canvas = document.beginPage(width, height)
        config["stream"] = stream
        config["document"] = document
    elif format_ in ["png", "jpeg", "webp"]:
        surface = skia.Surface(width=int(width), height=int(height))
        canvas = surface.getCanvas()
        config["surface"] = surface
        config["file_path"] = file_path
    elif format_ == "svg":
        stream = skia.FILEWStream(file_path)
        canvas = skia.SVGCanvas.Make((width, height), stream)
        config["stream"] = stream
    config["file_path"] = file_path
    config["width"] = width
    config["height"] = height
    config["format"] = format_
    return cls(canvas=canvas, _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: momapy.drawing.FontWeight | float
) -> float:
    """Return the lightest font weight bolder than the given font weight"""
    if isinstance(font_weight, momapy.drawing.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, {momapy.drawing.FontWeight.NORMAL}, or {momapy.drawing.FontWeight.BOLD}"
            )
    if font_weight < 400:
        new_font_weight = 400
    elif font_weight < 600:
        new_font_weight = 700
    else:
        new_font_weight = 900
    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 = momapy.drawing.PRESENTATION_ATTRIBUTES[attr_name]
        attr_value = attr_d["initial"]
        if attr_value is None:
            attr_value = momapy.drawing.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: momapy.drawing.FontWeight | float
) -> float:
    """Return the boldest font weight lighter than the given font weight"""
    if isinstance(font_weight, momapy.drawing.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, {momapy.drawing.FontWeight.NORMAL}, or {momapy.drawing.FontWeight.BOLD}"
            )
    if font_weight > 700:
        new_font_weight = 700
    elif font_weight > 500:
        new_font_weight = 400
    else:
        new_font_weight = 100
    return new_font_weight

new_page

new_page(width, height)

Create a new page in the output document.

Parameters:

Name Type Description Default
width

The width of the new page

required
height

The height of the new page

required
Note

Only PDF format supports multiple pages. Other formats will ignore this call.

Source code in src/momapy/rendering/skia.py
def new_page(self, width, height):
    """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 format supports multiple pages. Other formats will ignore this call.
    """
    format_ = self._config.get("format")
    if format_ == "pdf":
        self._config["document"].endPage()
        canvas = self._config["document"].beginPage(width, height)
        self.canvas = canvas

render_drawing_element

render_drawing_element(drawing_element)

Render a drawing element to the output.

Parameters:

Name Type Description Default
drawing_element

The drawing element to render

required

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

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

    Args:
        drawing_element: The drawing element to render

    This method handles filters, 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_, momapy.builder.Builder):
        class_ = class_._cls_to_build
    de_func = getattr(self, self._de_class_func_mapping[class_])
    filter = self.get_current_value("filter")
    if filter is not momapy.drawing.NoneValue:
        bbox = drawing_element.bbox()
        saved_canvas = self.canvas
        recorder = skia.PictureRecorder()
        canvas = recorder.beginRecording(
            skia.Rect.MakeXYWH(
                bbox.north_west().x,
                bbox.north_west().y,
                bbox.width,
                bbox.height,
            )
        )
        self.canvas = canvas
        de_func(drawing_element)
        picture = recorder.finishRecordingAsPicture()
        skia_paint = self._make_filter_paint(
            filter, drawing_element.get_filter_region()
        )
        self.canvas = saved_canvas
        self.canvas.drawPicture(picture, paint=skia_paint)
    else:
        de_func(drawing_element)
    self.restore()

render_layout_element

render_layout_element(layout_element)

Render a layout element to the output.

Parameters:

Name Type Description Default
layout_element

The layout element to render

required
Source code in src/momapy/rendering/skia.py
def render_layout_element(self, layout_element):
    """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_)

Render a map to the output.

Parameters:

Name Type Description Default
map_

The map to render

required
Source code in src/momapy/rendering/skia.py
def render_map(self, map_):
    """Render a map to the output.

    Args:
        map_: The map to render
    """
    self.render_layout_element(map_.layout)

restore

restore()

Set the current state to the last saved state

Source code in src/momapy/rendering/core.py
def restore(self):
    """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 Exception("no state to be restored")

save

save()

Save the current state

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

self_restore

self_restore()

Restore the Skia canvas state.

This method restores the Skia canvas to the state saved by the most recent call to self_save().

Source code in src/momapy/rendering/skia.py
def self_restore(self):
    """Restore the Skia canvas state.

    This method restores the Skia canvas to the state saved by the
    most recent call to self_save().
    """
    self.canvas.restore()

self_save

self_save()

Save the Skia canvas state.

This method saves the current state of the Skia canvas, including transformations and clipping regions.

Source code in src/momapy/rendering/skia.py
def self_save(self):
    """Save the Skia canvas state.

    This method saves the current state of the Skia canvas, including
    transformations and clipping regions.
    """
    self.canvas.save()

set_current_state

set_current_state(state: dict)

Set the current state to the given state

Source code in src/momapy/rendering/core.py
def set_current_state(self, state: dict):
    """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)

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: momapy.drawing.DrawingElement
):
    """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)

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):
    """Set the current value for an attribute"""
    if attr_value is None:
        attr_d = momapy.drawing.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 = momapy.drawing.INITIAL_VALUES[attr_name]
    if attr_name == "font_weight":
        if isinstance(attr_value, momapy.drawing.FontWeight):
            if (
                attr_value == momapy.drawing.FontWeight.NORMAL
                or attr_value == momapy.drawing.FontWeight.BOLD
            ):
                attr_value = self.font_weight_value_mapping[attr_value]
            elif attr_value == momapy.drawing.FontWeight.BOLDER:
                attr_value = self.get_bolder_font_weight(
                    self.get_current_value("font_weight")
                )
            elif attr_value == momapy.drawing.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