Create extension (custom role) [1] [2]

In some cases, there is no adequate role available for your purpose, for instance, when nesting two roles. In such cases, adding an extension to your Sphinx project can overcome this limitation.

In this example case, the guilabel role will be extended to support displaying a single unicode character, like a symbol, to describe buttons or GUI items which show such symbols instead of regular text.

Create a new Python module in a proper location (e.g. ./source/_ext/).

First off, we need to register our role in our Sphinx application:

def setup(app):
    app.add_role("unicode-guilabel", unicode_guilabel_role)

Here, we are adding the role unicode-guilabel and assign it to the function unicode_guilabel_role.

Next, we need to define this role processor function. The docutils parser used by Sphinx converts the input text into tree, made up of different types of nodes. That tree is then parsed by a writer which creates the desired output format, hence your role processor function must return such a node, taking arguments describing the marked-up text:

from docutils import nodes

def unicode_guilabel_role(name, rawtext, text, lineno, inliner,
                          options={}, content=[]):
    """Returns a guilabel node including a single unicode html-code"""

    if not text.startswith("&#"):
        msg = inliner.reporter.error("Wrong Unicode! "
                                     "Must start with '&#'.", line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [msg], [prb]

    if not text.endswith(";"):
        msg = inliner.reporter.error("Wrong Unicode! "
                                     "Must end with ';'.", line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [msg], [prb]

    html = f"<span class=\"guilabel\">{text}</span>"
    node = nodes.raw("\n".join(content), html, format="html", **options)

    return [node], []

The arguments are expected in a certain order (see more under Creating reStructuredText Interpreted Text Roles). The return value is a tuple containing two lists. The first list contains any new nodes to be added to the parse tree, the second list contains error or warning messages to show the user (processors are defined to return errors instead of raising exceptions because the error messages can be inserted into the output instead of halting all processing.

In this example, the node itself is a raw data node, which passes the node data as is. The nodes text is an HTML expression, wrapping a passed in text within a guilabel span assigned to the guilabel class.

Lastly, your extension must be added to conf.py:

import sys
import os

sys.path.insert(0, os.path.abspath(".") + "/_ext")

extensions = [
# your other extensions
"unicode_guilabel"
]

You can now use the new role like this:

You can click the :unicode-guilabel:`&#5130;` and :unicode-guilabel:`&#5125;`
buttons to navigate.

translates into:

You can click the ᐊ and ᐅ buttons to navigate.

Full unicode_guilabel.py:

from docutils import nodes


def setup(app):
    app.add_role('unicode-guilabel', unicode_guilabel_role)


def unicode_guilabel_role(name, rawtext, text, lineno, inliner,
                          options={}, content=[]):
    """Returns a guilabel node including a single unicode html-code"""

    if not text.startswith("&#"):
        msg = inliner.reporter.error("Wrong Unicode! "
                                     "Must start with '&#'.", line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [msg], [prb]

    if not text.endswith(";"):
        msg = inliner.reporter.error("Wrong Unicode! "
                                     "Must end with ';'.", line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [msg], [prb]

    html = f"<span class=\"guilabel\">{text}</span>"
    node = nodes.raw("\n".join(content), html, format="html", **options)

    return [node], []

Warning

It depends on the font, the used Sphinx theme (for example sphinx-rtd-theme) uses, whether a symbols can be displayed. If the font does not include the symbol for a certain unicode character, it will be displayed as 🛈.

To overcome this limitation, the theme must use a backup font, that features most or all defined Unicode symbols. A possible candidate is the Symbola font.

An older version (v9.00) is provided by the Font Library as a web font. To add it follow these steps:

Hint

These steps proved to work for the sphinx-rtd-theme. If using a different theme, the layout.html or used Jinja blocks may be named differently.

  1. Inside the sphinx sources directory (for example source) create a new directory for HTML templates (here: _templates) and an empty template file (here: layout.html):

    $ cd source
    $ mkdir _templates
    $ touch _templates/layout.html
    

    If the directory is new, also add the templates to your conf.py to be considered when building the HTML:

    # Add any paths that contain templates here, relative to this directory.
    templates_path = ['_templates']
    
  2. Copy the include HTML code (from the website’s Use this font section) into layout.html and mark it as the block extension (here: extrabody), so it looks like this:

    {% extends "!layout.html" %}
    
    {% block extrabody %}
        <link rel="stylesheet" media="screen" href="https://fontlibrary.org//face/symbola" type="text/css"/>
    {% endblock %}
    
  3. Open the custom.css and add the following content:

    Hint

    If using a different theme, check the originally used font-family setting for the guilabel role first and attach SymbolaRegular as the last option.

    .guilabel {
        font-family: Lato,proxima-nova,Helvetica Neue,Arial,sans-serif,SymbolaRegular
    }
    
  4. Rebuild the documentation to see the new font being applied.

Hint

A download for Symbola (v7.21) is available at https://fonts2u.com/symbola.font.