Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Page margins implementation #2

JayPanoz opened this issue Jul 13, 2017 · 6 comments


Copy link

commented Jul 13, 2017

Currently, we’re relying on CSS for horizontal margins (left/right). Vertical margins (top/bottom) are managed at the web view (or iframe) level so that we don’t mess with the viewport height unit.

Source is available on the phase1-pagination branch.

This issue discusses the pros and cons of using this CSS implementation.

How it works


Because columns have a floor but no ceiling in this configuration i.e. they can grow to 100% of their container’s width, we have to limit line-length using max-width on body.

body {
  max-width: 35rem;

Which means we must have margin set to auto so that the page is centered.

body {
  max-width: 35rem;
  margin: 0 auto !important;


In pink, the column width, and in yellow, the elements nested in body. The margin: auto centers the page.

It is still unclear how we should manage max-width though, since some EPUB files may have one set by the CSS author i.e. do we !important it or not.

This max-width is set using a CSS variable.

:root {
  --RS__maxLineLength: 35rem;

Which means you could use px, ch, etc. as units. By using rem in combination with margin: auto, we simply leverage HTML’s default responsiveness: the bigger the root font-size, the smaller the left and right margins, which is something you would expect in terms of typography.

Page margins

Since margin: auto can be 0, we must set a minimal page margin by using padding i.e. internal margin, which impacts the box sizing. Consequently, we have:

body {
  max-width: 35rem;
  margin: 0 auto !important;
  box-sizing: border-box; /* We need it because padding is part of the box */
  padding: 0 20px !important;

Once again, this can be set with a variable.

:root {
  --RS__pageGutter: 20px;

It is set in pixels so that it doesn’t increase with font-size.


As you can see, there is no column-gap, everything is currently managed with padding.

Since body is laid out in columns, this padding will be applied to each column i.e. 2 × 20px in two-page spread. It will also add up to column-gap i.e. 2 × 20px + column-gap (this value is currently 0 as it wasn’t yet implemented in the scroll function).


Closer to the metal

We rely on the browser/web view to compute and lay out everything, avoiding possible rounding errors which might create extra bugs (due to JS’ inaccuracies you must take care of).

You don’t have to tweak values in real-time so it’s faster in theory. We’re trying to take advantage of columns’ logic there. Although they’re using a pseudo-algorithm and are missing some pieces (minmax() for columns’ width, like in CSS grid), they can do great things once they are tamed. But we must also make sure this approach won’t have side effects on CFI.

It is responsive by default i.e. “one font-size to rule the whole.” For instance, if font-size is too big for 2 columns then it will automatically switch to 1, max-width taking over to manage the line-length.


Although it’s responsive by default, you can still use CSS variables to force specific values if needed.

Easy fine-tuning

You can fine-tune padding with media queries, either by targeting devices or relative widths.

User settings

Switching from 1 to 2 columns is one line of CSS but you would have to enable/disable the setting in some conditions e.g. make it available only in landscape mode on mobile.

A custom margin user setting could be applied changing the value for padding. Thanks to box-sizing: border-box, it would also apply to the page in portrait mode.

You would get some flexibility with the font-size setting i.e. either keeping the same page size in all configs or making it responsive to the current font-size.

Extra design options for CSS authors

Should an implementer decide to use CSS variables in production, and since they are interpolated by the rendering engine itself, it could open up new design options for CSS authors. For instance, you could in theory do “full-width” images by using the RS__pageGutter variable.


/* Please note you’ll obviously need a fallback so the following should be put inside a feature query */

figure.full-width {
  position: relative;
  left: 0;
  left: calc(var(--RS__pageGutter) * -1);
  width: 100%;
  width: calc(100% + var(--RS__pageGutter) * 2);


It works in two-page spread too.

You could also expose that as a data-attribute (e.g. data-RS-figure="full-width") in order to manage it as you wish or set the --RS__pageGutter variable as the value of another variable so that you can manage it in different configurations, especially as you may have set a column-gap, which would have to be taken into account for left and width, and reset overflow to visible for the figure.

:root {
  --authorPrefix__figureGutter: var(--RS__pageGutter);

/* Two columns */
@media screen and (min-width: 60em) {
  :root {
    --authorPrefix__figureGutter: 0px !important;

We could also have full-width headers, blockquotes, preformated text, etc (padding would simply be --RS__pageGutter to align contents).

I guess it would be more reasonable to tag anything CSS Variables as progressive enhancement in such cases, so that implementers don’t have to polyfill for browsers which don’t support them—should they use CSS variables, obviously. But that is implementers’ call.

Authors willing to do such effects will do it anyway. Only will they have a margin/padding on every element but figures. This might irritate some readers because this margin could be set in em and reflow with font-size, or px and be too large on small screens, or doesn’t adapt to the margin user setting, etc.

If we could at least explore and discuss design solutions (and their issues), it would improve the current situation a little bit (authors so frustrated they don’t even want to explain their current problems).



The whole logic might not be easy to grasp at first, since columns’ logic might be weird when you’re not used to it. I must still document how columns work to make it clearer to people who are not familiar with it.

You must deal with large screens

On large screens, you either have to control the font-size (bigger default if desktop + large viewport) or the viewport itself (web view || iframe’s size).

Users might no be used to responsive margins

Users might not be used to responsiveness and could see the margin: auto as a bug and not a feature if you decide to use em for max-width (would be computed based on the body’s font-size) or change the font-size at the :root level.

The cascade

CSS authors could mess things up e.g. page margins, line-length, media queries, etc. (either by using super specific selectors to force their own values or by accident), which would defeat this CSS approach since you’ll have to add inline styles in the DOM to make sure your values apply.

By providing design options to authors, we could prevent nasty hacks. Since a lot of hacks are the result of current design limitations, managing some on our side—with much better control—could probably much their life easier, especially if they just have to add attributes in their markup (cf. pop-up footnotes). See issue #1.


You can not have equal left, center (gap) and right “margins”. Center (gap) will be at least double the outside margins since you can’t target even and odd columns to apply padding on the left or the right (:nth-column() doesn’t exist).

You could add the missing width for left and right margins to the web view or iframe though.

Two-page spread implies some complexity

You have to reset max-width when you have two columns (or else the gap might be huge), and manage it for larger font-sizes (user-setting).

It requires normalization

You must normalize font-size for :root, at any cost necessary but we can keep the authors’ value for body. In other words, we would have to make it clear authors should consider 1rem = 16px.

How do RS tend do do this?

No margin, no padding, column-width and column-gap are dynamically set in pixels depending on the viewport, and the RS sometimes switches to one column when font-size is too big for 2.

Consequently, a user setting for page margins would bring some complexity and some Reading Systems prefer not to implement it.

Please note we should at least have a minimal margin (5–8px) for body. Indeed, should some glyphs appear at the start or end of the line, their descendants/ascendants might be clipped in italic/script (e.g. “f”, “g”, “j”). See this SO issue + I can report iBooks has had this issue since they removed their 5-pixel margin-left and -right on body and some users have complained about it publicly.

What we need

  • Implementers’ and authors’ feedback to check whether this approach can be sustainable.
  • Samples from publishers/CSS authors because we’re walking on eggshells there.

Since some user settings heavily depends on this decision, it’d better be taken as soon as possible. “Default CSS” and “reading modes” phases shouldn’t be impacted at first sight but the sooner, the better.


This comment has been minimized.

Copy link
Collaborator Author

commented Jul 20, 2017

Important note to TestFlight users: Adobe’s page template currently breaks pagination.

If you have something like

<link href="../Misc/page-template.xpgt" rel="stylesheet" type="application/vnd.adobe-page-template+xml"/> 

in the head of the document, the book will be displayed in a scrollable view. We’ll fix this issue in an upcoming update.


This comment has been minimized.

Copy link
Collaborator Author

commented Jul 28, 2017

Issues I can currently report:

  • line-length (max-width) must be larger as it is currently an issue with several ebooks… or it might be easier to constrain the size of the webview/iframe, especially as this will be done on larger screens (can do it in CSS for the iframe BTW);
  • although I didn’t notice issues, some implementers may want to use column-gap instead of padding to have a clear separation of RS’ and authors’ styles. I must admit I tend to lean towards this preference because there are interoperability concerns (work as the authors expect it to) + I’d really prefer to have implementers on the same page there;
  • you can’t have equal margins on the left/right and center, which is kinda weird in the “modern design esthetic”, and you have to add the difference for the webview/iframe (there’s no other way);
  • page margins have an impact on user settings, so I can’t really design some user settings before we make a decision.

This comment has been minimized.

Copy link
Collaborator Author

commented Aug 1, 2017

So, more info as regards margins.

From ebpaj guide v 1.13:

RSs shall not independently add margins that affect the usable screen size in the body element. Similarly, RSs shall not arbitrarily push together designated margins, paddings, and blank lines in publication data.

Which is what we’re currently doing.

Maybe we should also tell them that (from “Items RS are expected to have in the future”)

When using the designation of background color to the entire page, there are RSs which work, RSs which do not work, and RSs which reflect only part of the designation, which show that were are not in an environment to use it safely.

would be a lot easier to manage at the RS Level if they didn’t require that RSs shall not independently add margins that affect the usable screen size in the body element? As-is, those two are kind of conflicting i.e. a “you can’t have your cake and eat it” combo of requirements/expectations since it adds so much complexity to respect both.


This comment has been minimized.

Copy link
Collaborator Author

commented Aug 31, 2017

Update: I’m currently designing advanced pagination + scroll + margins.

My main objective is finding a design system which is not Kafkaesque since we must deal with different viewports, paged/scrolled views, the number of columns, and user settings and that could bring a lot of complexity and abstraction.

I’m not saying the model I’ve been designing so far is perfect—and I must still test it extensively—, but well, it seems to me KISS-enough at the moment.

  1. We have a reference value for page margins, meant for smaller screens.
  2. We adjust this value for phablets, tablets, large screens, x-large screens so that we have a reference based on the screen estate available.
  3. User settings are a factor of the reference, hence a simple calc(page margins × factor) function.
  4. Since I’ve not tested this approach extensively yet, I can’t tell for sure I won’t have to add extra complexity, depending on the number of columns (auto/user).

Question is do you feel like this is too simplistic at first sight?


It seems to me having the same margins in paged and scroll views are the way to go for consistency. After all, contents displayed in a single-column paged-view can be seen as “a fragmented document scrolling on the opposite axis.”

Columns are currently responsive by default i.e. a column must be at least 30em and if the viewport allows, 2 columns will be displayed. This implies that if the user sets some larger font-size, the CSS will get back to 1 column to improve readability.

I’m wondering if this is the best option though, as we can more or less predict for which configurations 2 columns may be set → smartphones and tablets in landscape mode + larger screens. And they could be responsive to the user’s font-size anyway.

Finally, I’d much prefer we don’t target specific devices using media queries, as this is unmaintainable in the long term.


This comment has been minimized.

Copy link
Collaborator Author

commented Jan 24, 2018

For vertical writing, I had to temporarily set an extra left and right padding on :root so that text doesn’t run the entire length of the web view (edge to edge). To my knowledge, it doesn’t create edge cases but more complex tests are lacking.

So we have this for the kihon-hanmen (text frame):

                    Viewport / :root
↑    | ############################################### |
|    |_________________________________________________|
|    |     |                  ↑                  |     |   ↑
|    |     |                  M                  |     |   |
|    |     |                  ↓                  |     |   |
|    |     | - - - - - - - - - - - - - - - - - - |     |   V
S    |     |                  ↑                  |     |   I
C    |     |                  P                  |     |   E
R    |     |                  ↓                  |     |   W
E    | ←P→ |               TextBox               | ←P→ |   P
E    |     |                  ↑                  |     |   O
E    |     |                  P                  |     |   R
N    |     |                  ↓                  |     |   T
|    |     | - - - - - - - - - - - - - - - - - - |     |   |
|    |     |                  ↑                  |     |   |
|    |     |                  M                  |     |   |
|    |     |                  ↓                  |     |   ↓
|    |—————————————————————————————————————————————————|
↓    | ############################################### |

In which:

  • “P” stands for padding (--RS__pageGutter CSS variable);
  • ”M” stands for margin (auto so that the text box can be vertically centered based on --RS__maxLineLength);
  • “#” represents the margin set at the app level (outside of the web view).

So if we decide to manage this extra padding-left|right at the app level (outside the web view), it means all 4 sides would have some margin (and we probably can’t set the same margin-left|right for all devices in all orientations) so I’m more than willing to see if managing that using CSS can work.


This comment has been minimized.

Copy link
Collaborator Author

commented Feb 27, 2018

So this has been documented and should now be handled as “real” issues. Closing it.

@JayPanoz JayPanoz closed this Feb 27, 2018

@JayPanoz JayPanoz removed the beta-testing label Feb 27, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
None yet
1 participant
You can’t perform that action at this time.