readium / readium-css

🌈 A set of reference stylesheets for EPUB Reading Systems, starting with Readium Mobile
https://readium.org/readium-css/
BSD 3-Clause "New" or "Revised" License
90 stars 20 forks source link

Page margins implementation #2

Closed JayPanoz closed 6 years ago

JayPanoz commented 7 years ago

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

Line-length

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;
}

1col-pagemargins

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.

2col-pagemargins

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).

Pros

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.

Customizable

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.

fullwidth_1col-pagemargins

/* 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);
}

fullwidth_2col-pagemargins

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).

Cons

Logic

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.

Harmony

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

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.

JayPanoz commented 7 years ago

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.

JayPanoz commented 7 years ago

Issues I can currently report:

JayPanoz commented 7 years ago

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.

JayPanoz commented 6 years ago

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?

Notes

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.

JayPanoz commented 6 years ago

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                  |     |   |
|    |     |                  ↓                  |     |   ↓
|    |—————————————————————————————————————————————————|
↓    | ############################################### |
     ———————————————————————————————————————————————————
            <----------------------------------->
                           body

In which:

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.

JayPanoz commented 6 years ago

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