ebooks, Beta Testing,
and the Apocalypse

Slides are available at
https://jaypanoz.github.io/ebookcraft2017/

  • ebookcraft
  • Workshop
  • Twitter

Good developers are lazy.
That’s why I’ve implemented a ‘Tweet my Speech’ feature.

Workshop materials can be downloaded at
https://github.com/JayPanoz/ebookcraft2017/

Main themes

  • Community
  • Knowledge
  • Inspiration

This is probably my last ebook conf so I want it to be fun.

Fake cover for a fictionnal Oh Really? book entitled Breaking Reading Systems, the definitive guide, by Jiminy Panoz.
Blitz eBook Framework.

On side projects

Everything I know
I learned during side projects.

  • 2013. 100+ EPUB Templates (CSS)
  • 2015. Blogging + Building tools
  • 2016. Blitz ebook Framework

Current good practices are practices in need of a disruption.

Blitz Next project.

Bringing responsiveness to EPUB without media queries

It’s not just books

Let’s kickstart your side project!

Demos

Jen Simmons’ labs demoes a lot of new and shiny CSS specs (grid, flexbox, CSS regions, shapes, etc.). On the homepage, you can see a cover inspired by the Swiss style, implemented with CSS Grid.

Experiments

If you’re into comics…

  1. A Responsive Graphic Novel In Flexbox
  2. Dynamic Graphic Novel with clip-path
  3. Manga Panel Animated with HTML5 Canvas
  4. Making Comic Book Speech Bubbles with SVG
  5. Staggered and Fainting Text with SVG
  6. Comic Book FX Lettering with SVG Filters

A ton of knowledge.

Want more?

Libraries are bad, frameworks are evil! Real devs do without the pathetic tools we have!!! Real Web Developer

Frameworks and libraries deal with

  • Browser support
  • Design
  • Bugs and edge cases
  • Performance
  • User eXperience

They can make an impact

jQuery.

Just use jQuery. Stack Overflow

You might not need jQuery. The Modern Web
(youmightnotneedjquery.com)

Javascript improved
because jQuery proved
there was a better way to do it.

A ton of knowledge…

for the whole community.

The stronger the community,
the greater the knowledge,
the better the tooling.

That’s why I’m here today.

How does
EPUB rendering work?

Basic schema showing an EPUB file which is then unzipped and finally displayed. When the user interacts with settings, the EPUB file’s contents are somehow modified and displayed again.

Any sufficiently advanced technology is indistinguishable from magic. Arthur C. Clarke

Here is the process

  1. Constructing the DOM Tree
  2. Constructing the CSSOM Tree
  3. Running JavaScript
  4. Creating the Render Tree
  5. Generating the Layout
  6. Painting

We must get familiar with the critical rendering path.

The DOM Tree

An object representation of your document.

Nodes are created from elements and text.

The CSSOM Tree

An object representation of the styles associated with the DOM.

CSS is a render blocking resource:
the Render Tree cannot be constructed without first fully parsing the CSS.

Running JavaScript

Scripts in which you append contents, change styles, add eventListeners for interactions, etc.

JS is a parser blocking resource:
the parsing of the HTML document itself is blocked by JavaScript.

The Render Tree

A representation of the elements
to be rendered on the page.

DOM Tree + CSSOM Tree + JS alterations

Layout

The size of the viewport.

It provides a context for CSS styles
which are dependent on it.

Paint

Visible elements of your document
are converted to pixels.

(The size of the DOM and some styles
can slow painting down.)

Just go out and talk to a tree. Make friends with it. Bob Ross

This applies to modern EPUB apps…

We make a lot of detours, but we’re always heading for the same destination. Paulo Coelho

Detours

  1. JS resources (pagination, features, events…)
  2. CSS resources (UI, default CSS, settings…)

Parser and render blocking resources.

Think of an EPUB app as a scenic route: it adds detours to the rendering path.

Behind the scenes of iBooks

  • BKContentCleanup.js
  • BKContentTheme.js
  • CFI.js
  • user_stylesheet_flowable.css.tmpl
JavaScript
  1. The DOM is modified (BKContentCleanup.js)
  2. Reading mode is applied (BKContentTheme.js)
  3. CFI‘s eventListener is init’ed
    (location, highlight, pop-up footnotes, etc.)
CSS
  1. The “page” is built dynamically
    (with or w/o columns)
  2. Default styles are enforced
    (e.g. root, image sizing)
  3. Overrides are applied (even if they’re useless)

