This section describes how styles defined in a style sheet are applied to document elements. Understanding how this works will help you when designing a custom style sheet.
rinohtype’s style sheets are heavily inspired by CSS, but add some additional functionality. Similar to CSS, rinohtype makes use of so-called selectors to select document elements in the document tree to style. Unlike CSS however, these selectors are not directly specified in a style sheet. Instead, all selectors are collected in a matcher where they are mapped to descriptive labels for the selected elements. A style sheet assigns style properties to these labels. Besides the usefulness of having these labels instead of the more cryptic selectors, a matcher can be reused by multiple style sheets, avoiding duplication.
This section currently assumes some Python or general object-oriented programming knowledge. A future update will move Python-specific details to another section, making things more accessible for non-programmers.
Flowable is a document element that is placed on a page. It is
usually a part of a document tree. Flowables at one level in a document tree
are rendered one below the other.
Here is schematic representation of an example document tree:
|- Section | |- Paragraph | \- Paragraph \- Section |- Paragraph |- List | |- ListItem | | |- Paragraph (item label; a number or bullet symbol) | | \- StaticGroupedFlowables (item body) | | \- Paragraph | \- ListItem | \- Paragraph | | \- StaticGroupedFlowables | | \- List | | |- ListItem | | | \- ... | | \- ... \- Paragraph
This represents a document consisting of two sections. The first section
contains two paragraphs. The second section contains a paragraph followed by
a list and another paragraph. All of the elements in this tree are instances of
List are subclasses of
GroupedFlowables; they group a number of flowables. In the case of
List, these are always of the
ListItem type. Each list item
contains an item number (ordered list) or a bullet symbol (unordered list) and
an item body. For simple lists, the item body is typically a single
Paragraph. The second list item contains a nested
Paragraph |- SingleStyledText('Text with ') |- MixedStyledText(style='emphasis') | |- SingleStyledText('multiple ') | \- MixedStyledText(style='strong') | |- SingleStyledText('nested ') | \- SingleStyledText('styles', style='small caps') \- SingleStyledText('.')
The visual representation of the words in this paragraph is determined by the applied style sheet. Read more about how this works in the next section.
The common superclass for flowable and inline elements is
which indicates that these elements can be styled using the style sheets.
Selectors in rinohtype select elements of a particular type. The class of a
document element serves as a selector for all instances of the class (and its
Paragraph class is a selector that matches all
paragraphs in the document, for example:
As with CSS selectors, elements can also be matched based on their context. For example, the following matches any paragraph that is a direct child of a list item or in other words, a list item label:
ListItem / Paragraph
Python’s ellipsis can be used to match any number of levels of elements in the document tree. The following selector matches paragraphs at any level inside a table cell:
TableCell / ... / Paragraph
To help avoid duplicating selector definitions, context selectors can reference
other selectors defined in the same matcher using
SelectorByName('definition term') / ... / Paragraph
Selectors can select all instances of
Styled subclasses. These
StyledText, but also
Elements of some of the latter classes only appear as children of other
flowables (such as
Similar to a HTML element’s class attribute,
Styled elements can
have an optional style attribute which can be used when constructing a
selector. This one selects all styled text elements with the emphasis style,
Styled.like() method can also match arbitrary attributes of
elements by passing them as keyword arguments. This can be used to do more
advanced things such as selecting the background objects on all odd rows of a
table, limited to the cells not spanning multiple rows:
TableCell.like(row_index=slice(0, None, 2), rowspan=1) / TableCellBackground
The argument passed as row_index is a slice object that is used for extended
indexing1. To make this work,
TableCell.row_index is an
object with a custom
__eq__() that allows comparison to a slice.
Rinohtype borrows CSS’s concept of specificity to determine the “winning” selector when multiple selectors match a given document element. Each part of a selector adds to the specificity of a selector. Roughly stated, the more specific selector will win. For example:
ListItem / Paragraph # specificity (0, 0, 0, 0, 2)
Paragraph # specificity (0, 0, 0, 0, 1)
since it matches two elements instead of just one.
Specificity is represented as a 5-tuple. The last four elements represent the number of location (currently not used), style, attribute and class matches. Here are some selectors along with their specificity:
StyledText.like('emphasis') # specificity (0, 0, 1, 0, 1) TableCell / ... / Paragraph # specificity (0, 0, 0, 0, 2) TableCell.like(row_index=2, rowspan=1) # specificity (0, 0, 0, 2, 1)
Specificity ordering is the same as tuple ordering, so (0, 0, 1, 0, 0) wins over (0, 0, 0, 5, 0) and (0, 0, 0, 0, 3) for example. Only when the number of style matches are equal, the attributes match count is compared and so on.
In practice, the class match count is dependent on the element being matched. If the class of the element exactly matches the selector, the right-most specificity value is increased by 2. If the element’s class is a subclass of the selector, it is only increased by 1.
The first element of the specificity tuple is the priority of the selector.
For most selectors, the priority will have the default value of 0. The priority
of a selector only needs to be set in some cases. For example, we want the
CodeBlock selector to match a
CodeBlock instance. However,
CodeBlock is a
Paragraph subclass, another selector
with a higher specificity will also match it:
CodeBlock # specificity (0, 0, 0, 0, 2) DefinitionList / Definition / Paragraph # specificity (0, 0, 0, 0, 3)
To make sure the
CodeBlock selector wins, we increase the priority of
CodeBlock selector by prepending it with a
+CodeBlock # specificity (1, 0, 0, 0, 2)
In general, you can use multiple
- signs to adjust the priority:
++CodeBlock # specificity (2, 0, 0, 0, 2) ---CodeBlock # specificity (-3, 0, 0, 0, 2)
At the most basic level, a
StyledMatcher is a dictionary that maps
labels to selectors:
matcher = StyledMatcher() ... matcher['emphasis'] = StyledText.like('emphasis') matcher['chapter'] = Section.like(level=1) matcher['list item number'] = ListItem / Paragraph matcher['nested line block'] = (GroupedFlowables.like('line block') / GroupedFlowables.like('line block')) ...
Rinohtype currently includes one matcher which defines labels for all common elements in documents:
from rinoh.stylesheets import matcher
styles = StyleSheet('IEEE', matcher=matcher) ... styles['strong'] = TextStyle(font_weight=BOLD) styles('emphasis', font_slant=ITALIC) styles('nested line block', margin_left=0.5*CM) ...
Styled has a
Style class associated with it. For
Paragraph, this is
ParagraphStyle. These style classes
determine which style attributes are accepted for the styled element. Because
the style class can automatically be determined from the selector, it is
possible to simply pass the style properties to the style sheet by calling the
StyleSheet instance as shown above.
A style sheet file contains a number of sections, denoted by a section title enclosed in square brackets. There are two special sections:
[STYLESHEET]describes global style sheet information (see
[VARIABLES]collects variables that can be referenced elsewhere in the style sheet
Other sections define the style for a document elements. The section titles
correspond to the labels associated with selectors in the
StyledMatcher. Each entry in a section sets a value for a style
attribute. The style for enumerated lists is defined like this, for example:
[enumerated list] margin_left=8pt space_above=5pt space_below=5pt ordered=true flowable_spacing=5pt number_format=NUMBER label_suffix=')'
Since this is an enumerated list, ordered is set to
and label_suffix are set to produce list items labels of the style 1),
2), …. Other entries control margins and spacing. See
for the full list of accepted style attributes.
It is possible to define styles which are not linked to a selector. These can be useful to collect common attributes in a base style for a set of style definitions. For example, the Sphinx style sheet defines the header_footer style to serve as a base for the header and footer styles:
[header_footer : Paragraph] base=default typeface=$(sans_typeface) font_size=10pt font_weight=BOLD indent_first=0pt tab_stops=50% CENTER, 100% RIGHT [header] base=header_footer padding_bottom=2pt border_bottom=$(thin_black_stroke) space_below=24pt [footer] base=header_footer padding_top=4pt border_top=$(thin_black_stroke) space_above=18pt
Because there is no selector associated with header_footer, the element type
needs to be specified manually. This is done by adding the name of the relevant
Styled subclass to the section name, using a colon (
separate it from the style name, optionally surrounded by spaces.
It is also possible to define new selectors directly in a style sheet file.
This allows making tweaks to an existing style sheet without having to create a
StyledMatcher. However, this should be used sparingly. If a great
number of custom selectors are required, it is better to create a new
The syntax for specifying a selector for a style is similar to that when
constructing selectors in a Python source code (see Matchers), but with a
number of important differences. A
Styled subclass name followed by
parentheses represents a simple class selector (without context). Arguments to
be passed to
Styled.like() can be included within the parentheses.
[special text : StyledText('special')] font_color=#FF00FF [accept button : InlineImage(filename='images/ok_button.png')] baseline=20%
Even if no arguments are passed to the class selector, it is important that the class name is followed by parentheses. If the parentheses are omitted, the selector is not registered with the matcher and the style can only be used as a base style for other style definitions (see Base Styles).
As in Python source code, context selectors are constructed using forward
/) and the ellipsis (
...). Another selector can be referenced
in a context selector by enclosing its name in single or double quotes.
[admonition title colon : Admonition / ... / StyledText('colon')] font_size=10pt [chapter title : LabeledFlowable('chapter title')] label_spacing=1cm align_baselines=false [chapter title number : 'chapter title' / Paragraph('number')] font_size=96pt text_align=right
Variables can be used for values that are used in multiple style definitions. This example declares a number of typefaces to allow easily replacing the fonts in a style sheet:
[VARIABLES] mono_typeface=TeX Gyre Cursor serif_typeface=TeX Gyre Pagella sans_typeface=Tex Gyre Heros thin_black_stroke=0.5pt,#000 blue=#20435c
It also defines the thin_black_stroke line style for use in table and frame styles, and a specific color labelled blue. These variables can be referenced in style definitions as follows:
[code block] typeface=$(mono_typeface) font_size=9pt text_align=LEFT indent_first=0 space_above=6pt space_below=4pt border=$(thin_black_stroke) padding_left=5pt padding_top=1pt padding_bottom=3pt
Another stylesheet can inherit (see below) from this one and easily replace fonts in the document by overriding the variables.
Style Attribute Resolution¶
The element styling system makes a distinction between text (inline) elements and flowables with respect to how attribute values are resolved.
Text elements by default inherit the properties from their parent. Take for
example the emphasis style definition from the example above. The value for
style properties other than font_slant (which is defined in the emphasis
style itself) will be looked up in the style definition corresponding to the
parent element, which can be either another
StyledText instance, or a
Paragraph. If the parent element is a
neither defines the style attribute, lookup proceeds recursively, moving up in
the document tree.
For flowables, there is no fall-back to the parent element’s style by default.
Style definitions for both types of elements accept a base attribute. When set, attribute lookup is first attempted in the referenced style before fallback to the parent element’s style or default style (described above). This can help avoid duplication of style information and the resulting maintenance difficulties. In the following example, the unnumbered heading level 1 style inherits all properties from heading level 1, overriding only the number_format attribute:
[heading level 1] typeface=$(sans_typeface) font_weight=BOLD font_size=16pt font_color=$(blue) line_spacing=SINGLE space_above=18pt space_below=12pt number_format=NUMBER label_suffix=' ' [unnumbered heading level 1] base=heading level 1 number_format=None
In addition to the names of other styles, the base attribute accepts two special values:
If a style attribute is not set for the style, lookup will continue in the next style matching the element (with a lower specificity). This is similar to how CSS works. You can use this together with the
+priority modifier to override some attributes for a set of elements that match different styles.
This enables fallback to the parent element’s style for flowables. Note that this requires that the current element type is the same or a subclass of the parent type, so it cannot be used for all styles.
When a value for a particular style attribute is set nowhere in the style
definition lookup hierarchy, its default value is returned. The default values
for all style properties are defined in the class definition for each of the
When rendering a document, rinohtype will create a style log. It is written to disk using the same base name as the output file, but with a .stylelog extension. The information logged in the style log is invaluable when developing a style sheet. It tells you which style maps to each element in the document.
The style log lists the document elements that have been rendered to each page as a tree. The corresponding location of each document element in the source document is displayed on the same line, if the frontend provides this information. This typically includes the filename, line number and possibly other information such as a node name.
For each document element, all matching styles are listed together with their specificity, ordered from high to low specificity. No styles are listed when there aren’t any selectors matching the element; the default attribute value is used (for flowables) or looked up in the parent element(s) (for text/inline elements). See attribute-resolution for specifics.
The winning style is indicated with a
> symbol in front of it. Styles that
are not defined in the style sheet or its base(s) are marked with an
Each style name is followed by the (file)name of the top-most stylesheet where
it is defined (within brackets) and the name of its base style (after
Here is an example excerpt from a style log:
... Paragraph('January 03, 2012', style='title page date') > (0,0,1,0,2) title page date [Sphinx] > DEFAULT (0,0,0,0,2) body [Sphinx] > default SingleStyledText('January 03, 2012') ---------------------------------- page 3 ---------------------------------- #### FootnoteContainer('footnotes') StaticGroupedFlowables() #### DownExpandingContainer('floats') StaticGroupedFlowables() #### ChainedContainer('column1') DocumentTree(id='restructuredtext-demonstration') Section(id='structural-elements') demo.txt:62 <section> > (0,0,0,1,4) content chapter [Sphinx] > chapter (0,0,0,1,2) chapter [Sphinx] > DEFAULT Heading('1 Structural Elements') demo.txt:62 <title> > (0,0,0,1,2) heading level 1 [Sphinx] > DEFAULT (0,0,0,0,2) other heading levels [Sphinx] > heading level 5 MixedStyledText('1 ') SingleStyledText('1') SingleStyledText(' ') SingleStyledText('Structural Elements') Paragraph('A paragraph.') demo.txt:64 <paragraph> > (0,0,0,0,2) body MixedStyledText('A paragraph.') SingleStyledText('A paragraph.') List(style='bulleted') demo.txt:124 <bullet_list> > (0,0,1,0,2) bulleted list [Sphinx] > enumerated list ListItem() None:None <list_item> x (0,0,1,0,4) bulleted list item ListItemLabel('•') > (0,0,1,0,6) bulleted list item label [Sphinx] > list item label (0,0,0,0,2) list item label [Sphinx] > default SingleStyledText('•') StaticGroupedFlowables() > (0,0,0,0,3) list item body [Sphinx] > DEFAULT Paragraph('A bullet list') demo.txt:124 <paragraph> > (0,0,0,0,5) list item paragraph [Sphinx] > default (0,0,0,0,2) body [Sphinx] > default MixedStyledText('A bullet list') MixedStyledText('A bullet list') SingleStyledText('A bullet list') List(style='bulleted') demo.txt:126 <bullet_list> > (0,0,1,0,5) nested bulleted list [Sphinx] > bulleted list (0,0,1,0,2) bulleted list [Sphinx] > enumerated list ListItem() None:None <list_item> x (0,0,1,0,4) bulleted list item ListItemLabel('•') > (0,0,1,0,6) bulleted list item label [Sphinx] > list item label (0,0,0,0,2) list item label [Sphinx] > default SingleStyledText('•') StaticGroupedFlowables() > (0,0,0,0,3) list item body [Sphinx] > DEFAULT ...
Indexing a list like this
lst[slice(0, None, 2)]is equivalent to