[Implementersâ doc]
Some parts of Readium CSS might feel like CSS voodoo at first sight. The goal of this document is to clarify how those parts actually work.
Readium CSS ships with a minimum viable framework to typeset unstyled publications (ReadiumCSS-default.css
). Layout is managed by the âbaseâ styles, used for all publications.
This stylesheet is based on HTML Suggested Rendering. Consequently, it will work if the markup is semantic.
If youâre familiar with CSS preprocessors (LESS, SASS, Stylus, etc.), you already know how to use it. It indeed relies on variables and functions, which are available in vanilla CSS today.
Whatâs noteworthy is that you can customize the entire publication by setting CSS variables (a.k.a. custom properties) and either use AS-IS at runtime or compile as a static representation before runtime (PostCSS config coming soon).
The stylesheet will then use those variables defined at the :root
level.
Finally, a simplified version of the font-size
normalize is embedded in this stylesheet (see next section).
--RS__compFontFamily
The typeface for headings. The value can be another variable e.g. var(-RS__humanistTf)
.
--RS__codeFontFamily
The typeface for code snippets. The value can be another variable e.g. var(-RS__monospaceTf)
.
--RS__typeScale
The scale to be used for computing all elementsâ font-size
. Since those font sizes are computed dynamically, you can set a smaller type scale when the user sets one of the largest font sizes.
Possible values: 1 |
1.067 |
1.125 (suggested default) |
1.2 |
1.25 |
1.333 |
1.414 |
1.5 |
1.618 |
The suggested default will accomodate most configurations, from small to large screens. Please note it is different from the --USER__typeScale
suggested default, which was designed to accomodate the publisherâs styles.
--RS__baseFontSize
The default font-size
for body copy. It will serve as a reference for all related computations.
--RS__baseLineHeight
The default line-height
for all elements.
--RS__flowSpacing
The default vertical margins for HTML5 flow content e.g. pre
, figure
, blockquote
, etc.
--RS__paraSpacing
The default vertical margins for paragraphs.
--RS__paraIndent
The default text-indent
for paragraphs.
--RS__linkColor
The default color
for hyperlinks.
--RS__visitedColor
The default color
for visited hyperlinks.
--RS__primaryColor
An optional primary accentuation color
you could use for headings or any other element of your choice.
--RS__secondaryColor
An optional secondary accentuation color
you could use for any element of your choice.
The assumption is that at least basic semantic elementsâ font-size
can be interpolated to a type scale, with minimal variation (±1â2 pixels difference for each element) in comparison to authorsâ styles.
Weâre using the calc()
function to emulate the following:
h1 = base font-size Ă typeScale ^ 3 (power of 3)
h2 = base font-size Ă typeScale ^ 2 (power of 2)
h3 = base font-size Ă typeScale
h4 = base font-size
body = base font-size
In which typeScale
can be customized to match the actual scale used in the publication. There lies the flexibility of this normalize.
Getting the type scale is a one-two punch:
parseInt
of the computed font-size
for a heading and the base (division = result)*Math.pow(result, 1/n)
, in which n
is 3
for h1
(cubic root) and 2
for h2
(square root).* h3
stops here.
Then the font-size
for each heading and body copy element will be recomputed based on this type scale.
Of course this approach is limited, there is little we can do to account for ids, classes, etc.
But it can at least be used for themes, or an opt-in user setting.
Readium CSS automagically finds the ideal line-height
of the current font and font-size
in use if the author hasnât set an explicit value.
Weâre using the current algorithm in the calc()
function:
(1em + (2ex - 1ch) - ((1rem - 16px) * 0.1667)) * var(--RS__lineHeightCompensation))
Therefore, the line-height
is:
1em
= the size of the font-size
;2ex - 1ch
= 2 x-height - 1 character width (0
), in order to take the typefaceâs proportions into account e.g. if the font has a small x-height, leading will be more solid, and vice versa;1rem - 16px
= the current userâs font-size
minus the one at 100%
(base font-size
);0.1667
= a scale which has been defined from an optimal range (it is a magic number which has been retro-engineered from a set containing hundreds of samples);var(--RS__lineHeightCompensation)
is a factor (integer) to compensate a less than ideal line-height
, especially in non-Latin scripts, where fonts tend to be square-ish.This isnât a perfect solution though, and this algorithm may be revisited in the future. Indeed, caveats are:
width
anyway;font-size
is not 16px
(e.g. Kindle experimental browser, user setting a larger font-size
, etc.);0.1667
feels like a magic number, or at least a weird number as it was retrofitted after testing hundreds of typefaces;400%
);User settings are appended on load and wonât have any effect until a user-setting variable is set.
In order to do that, we must use âconditional selectors.â Problem is there is no if/else statements
in CSS so how do we achieve this?
:root[style*="--USER__variable"]
Attribute selectors with pseudo-regex.
As soon as you set a property and its required flag to the html
(or root) element, the user setting applies.
Whenever needed, weâre leveraging explicit inheritance (inherit
value) so that the :root
value can be passed throughout the DOM.
Weâre also relying on the :not()
pseudo-class to exclude some elements. Think of it as the following command prompt: âmake all elements in the DOM inherit the value, excepted this one and that one.â
Performance of those selectors should not necessarily be a major concern. Indeed, authorsâ stylesheets (weight, complexity, etc.) and expensive properties will have a much larger impact on performance.
The biggest issue with this is that it requires some time to get accustomed to, as it objectively feels like some kind of CSS trick borderline to CSS voodoo. It has proved reliable during testing though, and already deals with user settings issues other Reading Systems might have had troubles addressing so far. In addition, it automagically forces the necessary recalc some user settings absolutely require.
In this design model, themes can be a simple set of user settings. They consequently can be treated as objects you can parse to add properties to the html
(root) element, and stringify to save/cache if the user creates a custom theme. No extra stylesheet needed.