Reading Systems don’t override for the sake of it,
overrides solve issues.

.calibre > div, .calibre1 > div {
  position: static !important;
}
/* centering hack to support Calibre-generated vertical centered documents */

:root:not([__ibooks_has_multiple_pages]) 
*[__ibooks_centering_hack^="horizontal-"] {
  min-width: {{.pageWidth}}px;
}
:root:not([__ibooks_has_multiple_pages]) 
*[__ibooks_centering_hack^="vertical-"] {
  min-height: {{.pageHeight}}px;
}
/* fix documents that abuse inline-block */

:root[__ibooks_has_multiple_pages] 
*[__ibooks_has_inline_block] {
  display: block !important;
}
BTW, this is escalating
  • Kindle now applies font fixes during upload
    (font size, font family and text color)
  • Apple recommends positioning the text at the sentence or paragraph level, not at the word level
    (UX issues: selection + search)

CSS Hacks come with great responsibility.

Some “hacks” are utter abuse

/*  Scrollable frame solution for FXL
 1. In Object export, enter "subchapter" as epub:type
 2. Add this snippet to your CSS 
 */

div[*|type = "subchapter"] { 
  position: relative;  
}  
div[*|type = "subchapter"] > div {  
  overflow: auto;  
}  

Other stuff you should know

  • iBooks devs set a 2-second delay
  • Rendering is a heavy process, contents are cached aggressively
  • Fixed-layout is pre-rendered inaccurately

iBooks is all about perceived performance.

Your performance budget is pretty tight.
This impacts ebook design dramatically.

EPUB is a spectrum.
There are different ways to manipulate contents.

Blitz Design Checklist

One more thing…

It’s hardware that makes a machine fast. It’s software that makes a fast machine slow. Craig Bruce

An
#eprdctn
Story

QA

  1. File crashes iBooks on iPad Mini 2
  2. File doesn’t crash iBooks on iPad 2, iMac, MacBook Pro, MacBook Air, etc.
You’ve got a mail.

Have you heard about EPUB files crashing iBooks? Friend

QA (v2)

  1. Some files crash iBooks on Apple’s first gen Retina mobile devices
  2. Those files don’t crash iBooks on Apple’s other devices
As @eBookNoir would say… “Bring me rum!”

QA (v3)

  1. Heavy use of JS in EPUB files crash iBooks on Apple’s first gen Retina mobile devices
  2. Be a maniac, debug like you’ve never debugged before
  3. Your #eprdctn computer will bite you in the arse

Your #eprdctn computer
will bite you in the arse.

An incomplete #eprdctn gig

This gig includes a 2014 Mac Mini, an iPad Mini 2 (Retina) and a Honor 5x smartphone (for the brave).

The Honor 5X is one of the best lower-priced large-screen smartphones available. The Guardian

Devices’ Technical Specifications
Cores CPU RAM
Mac Mini 2 2.8Ghz 16GB
iPad Mini 2 2 1.3Ghz 1GB
Honor 5x 8 1.5Ghz 2GB

Your laptop is a filthy liar. Alex Russell
(Google Chrome software engineer)

The mobile issues we must pay attention to:

  1. The network
  2. Manufacturers are using cheap storage
  3. It’s really hard to dissipate the heat coming out of the chips
  4. Most of the cores are powered down most of the time (battery life)
You’ll probably need rum.

The network

That’s a non issue, right?

EPUB files are stored locally, right?

Loading a webpage is much more than shipping bytes down the wire. Once the browser has downloaded our page’s scripts it then has to parse, interpret and run them. Addy Osmani
(Staff Engineer at Google)

Cheap storage

Flash not really better than good old spinning metal.

Expect more or less the perf of the best HDDs.

Power

big.LITTLE

Low power cores are used aggressively,
High power cores are used infrequently.

JetStream JavaScript benchmark (complex web apps)
Score Blow
Mac Mini 174.68
iPad Mini 2 55.712 -68%
Honor 5x 20.796 -88%
JavaScript — get element by id (ops/sec)
Ops/sec Blow
Mac Mini 43,224,096
iPad Mini 2 15,405,748 -64%
Honor 5x 2,725,418 -94%

Your mobile device is
a filthy liar too. Benchy McBenchmark

JavaScript — get element by id (ops/sec)
Ops/sec Blow
Mac Mini 43,224,096
iPad Mini 2 15,405,748 -64%
Honor 5x (liar) 2,725,418 -94%
Honor 5x (honest) 1,749,414 -96%

