Configuration¶
To use this extension, it must be added to your conf.py file’s extensions
option.
extension = [
# other extensions...
"sphinx_social_cards",
]
There are some required configuration values (and other optional values) that can be specified in
conf.py file as well. So, the social_cards
option also needs to be added to conf.py.
- class Social_Cards¶
A
dict
of configurations related to generating social media cards. Each attribute equates to a supported configuration option.Some options use another data class to validate its values, so be mindful of the documented attributes’ datatype.
social_cards = { "site_url": "https://2bndy5.github.io/sphinx-social-cards", "description": "Generate social media preview cards for your sphinx documentation.", }
- site_url : str¶
This required option will be the base URL that social media platforms use to fetch the social card’s image.
- description : str¶
This option will be used as the description metadata for all generated pages. It can be overridden for individual pages using the
description
metadata role.
- enable : bool¶
Set this option to
False
to disable automatic generation of social cards for each page. Thesocial-card
directive can be used to invoke or override social card generation for a specific page.
- cards_layout : str¶
The layout file’s name used to generate the social cards. If using a custom-made layout (not a pre-made layout), then the layout file must be a YAML file. If the the
cards_layout_dir
is specified, then that path is searched for a matching layout before searching the default path of pre-made layouts.This extension ships with some pre-made layouts for convenience.
Full
default
layout syntax#% block color_vals %# bg_color: &bg_color '{{ layout.background_color | yaml }}' fg_color: &fg_color '{{ layout.color | yaml }}' #% endblock %# layers: # the base layer for the background - background: color: *bg_color image: >- #% if layout.background_image -%# '{{ layout.background_image }}' #%- endif %# # the layer for the logo image - size: { width: 150, height: 150 } offset: { x: 990, y: 60 } icon: image: >- #% if page.meta.icon -%# '{{ page.meta.icon }}' #%- elif layout.logo.image -%# '{{ layout.logo.image }}' #%- endif %# color: '{{ layout.logo.color | yaml }}' # the layer for the site's name - offset: { x: 60, y: 60 } size: { width: 920, height: 60 } typography: content: '{{ config.docstitle }}' align: start center color: *fg_color # the layer for the page's title - size: { width: 920, height: 300 } offset: { x: 60, y: 150 } typography: content: >- #% if page.meta.title -%# '{{ page.meta.title }}' #%- elif page.title -%# '{{ page.title }}' #%- endif %# line: # height: 0.85 amount: 3 font: weight: 500 color: *fg_color # the layer for the site's (or page's) description - offset: { x: 60, y: 480 } size: { width: 1080, height: 90 } typography: content: >- #% if page.meta and page.meta.description -%# '{{ page.meta.description }}' #%- else -%# '{{ config.site_description }}' #%- endif %# line: height: 0.87 amount: 2 align: start bottom color: *fg_color
Full
default/accent
layout syntax#% extends "default.yml" %# #% block color_vals %# bg_color: &bg_color '{{ layout.accent | yaml }}' fg_color: &fg_color '{{ layout.color | yaml }}' #% endblock %#
Full
default/inverted
layout syntax#% extends "default.yml" %# #% block color_vals %# bg_color: &bg_color '{{ layout.color | yaml }}' fg_color: &fg_color '{{ layout.background_color | yaml }}' #% endblock %#
Full
default/variant
layout syntaxsize: { width: 1200, height: 630 } layers: # the base layer for the background - background: color: '{{ layout.background_color | yaml }}' image: '{{ layout.background_image }}' # the layer for an embedded icon in the background - size: { width: 570, height: 570 } offset: { x: 820, y: 30 } #% block background_icon -%# icon: image: >- #% if page.meta.icon -%# '{{ page.meta.icon }}' #%- elif layout.logo.image -%# '{{ layout.logo.image }}' #%- endif %# color: >- #% if layout.background_color in ('#000', '#000000', 'rgb(0, 0, 0)', 'hsl(0, 0%, 0%)', 'black') -%# #FFFFFF #%- else -%# #000000 #%- endif -%# 40 #%- endblock %# # the layer for the logo image - size: { width: 90, height: 90 } offset: { x: 60, y: 60 } icon: image: '{{ layout.logo.image }}' color: #% if layout.logo.color %#'{{ layout.logo.color | yaml }}'#% else %#null#% endif %# # the layer for the site's name - offset: { x: 180, y: 75 } size: { width: 960, height: 60 } typography: content: '{{ config.docstitle }}' line: height: 0.85 amount: 1 align: start center # the layer for the page's title - size: { width: 1080, height: 270 } offset: { x: 60, y: 180 } typography: content: >- #% if page.meta.title -%# '{{ page.meta.title }}' #%- elif page.title -%# '{{ page.title }}' #%- endif %# line: # height: 1.25 amount: 3 font: weight: 500 # the layer for the site's (or page's) description - offset: { x: 60, y: 480 } size: { width: 1080, height: 90 } typography: content: >- #% if page.meta.description -%# '{{ page.meta.description }}' #%- else -%# '{{ config.site_description }}' #%- endif %# line: height: 0.87 amount: 2 align: start bottom
Full
opengraph
layout syntax#% block font_colors %# project_desc_color: &project_desc_color 'rgb(88, 94, 99)' title_url_color: &title_url_color 'rgb(47, 54, 61)' #% endblock %# size: {width: 1200, height: 630} layers: # the base layer for the background #% block background -%# - background: linear_gradient: preset: PremiumWhite start: {} end: { x: 400, y: 210} spread: reflect #%- endblock %# # the layer for the logo image - size: { width: 150, height: 150 } offset: { x: 990, y: 60 } icon: image: >- #% if page.meta.icon -%# '{{ page.meta.icon }}' #%- elif layout.logo.image -%# '{{ layout.logo.image }}' #%- endif %# color: '{{ layout.logo.color | yaml }}' # the layer for the site's name - offset: { x: 60, y: 60 } size: { width: 920, height: 45 } typography: content: "'{{ config.docstitle }}'" line: # height: 0.8 amount: 1 color: *project_desc_color align: start center # the layer for page's title - size: { width: 920, height: 310 } offset: { x: 60, y: 120 } typography: content: >- #% if page.meta.title -%# '{{ page.meta.title }}' #%- elif page.title -%# '{{ page.title }}' #%- endif %# line: height: 1.25 amount: 4 color: *title_url_color font: weight: 500 # the layer for the site's (or page's) description - offset: { x: 60, y: 450 } size: { width: 1080, height: 60 } typography: content: >- #% if page.meta.description -%# '{{ page.meta.description }}' #%- else -%# '{{ config.site_description }}' #%- endif %# line: height: 1.05 amount: 2 align: start bottom color: *project_desc_color # the layer for the base URL - offset: { x: 60, y: 522 } size: { width: 1010, height: 38 } typography: content: '{{ config.site_url }}' color: *title_url_color font: weight: 700 align: start center # the layer for the watermark icon at the bottom-right - size: { width: 60, height: 60 } offset: { x: 1080, y: 520 } #% block watermark_icon -%# icon: image: >- #% if page.meta.icon -%# '{{ page.meta.icon }}' #%- else -%# sphinx_logo #%- endif %# color: rgb(132, 146, 175) #%- endblock %# # the layer for a colored bar at the bottom - size: { width: 1200, height: 30 } offset: { x: 0, y: 600} background: color: '{{ layout.accent | yaml }}'
Full
blog
layout syntax#% block metadata_color %# meta_color: &meta_color 'rgb(102, 102, 102)' #% endblock %# icon_size: &icon_size size: width: 40 height: 40 meta_info_size: &meta_info_size size: width: 160 height: 120 meta_info_lines: &meta_info_lines line: amount: 3 height: 1.1 avatar_size: &avatar_size size: width: 120 height: 120 size: { width: 1200, height: 630 } layers: # the layer for the background #% block background -%# - background: conical_gradient: preset: AboveTheSky center: { x: 600, y: 630 } angle: -90 #%- endblock %# # the layer for the bottom bar - size: { width: 1200, height: 30 } offset: { x: 0, y: 600 } background: color: '{{ layout.accent | yaml }}' # the layer for the title's left border - size: { width: 40, height: 360 } offset: { x: 40, y: 40 } rectangle: color: '{{ layout.accent | yaml }}' radius: 19 corners: ["top left", "bottom left"] mask: size: { width: 20, height: 360 } background: { color: white } # the layer for the page's title - size: { width: 1080, height: 300 } offset: { x: 80, y: 40 } typography: color: black content: >- #% if page.meta.title -%# '{{ page.meta.title }}' #%- elif page.title -%# '{{ page.title }}' #%- endif %# line: amount: 3 font: family: Roboto Slab weight: 500 # the layer for the list of hashtags' icon - offset: { x: 80, y: 350 } <<: *icon_size icon: image: >- #% if page.meta.tags -%# fontawesome/solid/tags #%- endif %# color: *meta_color # the layer for the list of hashtags - size: { width: 1030, height: 40 } offset: { x: 130, y: 350 } typography: color: *meta_color content: >- #% if page.meta.tags -%# #'{{ page.meta.tags.split(', ') | join(', #') }}' #%- endif %# line: amount: 1 height: 0.9 align: start center # the layer for the read-time's icon - offset: { x: 40, y: 450 } <<: *icon_size icon: image: >- #% if page.meta['read-time'] -%# fontawesome/solid/stopwatch #%- endif %# color: *meta_color # the layer for the read-time - offset: { x: 90, y: 450 } <<: *meta_info_size typography: color: *meta_color content: >- #% if page.meta['read-time'] -%# '{{ page.meta['read-time'] }}' #%- endif %# <<: *meta_info_lines # the layer for the date's icon - offset: { x: 260, y: 450 } <<: *icon_size icon: image: >- #% if page.meta.date or config.today -%# fontawesome/solid/calendar #%- endif %# color: *meta_color # the layer for the date - offset: { x: 310, y: 450 } <<: *meta_info_size typography: color: *meta_color content: >- #% if page.meta.date -%# '{{ page.meta.date }}' #%- elif config.today -%# '{{ config.today }}' #%- endif %# <<: *meta_info_lines # the layer for the language icon - offset: { x: 480, y: 450 } <<: *icon_size icon: image: >- #% if page.meta.language or config.language -%# fontawesome/solid/language #%- endif %# color: *meta_color # the layer for the language - offset: { x: 530, y: 450 } <<: *meta_info_size typography: color: *meta_color content: >- #% if page.meta.language -%# '{{ page.meta.language | title }}' #%- elif config.language -%# '{{ config.language | title }}' #%- endif %# <<: *meta_info_lines # the layer for the author's name - offset: { x: 700, y: 450 } size: { width: 330, height: 120 } typography: content: >- #% if page.meta.author -%# '{{ page.meta.author }}' #%- elif config.author -%# '{{ config.author }}' #%- endif %# line: amount: 2 height: 1.1 font: weight: 500 overflow: on color: black align: end center # the layer for the author's avatar - offset: { x: 1047, y: 457 } size: { width: 106, height: 106 } background: image: >- #% if page.meta.avatar -%# '{{ page.meta.avatar }}' #%- endif %# mask: ellipse: color: black size: { width: 106, height: 106 } # the layer for the avatar's border - offset: { x: 1040, y: 450 } size: { width: 120, height: 120 } ellipse: border: width: 8 color: #% if page.meta.avatar %#'{{ layout.accent | yaml }}'#% else %#null#% endif %#
- cards_layout_dir : List[Annotated[str | Path, AfterValidator(func=_validate_path)]]¶
The list of paths (absolute or relative to conf.py) where the
cards_layout
is located. In the case of similarly named layout files, the order in this list takes precedence.
- cards_layout_options : Cards_Layout_Options¶
A set (
dict
) of options that can be accessed via thelayout.*
jinja context. Seecards_layout_options
for more detail.
- cards_exclude : List[str] | Set[str]¶
This
list
can be used to exclude certain pages from generating social cards. Default is an emptylist
. Each item must be relative to the directory containing the conf.py file.Glob patterns
are supported, and file suffixes are only required when specifying an individual document source.social_cards = { "cards_exclude": [ "*-generated/*", # (1)! "changelog.rst", ] }
Use
**
to include all subdirectories
Note
This option does not affect the
social-card
directive.
- cards_include : List[str] | Set[str]¶
This
list
can be used to include certain pages fromcards_exclude
list
. Default is an emptylist
. Each item must be relative to the directory containing the conf.py file.Glob patterns
are supported, and file suffixes are only required when specifying an individual document source.social_cards = { "cards_include": [ "blog-posts/*", ] }
- image_paths : List[Annotated[str | Path, AfterValidator(func=_validate_path)]]¶
A list of directories that contain images to be used in the creation of social cards. By default, the path to the directory containing the conf.py file is automatically added to this list. Each entry in this list can be an absolute path or a path relative to the conf.py file.
This extension includes bundled SVG icons with distribution. The path to the bundled icons are appended to this list automatically.
- Bundled Icons:¶
name
Referenced in layouts using
Sphinx logo
sphinx_logo
material/<icon-name>
fontawesome/<brands|regular|solid>/<icon-name>
octicons/<icon-name>
simple/<icon-name>
- debug : Debug | bool¶
A field to specify layout debugging helpers. See Debugging Layouts for more detail.
- path : str¶
This option specifies where the generated social card images will be written to. It’s normally not necessary to change this option. Defaults to the documentation’s output in the subfolder ‘_static/social_cards’.
- cache_dir : str | Path¶
The directory (relative to the conf.py file) that is used to store cached data for generating the social cards. By default, this will create/use a directory named
"social_cards_cache"
located adjacent to the conf.py file.Caching Fonts
This path is also used to cache the downloaded fonts except for the distributed cache of Roboto font variants. If this path is checked into a git remote (such as repository hosted on GitHub), then the cache of fonts can be shared with project collaborators or reused in a Continuous Integration workflow.
# ignore cached images, but check in cached fonts doc/social_cards_cache/** !docs/social_cards_cache/fonts/*
Choosing the font¶
Fonts are fetched from Fontsource and cached in the cache_dir
folder. They can be specified in
multiple places. The order of precedence is:
Using the
cards_layout_options.font
which is specified in the conf.py file undersocial_cards.cards_layout_options
field.social_cards = { "cards_layout_options": { "font": { "family": "Roboto", } } }
The
typography.font
attribute allows for finite control over each layer of text when using Customized Layouts.layers: - typography: font: family: Roboto
Both approaches use the same font
specification.
Note
The Roboto fonts are cached and distributed with this extension.
If fonts cannot be fetched from Fontsource (and they are not already cached in the
cache_dir
or the distributed cache of Roboto font variants), then an exception will be
raised.
Weights over styles¶
Fontsource does not offer bold
or thin
(AKA narrow
) styles of fonts. Instead they
offer a multitude of weight
s. The rule of thumb is to to use
100
instead ofthin
400
instead ofregular
700
instead ofbold
Fonts from Fontsource typically have only normal
or italic
style
s. The weight
s
available vary per font family
. When a specified font does not provide the specified weight
,
then the closest available weight is used.
Variable and icon fonts are not supported
Since this extension uses pillow
to render fonts, the support for using pillow
with variable
fonts has not been implemented. This is because pillow
requires additional dependencies to
work with variable fonts.
Icon fonts, namely the material icon fonts from Google via Fontsource, are also not supported because they are designed to be used in a browser with CSS capability. Implementing icon fonts without CSS, would require using special string syntax in the yaml layout, and it just doesn’t seem worth it.
If someone is so inclined to add support for variable and icon fonts, then a Pull Request would be welcome.
Non-English languages¶
Some fonts will render boxes because they do not contain CJK characters, like for example the
default font
, Roboto
. If any text (eg. the project
name,
description
, or page title
) contain CJK characters, then choose another
font from Fontsource which comes with CJK characters.
social_cards = {
"cards_layout_options": {
"font": {
"family": "Noto Sans TC",
"subsets": "chinese-traditional",
}
}
}
social_cards = {
"cards_layout_options": {
"font": {
"family": "Noto Sans SC",
"subsets": "chinese-simplified",
}
}
}
social_cards = {
"cards_layout_options": {
"font": {
"family": "Noto Sans JP",
"subsets": "japanese",
}
}
}
social_cards = {
"cards_layout_options": {
"font": {
"family": "Noto Sans KR",
"subsets": "korean",
}
}
}
Choosing a color¶
All color fields related to this extension (via cards_layout_options
or in layer
attributes) can be specified as a Solid color syntax or a Gradient color specification.
Solid color syntax¶
- class Color¶
Represents a color.
A solid color can be specified in the following string forms:
-
social_cards = { "cards_layout_options": { "color": "Black", # NOTE case insensitivity "background_color": "azure", } }
layers: - background: color: Black # NOTE case insensitivity - ellipse: color: azure
-
social_cards = { "cards_layout_options": { "color": "#FFFFFF", # NOTE case insensitivity "background_color": "#FFffFF80", # 50% transparent } }
layers: - background: color: '#FFFFFF' # NOTE case insensitivity - ellipse: color: '#FFffFF80' # 50% transparent
-
social_cards = { "cards_layout_options": { "color": "rgb(255, 255, 255)", "background_color": "rgba(255, 255, 255, 0.5)" # 50% transparent, } }
layers: - background: color: 'rgb(255, 255, 255)' - ellipse: color: 'rgba(255, 255, 255, 0.5)' # 50% transparent
-
social_cards = { "cards_layout_options": { "color": "hsl(270, 60%, 70%)", "background_color": "hsl(270, 60%, 70%, .5)" # 50% transparent, } }
layers: - background: color: 'hsl(270, 60%, 70%)' - ellipse: color: 'hsl(270, 60%, 70%, .5)' # 50% transparent
Note
The alpha value (transparency) is ignored for
social_cards.debug.color
-
Gradient color specification¶
Being powered by Qt for Python, this extension supports rendering gradients of colors.
Complete details about using Gradient Colors is described separately for each supported gradient orientation:
Be sure to also check out the Preset Gradients for a complete list of conveniently
pre-defined list of colors
(with generated examples).
Metadata¶
Changing the title¶
By default all generated social cards’ title will use the page’s top-level heading, but it may be desirable to adapt the title to the page for which the social card represents. To do this, use one of the following options:
the title directive
.. title:: Some page-specific title
the meta directive
This option is more advanced as it allows direct manipulation of the resulting meta element(s). However, this tactic will ensure the metadata is present in the generated HTML despite whatever theme is used.
.. meta:: :title: Some page-specific title
Changing the description¶
By default all generated social cards’ description will use the description
,
but it may be desirable to adapt the description to the page for which the social card represents.
To do this, use one of the following options:
the meta directive
This option is more advanced as it allows direct manipulation of the resulting meta element(s). However, this tactic will ensure the metadata is present in the generated HTML despite whatever theme is used.
.. meta:: :description: Some page-specific description.
Changing the icon¶
By default all generated social cards’ icon will use the Cards_Layout_Options.logo
,
but it may be desirable to adapt the icon to the page for which the social card represents.
Changing the icon may not be consistent for different layouts.
Some layouts use the icon differently from the html_logo
(or
icon
[logo
] for the sphinx-immaterial theme).
When a layout uses an icon
is used instead of the logo
(as with the default
layout), then the color of the icon
is not altered.
To do this, use either the icon
or the card-icon
metadata field.
Blog front matter¶
Some of the front matter supported by the ABlog sphinx extension is used in the the pre-defined blog
cards_layout
:
Debugging Layouts¶
- class Debug¶
To ease creation of custom layouts, optional debugging glyphs can be
enable
d in the generated social card images.social_cards = { "description": "Generate social media cards for documentation pages with Sphinx", "site_url": "https://2bndy5.github.io/sphinx-social-cards/", "debug": True }
Each layer will have a boundary box drawn with some text to indicate the layer number (as ordered in the list of layout
layers
) and corresponding orientation.The text in the top-left corner indicates the layer number,
x
andy
.The text in the bottom-right corner indicates the layer number,
width
andheight
.