Layer of a Layout

Each layer can be specified in layout’s YAML file as a YAML list value for the layers mapping.

A simple layout of 2 layers
size: { width: 250, height: 250 }
layers:  # each layer in this list is denoted with a dash ('-')
  - # layer 0
    background:
      color: orange
  - # layer 1
    icon:
      image: sphinx_logo

A image generated by sphinx-social-cards

class Layer

Each layer can have different attributes. A typical layer has size and offset attributes with 1 additional attribute detailing a background or icon or typography or rectangle or ellipse. However, these attributes may combined as needed.

Each attribute has a priority, so using multiple attributes on a single layer will render consistently. The priority order is as follows (excluding size and offset):

  1. background

  2. rectangle

  3. ellipse

  4. polygon

  5. icon

  6. typography

  7. mask

Meaning, any background attribute is always rendered before other layer attributes. Additionally, any typography attribute is rendered after other attributes but before applying the mask.

A layout with a single layer that has multiple attributes
size: { width: 200, height: 200 }
layers:
  - typography: # the layer's typography attribute
      content: "S"
      align: center
    background: # the layer's background attribute
      color: '{{ layout.background_color | yaml }}'
    icon: # the layer's icon attribute
      image: '{{ layout.logo.image }}'

# NOTE that the order of layer attributes does not matter

A image generated by sphinx-social-cards

Error

Each layer can only have 1 of each type of attribute. For example you cannot use 2 background attributes in a single layer:

my-layout.yml
size: { width: 600, height: 250 }
layers:
  - background: { image: 'images/rainbow.png' }
    # The `background` attribute is overwritten by next line
    background: { color: '#ff000037' }

# NOTE: The layer's background attribute is composed solely by
# the last instance of the background attribute in the layer.

A image generated by sphinx-social-cards

background : Background | None

An optional Layer Background attribute.

typography : Typography | None

An optional Layer Typography Attribute.

rectangle : Rectangle | None

An optional Layer Rectangle attribute.

ellipse : Ellipse | None

An optional Layer Ellipse attribute.

polygon : Polygon | None

An optional Layer Polygon attribute.

icon : Icon | None

An optional Layer Icon Attribute.

size : Size | None

The layer size. Defaults to values inherited from the layout.size.

offset : Offset

The layer offset. Defaults to { x: 0, y: 0 }.

mask : Mask | None

An optional Layer Mask attribute.

Using Jinja Syntax within the Layout

Seealso

It is advised to read up on how to use Jinja syntax.

Conventionally, Jinja syntax uses characters that have actual meaning in YAML. To help streamline the combination of YAML and Jinja syntaxes, this extension uses a modified jinja syntax:

differences between Jinja syntax

Conventional

sphinx-social-cards

Line Statements

{% ... %}

#% ... %#

Expressions

{{ ... }}

'{{ ... }}' [1]

Comments