Recent iPhones and iPads
will bite you in the arse too.

Torture numbers and they’ll confess to anything. Gregg Easterbrook

Real life EPUB + Honor 5x

  • Rendering takes at least 3 seconds in most apps
  • Turning a page takes 2 seconds in those apps
  • JS events fire with a huge lag in some apps
  • Some apps crash b/c more advanced scripts
  • ADE fails at displaying HD images in FXL

Mobile is Hostile

Devices’ Technical Specifications
Cores CPU RAM
Mac Mini 2 2.8Ghz 16GB
iPad Mini 2 2 1.3Ghz 1GB
Honor 5x 8 1.5Ghz 2GB
High-end eReader 1 1Ghz 512MB

EPUB is a spectrum.
There is no average,
there are extremes.

Blitz Performance Checklist

Useful links

I’m a killjoy.

Last year, during ebookcraft’s debate Laura Brady used expletives. I want Laura to use expletives again.

You’re gonna kiss the sun and taste the motherfuckin’ rainbow. Elmo

How to do progressive enhancement?

Progressive enhancement is
starting with a rock-solid foundation

Rock-solid foundation

  1. A well structured doc (HTML)
  2. Works if CSS is not supported
  3. Works if JS is not supported

Progressive enhancement is
starting with a rock-solid foundation
and then
adding enhancements to it.

Adding enhancements

  1. Modern CSS (flexbox, shapes, OTF…)
  2. JS Interactions (static to dynamic)
  3. Still works everywhere

EPUB is a spectrum.
Reading Systems provide
a range of capabilities.

CSS

@supports

Feature Queries
aka the best thing that
ever happened to EPUB

(Sorry @dauwhe)

@supports (property: value) {
  selector {
    property: value;
  }				
}
@supports not (prop1: value1) {
  selector {
    prop2: value2;
  }				
}
@supports (prop1: value1) or (prop3: value3) {
  selector {
    prop1: value1;
    prop3: value3;
  }				
}
@supports (prop4: value4) and (prop5: value5) {
  selector {
    prop4: value4;
    prop5: value5;
  }				
}

Drop Caps

Initial-letter is a new prop which allows folks to make beautiful drop caps, unlike the shitty ones we’re doing right now with CSS Hacks. Dave Cramer is Initial-letter’s master.
@supports (initial-letter: 3 2) {
  .first-para:first-letter {
    initial-letter: 3 2;
  }
}

Funky transforms

You can use transforms for grunge things, and it even works in Kindle if you prefix transform!
@supports (transform: rotate(1deg) translateX(-1%)) {
  .journal-titre .rotate-1 {
    display: block;
    transform: rotate(-1deg);
  }
  .journal-titre .rotate-2 {
    display: block;
    transform: rotate(1deg);
  }
  .journal-titre .rotate-3 {
    display: block;
    transform: rotate(-1deg) translateX(-2%);
  }
  .journal-titre .rotate-4 {
    display: block;
    transform: rotate(-3deg) translateY(-0.2em);
  }
}

Open Type Features

For some reason, Reading Systems don’t usually leverage Open Type Features. Sad!
  • But you can now use stylistic sets, true small capitals, fractions or oldstyle numerals. This will improve legibility. A lot.
  • And you can use tabular numerals too, especially in tables, even if tables are e-production’s bigger evil.
@supports (font-variant-caps: small-caps) {
  body {
    font-variant: oldstyle-nums proportional-nums;
  }
  .small-caps {
    font-variant-caps: small-caps;
  }
}

Shapes

  • Until today, when you floated an element, text wrapped the image’s bounding box.
  • By using shape-outside and clip-path, we can specify the shape the text should wrap.
element {
  float: left;
}
@supports (shape-outside: {value}) {
  element {
    shape-outside: {value};
    clip-path: {value};
  }
}

Calc()

  • Portrait-aspect-ratio images with a caption is kind of a nightmare in CSS. Don’t pretend it isn’t, you’re lying, you know it.
  • When the user increases font-size, you can’t guarantee the caption won’t be displayed on the next page. And it looks amateurish, admit it.
  • CSS’ calc function to the rescue!
  • It helps you specify the image should be an height based on the viewport size minus the caption’s height.
@supports (height: calc(98vh - 5em)) {
  img {
    width: auto;
    max-width: 100%;
    min-height: 300px;
    height: calc(98vh - 5em);
    max-height: 95%;
    object-fit: contain;
  }
}

