Phil's Musings notes from a peripatetic programmer

Recent Posts


css-polyfills for Books

“Why CSS instead of Javascript?””

Wouldn’t it be great if authors could customize their books without having them write (or run) arbitrary JavaScript? This post shows a way to do it.

Our Problem

Our books end up being published in various formats with various support for CSS.

We use Docbook for PDFs partly because we need to move content around (ie collating exercises at the end of a chapter, making an index) and XSLT provides a way to move XML around.

Unfortunately, this means 4 things:

  • developers need to learn XSLT
  • we must regression-test all of our books whenever we fix a bug or add a feature
  • CSS for the PDF is different for ePUB and online
  • numbering things like exercises is different in a PDF than online

Fortunately, there are a few W3C Drafts that help fill in some of the gaps: Generated Content for Paged Media and CSS3 Generated and Replaced Content Module.

Intersection of Some CSS Features:

Feature EPUB2 Browsers PrinceXML (PDF)
::before no yes yes
counter-increment: no yes yes
content: no partial yes
target-text() no no yes
page-break-*: no no yes
move-to: no no no
::outside::before no no no
:has() no no no

To replace Docbook and have one CSS file to style the various formats we need to support all of these features and note a few differences:

  • PDF is generated using a single large HTML file (CSS needs to operate on all chapters)
  • ePUB needs to be chunked into multiple HTML files (ideally using CSS page-break-*)
  • Online, a single HTML file can be viewed outside the context of a book

Browsers

Ideally, we would be able to get access to all of these unsupported selectors and rules using the CSS Document Object Model but browsers only expose the selectors and rules they understand.

The Solution

Enter CSS-Polyfills.

The project uses LessCSS and jQuery to parse the CSS file and “bake” the changes into the HTML.

With it you can do things that are not possible using CSS supported by browsers. For example, you can style an element based on children inside:

.note:has(> .title) { /* Give it a fancier border */ }

Or, you can automatically generate a glossary at the and of a chapter based on terms in the chapter:

.term > .definition { move-to: glossary-area; }
.chapter:after {
  content: pending(glossary-area);
}

You can even style links depending on the target:

a[href] {
  // Use x-target-is as a switch for which link text to use
  content: x-target-is(attr(href), 'figure')   'See Figure';
  content: x-target-is(attr(href), 'table')    'See Table';
  content: x-target-is(attr(href), '.example') 'See Example';
}

In another post, I’ll go over some of the “Freebies” that come out of this project like CSS Coverage and CSS+HTML Diffs for regression testing.

Bonuses

By parsing the CSS file and “baking” the styles into the HTML there are a few “freebies” that come out.

Easy CSS Coverage

As a free perk, you can easily generate Coverage data for your CSS files by transforming a HTML and CSS file from the commandline and filtering stderr.

HTML+CSS Diffs

To do regression tests on books we merely need to generate the “baked” HTML file twice, once with the old CSS and once with the new CSS (all the styles are “baked” into style="..." attributes). Then, a quick XSLT file can compare the two and generate a version of the page with <span> tags marking the places where styling changed.

See https://github.com/philschatz/css-diff.js for a package that does this.

Keep Reading...

HTML+CSS Diffs

CSS Diffs

Our textbooks are frequently hundreds of pages long and use a single CSS file, so making a CSS change can change content in unexpected places.

Again, CSS Polyfills and a little XSLT file makes this easy.

CSS-Diff takes an HTML and CSS file and produces an HTML file with all the styling “baked” in. Then, the provided XSLT file can compare 2 “baked” HTML files and inject <span> tags whenever the styles differ.

See the CSS-Diff project to run it from the commandline.

Keep Reading...

css-polyfills.js do more with CSS!

I often hear “CSS is meant to style and HTML should describe content”, but CSS alone is not enough to make textbooks.

Sometimes you need to:

  1. style an element based on what’s inside (notes containing a title)
  2. change link text based on the target (See Figure 2a vs See Table 4.3)
  3. move elements somewhere else in the book (answers to the back)

Well, now there is css-polyfills.js, based on some great CSS3 specs, Sizzle, and selector-set.

Simple Examples (from Bootstrap)

With css-polyfills.js you can do things that are not possible using CSS in browsers. Bootstrap’s Dismissable Alerts require adding a special class on the alert if it contains a close button. This can be accomplished without adding the alert-dismissable class by using :has:

.alert:has(> .close) { /* Styles for `.alert-dismissable` */ }

Bootstrap Modals and Input Groups require adding wrapper elements in order to style properly; with nested ::before and ::outside these are unnecessary.

You can construct the 2 additional elements (.modal, .modal-dialog) around .modal-content using the following CSS:

.modal-content                  { ... }
.modal-content::outside         { /* Styles for `.modal-dialog` */}
.modal-content::outside::outside{ /* Styles for `.modal` */ }

More Examples

You can even style links depending on the target:

a[href^="#"] {
  // Use target-is as a guard for which link text to use
  content: target-is(attr(href), 'figure')   'See Figure';
  content: target-is(attr(href), 'table')    'See Table';
  content: target-is(attr(href), '.example') 'See Example';
}

Or, you can move elements down the page (ie collate footnotes at the bottom of a wikipedia article):

.footnote { move-to: footnotes-area; }
#footnotes {
  content: pending(footnotes-area);
}

You can also create the linkable footnotes on wikipedia by keeping the references near the content and use CSS to create links and move the references to the bottom of the page (See Clickable Footnotes).

Of course, there’s much more that can be accomplished using css-polyfills.js; check the README.md for more details.

Keep Reading...

CSS Coverage for Javascript Unit Tests

There are many CSS coverage projects but none plug directly into JS unit tests, instrumenting at the same time as the JavaScript coverage.

Assuming you have BlanketJS and Mocha tests, just grab css-coverage.js and add the following into the test harness HTML file :

<link rel="stylesheet/coverage" href="path/to/file.less" />
<script src="css-coverage.js"></script>

Less.js + Mocha + BlanketJS

The coverage script uses the less.tree AST to parse all the selectors and the debugInfo data to get line numbers. It adds a testdone hook to Mocha that runs all the selectors on the page. Then, the line number and coverage counts are given to BlanketJS so LESS/CSS files are included in the coverage report.

Example Screenshot

CSS Coverage Demo

Check out the Mocha + BlanketJS demo and the github repo for more!

Other Features

  • Loading LESS files using RequireJS is supported with 0 additional work
  • Instrumented less files that import other files are automatically instrumented
Keep Reading...

Building an eBook using GitHub

At Connexions we build free textbooks. To do it we have roughly 4 services:

  • Published Repository (Published Books)
  • Authoring Repository (Drafts)
  • Generate exports (EPUB, PDF)
  • Website

As someone who uses GitHub and Travis daily, I wondered how much of this GitHub (and Travis) can do for us. It turns out, quite a bit. If each book is a repository whose contents is an Unzipped EPUB then amazing things are possible.

Book Publishing = GitHub Repo + GitHub Pages + “Download as Zip” + Service Hooks

  • the master branch is the published version of the book
  • tags are various editions of a book
  • gh-pages branch can be used as a book reader (Website)
  • “Download as Zip” is the EPUB version of the book
  • The editor can “save” via the GitHub API
  • Travis-CI can be used to generate a PDF (and “push” to AWS or some other place)

So, I took our editor and made it save EPUB files to GitHub and viola, a book editor using GitHub!

Keep Reading...