Nodezator's logo
Nodezator: multi-purpose visual node editor for the Python programming language
pip install nodezator, then run the nodezator command

Render math notation with ASCIIMath using py_asciimath and sympy libraries

Screenshot of Nodezator demonstrating conversion of ASCIIMath code into a rendered surface.

A user asked me for ASCIIMath support within Nodezator, that is, to be able to turn ASCIIMath (a markup language to represent math notation) into the corresponding rendered image. Thankfully, this could already be done within Nodezator! Nodezator is just a node-based environment for Python callables, so it can use virtually any Python code/library available. All I had to do was experiment a bit with available Python libraries and the solution I came up with was the one demonstrated in the video below:

In it, I use the py_asciimath library to convert the ASCIIMath code into a format that is more widely supported, which is Latex. The Latex code generated can then be easily rendered as an image by the sympy library, using its preview function. For sympy.preview to work for this purpose, though, you must make sure the latex command is available in your system, since it is used internally by sympy to perform the rendering. Latex is available for all major systems. Only parts of it can be installed depending on your specific purposes, but since I wasn't sure what parts of it were needed for math notation rendering, I decided to make a full installation, which is what I recommend. Beware that it is a somewhat large download though (a few gigabytes).

Those operations resulted in 02 custom nodes being created: asciimath2latex and latex2surface:

Nodes for converting ASCIIMath code into a rendered surface (image).

Here are the sources of those nodes:

asciimath2latex node:


### third-party import
from py_asciimath.translator.translator import ASCIIMath2Tex



## translator callable
latex_from_asciimath = ASCIIMath2Tex(log=False, inplace=True).translate


## main callable

def asciimath2latex(asciimath_code:str='') -> [
    {'name': 'latex_code', 'type': str}
]:
    return latex_from_asciimath(
        asciimath_code,
        displaystyle=True,
        from_file=False,
        pprint=False,
    )


main_callable = asciimath2latex

latex2surface node:


### standard library import
from io import BytesIO


### third-party imports

## pygame-ce

from pygame import Surface

from pygame.image import load as load_image


## sympy
from sympy import preview



def latex2surface(
    latex_code: str = '',
    font_size_prefix : {
        'widget_name': 'option_menu',
        'widget_kwargs': {
            'options': [
                'none',
                r'\tiny',
                r'\scriptsize',
                r'\footnotesize',
                r'\small',
                r'\normalsize',
                r'\large',
                r'\Large',
                r'\LARGE',
                r'\huge',
                r'\Huge',
            ],
        },
        'type': str,
    } = r'\Large',
) -> [
    {'name': 'surface', 'type': Surface}
]:

    ### add font size prefix if requested

    if font_size_prefix != 'none':
        latex_code = font_size_prefix + latex_code

    ### create bytes stream and populate it with bytes
    ### representing rendered PNG file

    bytes_io = BytesIO()

    preview(
        latex_code,
        output='png',
        viewer='BytesIO',
        outputbuffer=bytes_io,
    )

    ### change the stream position to start of stream
    ### (this way the bytes can be read properly)
    bytes_io.seek(0)

    ### convert bytes stream (a file-like object), into
    ### a surface
    surface = load_image(bytes_io, 'file.png')

    ### close the bytes stream, since we won't need it anymore
    bytes_io.close()

    ### finally return the surface
    return surface


main_callable = latex2surface

You can use this source however you see fit, it is mostly comprised of calls to functions from external libraries anyway (but even if I were to license it, I'd use a public domain license, just like I did with Nodezator and other Indie Python projects). If you don't know how to load nodes into Nodezator or how to use it altogether, there's an online manual with all information you need: https://manual.nodezator.com.

The other nodes in the demonstration video are nodes available by default in Nodezator. To visualize the surface, I used the view_surface node (popup menu > general viewer nodes > view_surface), but before I used the increase_surf_border node (popup menu > pygame-ce > Encapsulations > increase_surf_border), with the color set to white to add a border around it (to serve as a padding). This is not shown in the video, but if the user desires, the surface can also be saved to disk as an image file (.png/.jpg) with the save_surf_to_file node (popup menu > pygame-ce > pygame.image > save_surf_to_file).

Here's the link to the original discussion between me and the user: https://github.com/IndiePython/nodezator/discussions/67.