A dynamic typescale, based on the viewport

h1 {
  font-size: calc(1em + 3vmin);
}
h2 {
  font-size: calc(1em + 2vmin);
} 
h3 {
  font-size: calc(1em + 1vmin);
}

In theory, it’s simple and beautiful, it solves a lot of issues, including user settings.

In practice, iBooks scales vmin as well…

Why the fuck did you do that! Jules

Float to full-width

.float {
  min-width: 40%;
  max-width: 100%;
  width: calc((30em - 100%) * 1000);
}

Flexbox

  • Flexbox brings vertical alignement in paginated environments.
  • This may come in handy for titlepages, for which margins are computed automatically to keep the vertical alignment you’ve set.
@supports (display: flex) {
  parent {
    min-height: 95vh;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
  }
}
  • By using flexbox, you can also specify grids.
  • Elements of the grid will resize and/or wrap automatically.
@supports (display: flex) {
  .grid {
    display: flex;
    flex-wrap: wrap;
    flex: 1 1 15em;
  }
  .grid li {
    page-break-inside: avoid;
  }
}
In theory, you can also use calc() with width, and min- and max-width as a range, to specify if an image should be floated or not based on the container’s width.

Blitz eBook* Tricks

* Sorry Laura, github’s URLs are case-sensitive.

JavaScript

if (feature) { /* do something */ }

(It’s like @supports but for JS)

Taking static resources and turning them into dynamic interactions.

Building optional features which can
improve the reading experience.

JavaScript can deal with the painful stuff…
like tables.

Lots of people think JavaScript
is evil when it comes to a11y.

The truth is, JavaScript can
improve a11y dramatically.

What’s happening here?

  1. A specific stylesheet for dyslexia is loaded
  2. Buttons + inputs are created and provide extras
  3. If speech synthesis is supported, it can be enabled/disabled via a checkbox
  4. If a mouse/trackpad is available, a reading rule is made available

Useful links

Progressive enhancements make books progressive.

Stop designing for the lowest common denominator,
that’s self-inflicted pain.

It looks like a lost cause but if we do things well who knows what could happen? Luis Enrique
(Coach of the FC Barcelona team which
defeated PSG 6–1 after losing 4–0)

Patterns and widgets a community creates and uses become de facto standards.

Vendors can’t ignore
de facto standards.

We’re currently letting them set and have complete control over those.

How to build
your own tools?

Knowledge is power. France is Bacon

It’s a little recipe I use in emergencies. Mac Gyver

Keep It Stupidly Simple

Leverage CSS and JS

Meet a11y.css

Alix’ homepage.

A project by frenchman Gaël Poupard.

(a11y.css website)

We don’t have oil,
but we have ideas. Valéry Giscard d’Estaing

It’s just a CSS file…

warning developers about possible accessibility risks and mistakes that exist in HTML code.

img[alt=""] {
  outline: 4px solid gold;
}
img[alt=""] ~::after {
  content: "Empty [alt] attribute. This is only okay for decorative images. Is it a decorative image?";
}

A ton of knowledge

References. References everywhere. (a Toy Story meme)

This will blow your mind

Meaningful alt text
Blah Blah Blah Blah

Building an iBooks Previewer with CSS

column-width

column-gap

Hint

html {
  -webkit-column-width: {{.pageWidth}}px;
  -webkit-column-gap: {{.gutter}}px;
  width: {{.width}}px;
  height: {{.pageHeight}}px;
}

Any volunteer?

(Come on, don’t be shy)

html.iBooksPreviewer-iPadLandscape {
  box-sizing: border-box;
  width: 1024px;
  height: 768px;
  margin: 0;
  padding: 88px 61px 61px 61px;
  overflow-x: auto;
  -webkit-column-width: 422px;
  -webkit-column-gap: 58px;
  column-width: 422px;
  column-gap: 58px;
  column-fill: auto;
}
.iBooksPreviewer-iPadLandscape > body {
  margin: 0 !important;
}
.iBooksPreviewer-day {
  background-color: #fafbfa !important;
  color: #000000 !important;
}
.iBooksPreviewer-sepia {
  -webkit-filter: sepia(100%) contrast(0.95);
  filter: sepia(100%) contrast(0.95);
}
.iBooksPreviewer-gray {
  background-color: #5a5a5c !important;
  color: #ffffff !important;
}
.iBooksPreviewer-night {
  background-color: #121212 !important;
  color: #b1b1b1 !important;
}
.iBooksPreviewer-gray a,
.iBooksPreviewer-night a {
  color: #55beff !important;
  -webkit-text-fill-color: #55beff !important;
}
.iBooksPreviewer-baselineHelper {
  background-image: -webkit-linear-gradient(top, rgba(122,122,122,0) 92%,rgba(122,122,122,0.6) 100%); 
  background-image: linear-gradient(to bottom, rgba(122,122,122,0) 92%,rgba(122,122,122,0.6) 100%);
  background-size: 100% 1.5em;
  background-position: top 0.25em left 0;
}