{# ... #}

## ... ##

Escaping Jinja Syntax

Use '{{ "'{{" }}' to escape a Jinja reference.

layers:
  - typography:
      content: "'{{ "'{{" }}' page.title }}"
      # renders as: "{{ page.title }}"

Layouts are Jinja Templates

A layout file is basically a Jinja template. So, layers can be generated dynamically using Jinja syntax.

Drawing 3 circles programmatically with Jinja
#% set diameter, width, height = (100, 600, 250) %#
size:
  width: '{{ width }}'
  height: '{{ height }}'
layers:
  - background: { color: '#0000007F' }
  #% for i in range(3) %#
  - ellipse:
      color: "#'{{ ('0' * i) + 'F' + ('0' * (2 - i)) }}'"
    size:
      width: '{{ diameter }}'
      height: '{{ diameter }}'
    offset:
      x: '{{ width / 6 * (i * 2 + 1) - (diameter / 2) }}'
      y: '{{ (height - diameter) / 2  }}'
  #% endfor %#

A image generated by sphinx-social-cards

Inheriting Layouts

Layouts can even inherit from other layouts! The Jinja documentation has an excellent explanation on Template Inheritance. The rest of this section builds upon that useful information, so please read that first.

As a quick example, the default layout is inherited by default/accent and default/inverted layouts. In those layouts, a Jinja block (#% block color_vals %#) is used to override the inherited color aliases.

#% block color_vals %#
bg_color: &bg_color '{{ layout.background_color | yaml }}'
fg_color: &fg_color '{{ layout.color | yaml }}'
#% endblock %#
#% extends "default.yml" %#
#% block color_vals %#
bg_color: &bg_color '{{ layout.accent | yaml }}'
fg_color: &fg_color '{{ layout.color | yaml }}'
#% endblock %#
#% extends "default.yml" %#
#% block color_vals %#
bg_color: &bg_color '{{ layout.color | yaml }}'
fg_color: &fg_color '{{ layout.background_color | yaml }}'
#% endblock %#

Note

The Jinja #% extends layout-file %# statement requires the layout file name to be in quotes. Additionally, if inheriting from a layout in a sub-directory of layouts, then use the relative path to the layout.

Inheriting from default/variant layout
#% extends "default/variant.yml" %#

Inheritance Tutorial

Let’s say you want to change the pre-designed opengraph layout into a dark themed version. This can easily be done by inheriting the opengraph layout in your new custom layout. Upon inspection, we will find 3 jinja blocks in the opengraph layout’s source:

  1. The font_colors block defines the colors used for fonts:

    opengraph.yml
    1
    2
    3
    4
    #% block font_colors %#
    project_desc_color: &project_desc_color 'rgb(88, 94, 99)'
    title_url_color: &title_url_color 'rgb(47, 54, 61)'
    #% endblock %#
    

    Important

    We only need to change the actual color values.

    Error

    Changing the repeated tag names will break the layout.

  2. The background block defines the layer that specifies the background:

    opengraph.yml
     9
    10
    11
    12
    13
    14
    15
    16
      #% block background -%#
      - background:
          linear_gradient:
            preset: PremiumWhite
            start: {}
            end: { x: 400, y: 210}
            spread: reflect
      #%- endblock %#
    
  3. The watermark_icon block defines the layer that specifies the sphinx logo in the bottom corner:

    opengraph.yml
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
        #% block watermark_icon -%#
        icon:
          image: >-
            #% if page.meta.icon -%#
            '{{ page.meta.icon }}'
            #%- else -%#
            sphinx_logo
            #%- endif %#
          color: rgb(132, 146, 175)
        #%- endblock %#
    

    Note

    We could of course change this image via the cards-icon. But, notice the color for the icon is hard-coded because the background color is also hard-coded.

What about the other colors and stuff?

The color and image of the logo is already optionally controlled via the cards_layout_options[logo]. Additionally, the color of the bottom stripe can already be controlled via the cards_layout_options[accent]. Any further changes would necessitate a completely new layout.

Now, we will create our custom layout file in the sphinx project’s source folder (adjacent to the conf.py file) - typically in a docs directory. Remember to add the directory path of this new layout to social_cards[cards_layout_dir] and social_cards[cards_layout] in the conf.py file. For this example, the new layout will be located in docs/social_cards/opengraph-dark.yml, so we add:

conf.py
social_cards = {
    "site_url": html_base_url,
    "description": "a project-wide description",
    "cards_layout_dir": "social_cards",
    "cards_layout": "opengraph-dark",
}

In the newly created opengraph-dark.yml file, we inherit the opengraph layout and make our customizations:

opengraph-dark.yml
#% extends "opengraph.yml" %#
#% block font_colors %#
project_desc_color: &project_desc_color 'rgb(239, 240, 241)'
title_url_color: &title_url_color 'rgb(222, 230, 237)'
#% endblock %#

# We need to keep the same indent as the inherited source,
# otherwise the YAML parser may get confused.

  #% block background -%#
  - background:
      linear_gradient:
        start: {}
        end: { x: 400, y: 210}
        spread: reflect
        colors:
          0.0: rgb(23, 23, 23)
          0.35: rgb(18, 18, 18)
          1.0: rgb(13, 13, 13)
  #%- endblock %#

    #% block watermark_icon -%#
    icon:
      image: sphinx_logo
      color: rgb(116, 116, 83)
    #%- endblock %#

A image generated by sphinx-social-cards

Referencing Jinja Contexts

Items of Jinja contexts can be referenced in the layout as Jinja variables:

Getting the page title from the page context.
layers:
  - typography:
      content: '{{ page.title }}' # (1)!
      color: '{{ undefined }}' # (2)!
  1. A YAML string that begins with a letter does not require extra surrounding quotes. Here, the single quotes are required for Jinja expressions, but they are removed by Jinja before the YAML is parsed.

  2. Any reference to an undefined Jinja context variable is automatically converted to the YAML null value. This can be useful for font colors because a null value will fallback to using the value of layout.color.

Conditionally referencing context string values with a multi-line YAML string.
layers:
  - icon:
      image: >- # (1)!
        #% if page.meta.icon %#
        '{{ page.meta.icon }}'
        #% else %#
        '{{ layout.logo.image }}'
        #% endif %#
  1. Here > signifies that the following indented block is a single string, but line breaks are replaced with spaces. With -, >- instructs the YAML parser to strip a trailing line break from the multi-line string.

The yaml Jinja filter (for referencing color values)

Seealso

Jinja comes with a bunch of documented builtin filters. General usage is briefly described in the Jinja Filters section.

Not all Jinja contexts hold a str or int value. For example, a color that was specified as a gradient will be in the form of a Python dict. If you want to support color gradients in the layout, then a custom filter (yaml) is added to the Jinja environment that parses the layout before the YAML parser reads the layout.

Referencing a color value from jinja context
layers:
  - background:
      color: '{{ layout.background_color | yaml }}' # (1)!
  1. A background_color specified as a gradient like so:

    social_cards = {
        "cards_layout_options": {
            "background_color": { # a linear gradient
                "start": {"x": 0, "y": 0 },
                "end": {"x": 1200, "y": 630},
                "preset": 84,  # aka "PhoenixStart"
            },
        },
    }
    

    will be converted to a proper YAML mapping with the yaml filter:

    color: { start: { x: 0, y: 0 }, end: { x: 1200, y: 630 }, preset: 84 }
    

Solid colors do not need the yaml filter

All Jinja Contexts that define a solid color are automatically translated to use the string form, rgb(<red>, <green>, <blue>) (or rgba(<red>, <green>, <blue>, <alpha>) if an alpha value/transparency was included).

Meaning, layout designers do not have to worry about colors specified with a beginning # which would be interpreted as a YAML comment (eg: "#0FF1CE" is translated to rgb(15, 241, 206)).

Warning

Do not use a multi-line YAML string to reference colors from Jinja contexts. Doing so will result in a quoted YAML value:

Do not do this!
layers:
  - background:
      color: >-
        #% if layout.logo.color %#
        '{{ layout.logo.color | yaml }}'
        #% else %#
        '{{ layout.background_color | yaml }}'
Do this instead
layers:
  - background:
      color: #% if layout.logo.color %#'{{ layout.logo.color | yaml }}'#% else %#'{{ layout.background_color | yaml }}'

Jinja Contexts

Every generated social card uses a set of Jinja contexts:

class Config

A dict whose items expose some configuration options in conf.py. The following items are included in this context:

theme : dict[str, Any]

A dict whose items correspond to the html_theme_options. This dict is very dependent on the choice of sphinx theme and what it defines in its theme.conf file.

site_description : str | None

The social_cards.description value.

site_url : str

The social_cards.site_url value. This value has the transport protocol (https://) automatically removed for convenience.

docstitle : str | None

The project value which is used as the site’s title.

author : str | None

The author value.

language : str | None

The full language name that corresponds to the language value

today : str | None

The today value. Defaults to current date using "<month> <day> <year>" format.

JinjaContexts.layout : Cards_Layout_Options

A dict whose items correspond to the cards_layout_options.

class Page

A dict whose items include the following:

meta : dict[str, str]

A dict whose items correspond to the page’s Metadata (or meta element(s) created via the meta directive).

title : str | None

The value of the title of the page for which the card is generated.

canonical_url : str

A URL of the current page relative to the site_url value.

is_homepage : bool

A bool value that indicates if the current page is the root of the site.

JinjaContexts.plugin : dict[str, Any]

A dict whose items correspond to compatible plugins‘ contexts.