Building a console with JS

alert()

If my calculations are correct […] you’re gonna see some serious shit. Doc Brown

console.log()

Hint

The console already exists,
you have to plug it into the DOM.

Gentlepeople, start your Search engine

(The answer is on Stack Overflow)

var logger = document.getElementById('logger');

['log','warn','error'].forEach(function (verb) {
  console[verb] = (function (method, verb, log) {
    return function (text) {
      method(text);
      var msg = document.createElement('span');
      msg.classList.add(verb);
      msg.textContent = verb + ': ' + text;
      logger.appendChild(msg);
    };
  })(console[verb].bind(console), verb, logger);
});

Useful functions

function printDom() {
  var dom = document.documentElement.outerHTML;
  console.log(dom);
}
function getStyle(element, cssProp) {
  var styleLog, 
      cssValue = window.getComputedStyle(element, null).getPropertyValue(cssProp),
      tagName = element.tagName.toLowerCase();

  styleLog = tagName + " \{ " + cssProp + ": " + cssValue + "; \}";
  console.log(styleLog);
}
var customStyles = ["font-family", "font-size", "line-height"];

function getCustomStyles(element) {
  var customStylesLog,
      cssObj = window.getComputedStyle(element, null),
      tagName = element.tagName.toLowerCase();

  customStylesLog = tagName + " \{";
  for (var i = 0; i < customStyles.length; i++) { 
    var cssObjProp = customStyles[i];
    customStylesLog += "\n  " + cssObjProp + ": " + cssObj.getPropertyValue(cssObjProp) + ";";
  }
  customStylesLog += "\n\}";
  console.log(customStylesLog);
}

It’s simplistic, it’s not elegant, you could so much more, much more efficiently… It’s pure crap! Real Web Developer

Shit you build
will be a lot more useful than
elegant tools you don’t use.

You’ll acquire the necessary knowledge to understand the over-engineered tools you didn’t use.

First do it, then do it better.

Merci !

OMG I dit it! I survived @JiminyPan’s workshop!!!

God help us, we’re in the hands of engineers. Ian Malcom

Stuff I do

Appendices

Homework

Improve a11y.css for EPUB

  1. display warning if unsemantic elements are used
  2. display warning if there is no epub:type
  3. implement your own good practices

Improve iBooksPreviewer with JavaScript

  1. put contents of the HTML file into an iframe
  2. append the stylesheet when the script runs
  3. add controls and user settings

Create ePubPreviewer from iBooksPreviewer

  1. add other Reading Systems in Previewer’s CSS
  2. create and append a menu to pick a previewer via JS
  3. publish online and create a bookmarklet

Improve console.js

  1. create and append pre + its styles via JS
  2. create useful functions you’ll use regularly
  3. add an input to type and run functions from the ebook
  4. design a special debug mode in which tapping/clicking an element logs (some of) its styles

Benchmarks data

High-end eReader Technical Specifications
Cores CPU RAM
Kobo Aura One 1 1Ghz 512MB
Kindle Oasis 1 1Ghz 512MB
Nook Glowlight+ 1 1Ghz 512MB

Cheap storage

Read/Write Sequential (in MB/s)
Read Write
Mac Mini (FD) 715.5 430.7
Mac Mini (HDD) 104 103
iPad Mini 2 121 64.6
Honor 5x (int.) 125 49.49
Honor 5x (ext.) 128.58 46.96

Graphics

MotionMark (CSS animations, SVG, canvas)
Score Blow
Mac Mini 406.98
iPad Mini 2 64.16 -84%
Honor 5x 17.80 -96%

You might not want to use jQuery

jQuery versus Vanilla JS — get element by id (ops/sec)
jQuery JS Boost
Mac Mini 3,375,112 43,224,096 1181%
iPad Mini 2 1,237,461 15,405,748 1145%
Honor 5x 204,505 1,749,414 755%