SVG Clip Path and Shapes. An interesting alternative

We’ll borrow a play from the SVG playbook for this post. We’ll use clip path to generate masks around an image to control what the user gets to see and what shapes the image takes without changing the image itself.

We’ll look at the process and then we’ll build an e-book with examples to test if this works with iBooks (and whatever other reader you want to test with) and how can we better leverage the feature in our reflowable projects.

CSS clip-path

CSS clip-path attribute is the star of the show either in CSS, via SVG or a mix of the two of them, it will clip the image and hide portions outside the clipping region (and therefore changing the image’s visible shape) without changing the image file.

Rather than figure out the coordinates for each point in the shape or polygon I’ll be working on I chose to use Clippy, a tool by Bennett Feely. It is certainly not the only one but it is certainly the easiest to use of those I’ve found. If you use Brackets you may want to look at the CSS Shapes Editor that’s available for the editor.

For this example I took a triangle and put it on its side, the same shape in the Demosthenes example but with a different image.

The code looks like this:

See the Pen Breaking The Box — Step 1 by Carlos Araya (@caraya) on CodePen.2039

SVG Clip path

All is well and good for browsers that support the CSS clip-path property whether prefixed or not. But what happens to older browsers? Fortunately for us support for SVG is wider than the support for CSS clip path.

SO we take a two-pronged approach, we create an SVG clip path element and then we reference the SVG from our CSS.

This bit looks like this:

See the Pen Breaking The Box — Step 2 by Carlos Araya (@caraya) on CodePen.2039

CSS shapes

I’ve discussed CSS shapes in other blog posts so I won’t cover it again here. But it’s important to keep this in mind as it’ll be what will pull the components together below.

Putting it all together

We have all the components we need. It’s time to put them together. We use shapte-outside to tel the CSS engine to put the content closer to masked shape of the image.

The final code looks like this:

See the Pen Breaking The Box — Step 3 by Carlos Araya (@caraya) on CodePen.2039

Moving it to ePub and the result thereof

I had initially targeted iBooks but even within the iBooks platform, the results are inconsistent. I’m working on trying to figure out if it’s a code issue or if the different versions of iBooks really are that inconsistent with each other.

iBooks for Mac (1.1.1 (600) running on OS X 10.10.3) produces no visible result. The image is not displayed at all.

iBooks for iOS in an iPad Air 2 produces a distorted image and not the sharp triangle like the one provided for the open web.

I’m researching if this is an issue with the way I’m using clip-path, the limitations for using SVG clip path inside an XHTML document, or just that it’s not supported.

If you want to help me test the epub I created (with the cover and title for Peter Pan) is available here

Links and credits

Idea from http://demosthenes.info/blog/1007/Combining-CSS-clip-path-and-Shapes-for-New-Layout-Possibilities

Image used in this post courtesy of Craig Deakin used under a Creative Commons attribution license

image is available in codepen

Trimming the CSS fat

Trimming the CSS fat

After reading India Amos Degristling the sausage: BBEdit 11 Edition I thought I’d share my tricks for making CSS files as small as possible. While I learned these tricks as a front end developer they apply equally to creating content for e-books.

One thing that has always stopped me from fully embracing frameworks is that they use an insane amount of code and it’s really difficult to trim until I’m done with a project and, at that time, I usually don’t want to have to look at CSS for a few weeks.

In researching front end techniques I’ve discovered a few techniques to make CSS development more bearable and to cut the size of our CSS files at the same time.

The main requirement for the chosen tools is that they have both a command line tool or a grunt/gulp build system plugin.

For tools like CodeKit, a Macintosh application, or Prepros, cross platform, they must support all the tool discussed.

Both of these task runners, and the plugins that run within them, depend on Node.js and NPM. They both must be installed on your system before any of the tools discussed will work.

SASS

SASS and related libraries require Ruby and the SASS gem. Ruby is installed in most (if not all) Linux and OS X systems.

To install SASS just do gem install sass

I’ve been a fan of SASS ever since I first read about it a few years ago. It allows you to build more complex structures that you can with pure CSS.

Part of the fat trimming is the use of variables and reducing the number of redundant selector rules that we write.

I have written about SASS and some of its features

I followed it up with this post about advanced features to make CSS more manageable.

Grunt/Gulp Build System

After a long time saying I didn’t need a build system but, the more tools and techniques I’ve discovered. the harder it gets to remember the command line tools you have to use to accomplish these tasks

Grunt is the first task runner I saw and the one I still work with. It works in discrete tasks. It is very configuration heavy; the Gruntfile.js configuration file is full of instructions for how to run each task.

In the example below we define our tasks, along with the options and settings for each, and finally we define a custom task that includes all the steps we want to take.


/*global module */
/*global require */
(function () {
  "use strict";
  module.exports = function (grunt) {
    // require it at the top and pass in the grunt instance
    // it will measure how long things take for performance
    //testing
    require("time-grunt")(grunt);

    // load-grunt will read the package file and automatically
    // load all our packages configured there.
    // Yay for laziness
    require("load-grunt-tasks")(grunt);

    grunt.initConfig({
      // SASS RELATED TASKS
      // Converts all the files under scss/ ending with .scss
      // into the equivalent css file on the css/ directory
      sass: {
        dev: {
          options: {
            style: "expanded"
          },
          files: [{
            expand: true,
            cwd: "scss",
            src: ["*.scss"],
            dest: "css",
            ext: ".css"
          }]
        },
        production: {
          options: {
            style: "compact"
          },
          files: [{
            expand: true,
            cwd: "scss",
            src: ["*.scss"],
            dest: "css",
            ext: ".css"
          }]
        }
      },
     scsslint: {
        allFiles: [
          "scss/*.scss",
          "scss/modules/_mixins.scss",
          "scss/modules/_variables.scss",
          "scss/partials/*.scss"
        ],
        options: {
          config: ".scss-lint.yml",
          force: true,
          colorizeOutput: true
        }
      },

      autoprefixer: {
        options: {
          browsers: ["last 2"]
        },

        files: {
          expand: true,
          flatten: true,
          src: "scss/*.scss",
          dest: "css/"
        }
      },

      // CSS TASKS TO RUN AFTER CONVERSION
      // Cleans the CSS based on what"s used in the specified files
      // See https://github.com/addyosmani/grunt-uncss for more
      // information
      uncss: {
        dist: {
          files: {
            "css/tidy.css": ["*.html", "!docs.html"]
          }
        }
      }
    }); // closes initConfig

    // CUSTOM TASKS
    // Usually a combination of one or more tasks defined abov

    // Prep CSS starting with SASS, autoprefix et. al
    grunt.task.registerTask(
      "prep-css",
      [
        "scsslint",
        "sass:dev",
        "autoprefixer",
        "uncss"
      ]
    );
  }; // closes module.exports
}()); // closes the use strict function

Gulp is a stream oriented task runner where the emphasis is connecting (piping) the output one task to the input of the next. In the example below we create a task and then pipe the different plugins as input until the last pipe is for the destination of the product.


var cssc   = require("gulp-css-condense"),
    csso   = require("gulp-csso"),
    more   = require("gulp-more-css"),
    shrink = require("gulp-cssshrink");

gulp.task("styles", function () {
    return sass("./styles", {
        loadPath: "./vendor/bootstrap-sass/assets/stylesheets"
    }).on("error", console.warn.bind(console, chalk.red("Sass Errorn")))
        .pipe(autoprefixer())
        .pipe(combinemq())
        .pipe(cssc())
        .pipe(csso())
        .pipe(more())
        .pipe(shrink())
        .pipe(gulp.dest("./build/css"));
});

Combine Media Queries

The first optimization is to consolidate our Media Queries using Combine MQ. The idea behind this is to reduce the number of Media Queries and their associated rules.

We do this reduction first to make sure that we won’t have to run Autoprefixer and UnCSS again after reducing the number of Media Queries in our final CSS file.

There are Grunt and Gulp plugins available

AutoPrefixer

Autoprefixer helps in dealing with ‘prefix hell’ for the most part.

In their race to be first to implement a css feature, vendors added it behind a vendor-specific prefix (-webkit for Safari, Chrome and Opera, -o for Opera before they adopted Webkit, -moz for Firefox and -ms for Microsoft) to hide it for the browsers that had not adopted it or implemented differently.

Note that Autoprefixer does not handle ePub specific vendor prefixes. There are PostCSS tools that will do it for you when/if needed.

I’ve chosen not to implement these postcss plugins

That left developers having to figure out which elements had which vendor prefixes and to update them when/if the vendor finally decided to drop the prefix altogether.

Autoprefixer is a command line tool that will take care of vendor prefixes. It uses caniuse.com to determine what prefixes to apply to which element.

You can also specify how far back to go for prefixes. Examples of valid browser values:

  • last 2 versions: the last 2 versions for each major browser.
  • last 2 Chrome versions: the last 2 versions of Chrome browser.
  • > 5%: versions selected by global usage statistics.
  • > 5% in US: uses USA usage statistics. It accepts [two-letter country code].
  • Firefox > 20: versions of Firefox newer than 20.
  • Firefox >= 20: versions of Firefox newer than or equal to 20.
  • Firefox < 20: versions of Firefox less than 20.
  • Firefox <= 20: versions of Firefox less than or equal to 20.
  • Firefox ESR: the latest [Firefox ESR] version.
  • iOS 7: the iOS browser version 7 directly.

You can also target browsers by name:

  • Android for Android WebView.
  • BlackBerry or bb for Blackberry browser.
  • Chrome for Google Chrome.
  • Firefox or ff for Mozilla Firefox.
  • Explorer or ie for Internet Explorer.
  • iOS or ios_saf for iOS Safari.
  • Opera for Opera.
  • Safari for desktop Safari.
  • OperaMobile or op_mob for Opera Mobile.
  • OperaMini or op_mini for Opera Mini.
  • ChromeAndroid or and_chr for Chrome for Android (mostly same as common Chrome).
  • FirefoxAndroid or and_ff for Firefox for Android.
  • ExplorerMobile or ie_mob for Internet Explorer Mobile.

Autoprefixer is available as a command line tool, a Grunt Plugin and a Gulp Plugin

UnCSS

User-interface libraries like Bootstrap, TopCoat and so on are fairly prolific, however many developers use less than 10% of the CSS they provide (when opting for the full build, which most do). As a result, they can end up with fairly bloated stylesheets which can significantly increase page load time and affect performance. grunt-uncss is an attempt to help with by generating a CSS file containing only the CSS used in your project, based on selector testing.

From Grunt UnCSS

Uncss takes a set of HTML files, a css stylesheet and produces a new stylesheet with only those rules actually used in the HTML files. The idea is to reduce the size of the CSS being pushed to the client.

Shrinking the size of our CSS file(s) may not seem like a big deal but it becomes important when you use large libraries like Bootstrap or Zurb Foundation or when your own CSS libraries become too large to handle (special cases can be killers.)

Addy Osmani, the creator and maintainer claims that he has reduced the CSS size on a multi page Bootstrap project from over 120KB to 11KB.

UnCSS Size Reduction

There are UnCSS plugins for Grunt and Gulp available

CSSO or other minimizers

Now that we have a prefixed CSS file with only the classes we need we can look at further size reduction by doing optional compressions. I’ve chose to be somehwat conservative and choose two possible options of the many minimizers available through NPM and Grunt.

If you want to see a more detailed comparison check sysmagazine comparison of CSS and Javascript processors

CSS Optimizer

We will first run the CSS file through CSS Optimizer. What brought this plugin to my attention is that it not only does the traditional minimizations. According to the documentation it can perform:

  • Safe transformations:
    • Removal of whitespace
    • Removal of trailing ;
    • Removal of comments
    • Removal of invalid @charset and @import declarations
    • Minification of color properties
    • Minification of 0
    • Minification of multi-line strings
    • Minification of the font-weight property
  • Structural optimizations:
    • Merging blocks with identical selectors
    • Merging blocks with identical properties
    • Removal of overridden properties
    • Removal of overridden shorthand properties
    • Removal of repeating selectors
    • Partial merging of blocks
    • Partial splitting of blocks
    • Removal of empty ruleset and at-rule
    • Minification of margin and padding properties

As with the other tools, there are Grunt and
Gulp plugins available.

CSS Shrink

While CSSO may have gotten as small as possible, I’d rather make sure. That’s where CSS Shrink comes in.

You may be wondering why is Carlos being so obsessive with reducing the size of his CSS files?

Fair question. Here’s the answer:

Images are loaded asynchronously. We can load JavaScript asynchronously if we so choose. CSS is the only component of your web page that only loads synchronously and most browsers will block rendering the page until all your CSS downloads. That’s why it pays for it to be the smallest we can make it and combined the CSS into as few files as possible.

CSS Shrink provides that second level of compression, just to make sure we didn’t miss anything 🙂

As usual, plugins for Grunt and Gulp are available.

CodeKit/Prepros: One tool to rule them all

I know that some developers would rather not use command line tools. There are applications that provide almost equivalent functionality.

CodeKit (Mac only) and Prepros (Mac and Windows)

The screenshot below shows Codekit’s UI with a project open

Codekit Project UI

The second screenshopt shows SCSS compilation options on the right side of the screen.

Codekit Compilation Options

I own a copy of Codekit more from curiosity than from any actual use but realize that it may be better for developers who are not comfortable with command line interfaces.

Code Repository and Additional Goodies

I’ve created a Github Repository to go along with the ideas in this article. It’s a drop-in structure for a new project and it’s also an opinionated skeleton for new projects.

Issues, comments and Pull Requests are always welcome

XML Workflows: Tools and Automation

Because we use XML we can’t just dump our code in the browser or the PDF viewer and expect it to appear just like HTML content.

We need to prepare our content for conversion to PDF before we can view it. There are also front-end web development best practices to follow.

This chapter will discuss tools to accomplish both tasks from one build file.

What software we need

For this to work you need the following software installed:

  • Java (version 1.7 or later)
  • Node.js (0.10.35 or later)

Once you have java installed, you can install the following Java packages

  • Saxon (9.0.6.4 for Java)

A note about Saxon: OxygenXML comes with a version of Saxon Enterprise Edition. We’ll use a different version to make it easier to use outside the editor.

Node packages are handled through NPM, the Node Package Manager. On the Node side we need at least the grunt-cli package installed globally. TO do so we use this command:

$ npm install -g grunt-cli

The -g flag will install this globally, as opposed to installing it in the project director.

Now that we have the required sotfware installed we can move ahead and create our configuration files.

Optional: Ruby, SCSS-Lint and SASS

The only external dependencies you need to worry about are Ruby, SCSS-Lint and SASS. Ruby comes installe in most (if not all) Macintosh and Linux systems; an installer for Windows is also available.

SASS (syntactically awesome style sheets) are a superset of CSS that brings to the table enhancements to CSS that make life easier for designers and the people who have to create the stylesheets. I’ve taken advantage of these features to simplify my stylesheets and to save myself from repetitive and tedious tasks.

SASS, the main tool, is written in Ruby and is available as a Ruby Gem.

To install SASS, open a terminal/command window and type:

$ gem install sass

If you get an error, you probably need to install the gem as an administrator. Try the following command

$ sudo gem install sass

and enter your password when prompted.

SCSS-Lint is a linter for the SCSS flavor of SASS. As with other linters it will detect errors and potential erors in your SCSS style sheets. As with SASS, SCSSLint is a Ruby Gem that can be installed with the following command:

$ sudo gem install scss-lint

The same caveat about errors and installing as an administrator apply.

Ruby, SCSS-Lint and SASS are only necessary if you plan to change the SCSS/SASS files. If you don’t you can skip the Ruby install and work directly with the CSS files

If you want to peek at the SASS source look at the files under the scss directory.

Installing Node packages

Grunt is a Node.js based task runner. It’s a declarative version of Make and similar tools in other languages. Since Grunt and it’s associated plugins are Node Packages we need to configure Node.

At the root of the project there’s a package.json file where all the files necessary for the project have already been configured. All that is left is to run the install command.

npm install

This will install all the packages indicated in configuration file and all their dependencies; go get a cup of coffee as this may take a while in slower machines.

As it installs the software it’ll display a list of what it installed and when it’s done you’ll have all the packages.

The final step of the node installation is to run bower, a front end package manager. It is not configured by default but you can use it to manage packages such as jQuery, Highlight.JS, Polymer web components and others.

Grunt & Front End Development best practices

While developing the XML and XSL for this project, I decided that it was also a good chance to test front end development tools and best practices for styling and general front end development.

One of the best known tools for front end development is Grunt. It is a Javascript task runner and it can do pretty much whatever you need to do in your development environment. The fact that Grunt is written in Javascript saves developers from having to learn another language for task management.

Grunt has its own configuration file (Gruntfile.js) one of which is provided as a model for the project.

As currently written the Grunt file provides the following functionality in the assigned tasks. Please note that the tasks with an asterisk have subtasks to perform specific functions. We will discuss the subtasks as we look at each portion of the file and its purpose.

      autoprefixer  Prefix CSS files. *
             clean  Clean files and folders. *
            coffee  Compile CoffeeScript files into JavaScript *
              copy  Copy files. *
            jshint  Validate files with JSHint. *
              sass  Compile Sass to CSS *
            uglify  Minify files with UglifyJS. *
             watch  Run predefined tasks whenever watched files change.
          gh-pages  Publish to gh-pages. *
    gh-pages-clean  Clean cache dir
             mkdir  Make directories. *
          scsslint  Validate `.scss` files with `scss-lint`. *
             shell  Run shell commands *
              sftp  Copy files to a (remote) machine running an SSH daemon. *
           sshexec  Executes a shell command on a remote machine *
             uncss  Remove unused CSS *
              lint  Alias for "jshint" task.
          lint-all  Alias for "scsslint", "jshint" tasks.
          prep-css  Alias for "scsslint", "sass:dev", "autoprefixer" tasks.
           prep-js  Alias for "jshint", "uglify" tasks.
      generate-pdf  Alias for "shell:single", "shell:prince" tasks.
 generate-pdf-scss  Alias for "scsslint", "sass:dev", "shell:single",
                    "shell:prince" tasks.
      generate-all  Alias for "shell" task.

The first thing we do is declare two variables (module and require) as global for JSLint and JSHint. Otherwise we’ll get errors and it’s not essential to declare them before they are used.

We then wrap the Gruntfile with a self executing function as a deffensive coding strategy.

When concatenating Javascript files there may be some that use strict Javascript and some that don’t; With Javascript variable hoisting the use stric declaration would be placed at the very top of the concatenated file making all the scripts underneat use the strict declaration.

The function wrap prevents this by making the use strict declaration local to the file where it was written. None of the other templates will be affected and they will still execute from the master stylesheet. It’s not essential for Grunt drivers (Gruntfile.js in our case) but it’s always a good habit to get into.

Setup

/*global module */
/*global require */
(function () {
  'use strict';
  module.exports = function (grunt) {
    // require it at the top and pass in the grunt instance
    // it will measure how long things take for performance
    //testing
    require('time-grunt')(grunt);

    // load-grunt will read the package file and automatically
    // load all our packages configured there.
    // Yay for laziness
    require('load-grunt-tasks')(grunt);

The first two elements that work with our content are time-grunt and load-grunt-tasks.

Time-grunt provides a breakdown of time and percentage of total execution time for each task performed in this particular Grunt run. The example below illustrates the result when running multiple tasks (bars reduced in length for formatting.)

Execution Time (2015-02-01 03:43:57 UTC)
loading tasks      983ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 12%
scsslint:allFiles   1.1s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13%
sass:dev           441ms  ▇▇▇▇▇▇▇▇▇ 5%
shell:html          1.5s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 18%
shell:single        1.2s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 14%
shell:prince        2.9s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 36%
Total 8.1s

Load-grunt-tasks automates the loading of packages located in the package.json configuration file. It’s specially good for forgetful people like me whose main mistake when building Grunt-based tool chains is forgetting to load the plugins to use :-).

Javascript

    grunt.initConfig({

      // JAVASCRIPT TASKS
      // Hint the grunt file and all files under js/
      // and one directory below
      jshint: {
        files: ['Gruntfile.js', 'js/{,*/}*.js'],
        options: {
          reporter: require('jshint-stylish')
            // options here to override JSHint defaults
        }
      },

      // Takes all the files under js/ and selected files under lib
      // and concatenates them together. I've chosen not to mangle
      // the compressed file
      uglify: {
        dist: {
          options: {
            mangle: false,
            sourceMap: true,
            sourceMapName: 'css/script.min.map'
          },
          files: {
            'js/script.min.js': ['js/video.js', 'lib/highlight.pack.js']
          }
        }
      },

JSHint will lint the Gruntfile itself and all files under the js/ directory for errors and potential errors.

[20:58:14] carlos@rivendell xml-workflow 13902$ grunt jshint
Running "jshint:files" (jshint) task

Gruntfile.js
  line 9    col 33  Missing semicolon.
  line 269  col 6   Missing semicolon.

  ⚠  2 warnings

Warning: Task "jshint:files" failed. Use --force to continue.

Aborted due to warnings.

Uglify allow us to concatenate our Javascript files and, if we choose to, further reduce the file size by mangling the code (See this page for an explanation of what mangle is and does). I’ve chosen not to mangle the code to make it easier to read. May add it as an option for production deployments.

SASS and CSS

As mentioned elsewhere I chose to use the SCSS flavor of SASS because it allows me to do some awesome things with CSS that I wouldn’t be able to do with CSS alone.

The first task with SASS is convert it to CSS. For this we have two separate tasks. One for development (dev task below) where we pick all the files from the scss directory (the entire files section is equivalent to writing scss/*.scss) and converting them to files with the same name in the css directory.

      // SASS RELATED TASKS
      // Converts all the files under scss/ ending with .scss
      // into the equivalent css file on the css/ directory
      sass: {
        dev: {
          options: {
            style: 'expanded'
          },
          files: [{
            expand: true,
            cwd: 'scss',
            src: ['*.scss'],
            dest: 'css',
            ext: '.css'
          }]
        },
        production: {
          options: {
            style: 'compact'
          },
          files: [{
            expand: true,
            cwd: 'scss',
            src: ['*.scss'],
            dest: 'css',
            ext: '.css'
          }]
        }
      },

There are two similar versions of the task. The development version will produce the format below, which is easier to read and easier to troubleshoot (css-lint, discussed below, tells you what line the error or warning happened in.)

@import url(http://fonts.googleapis.com/css?family=Roboto:100italic,100,400italic,700italic,300,700,300italic,400);
@import url(http://fonts.googleapis.com/css?family=Montserrat:400,700);
@import url(http://fonts.googleapis.com/css?family=Roboto+Slab:400,700);
@import url(http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400);
html {
  font-size: 16px;
  overflow-y: scroll;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
}

body {
  background-color: #fff;
  color: #554c4d;
  color: #554c4d;
  font-family: Adelle, Rockwell, Georgia, 'Times New Roman', Times, serif;
  font-size: 1em;
  font-weight: 100;
  line-height: 1.1;
  padding-left: 10em;
  padding-right: 10em;
}

The production code compresses the output. It deletes all tabs and carriage returns to produce cod elike the one below. It reduces the file size by eliminating spaces, tabs and carriage returns inside the rules, otherwise both versions are equivalent.

@import url(http://fonts.googleapis.com/css?family=Roboto:100italic,100,400italic,700italic,300,700,300italic,400);
@import url(http://fonts.googleapis.com/css?family=Montserrat:400,700);
@import url(http://fonts.googleapis.com/css?family=Roboto+Slab:400,700);
@import url(http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400);
html { font-size: 16px; overflow-y: scroll; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; }

body { background-color: #fff; color: #554c4d; color: #554c4d; font-family: Adelle, Rockwell, Georgia, 'Times New Roman', Times, serif; font-size: 1em; font-weight: 100; line-height: 1.1; padding-left: 10em; padding-right: 10em; }

I did consider adding cssmin but decided against it for two reasons:

SASS already concatenates all the files when it imports files from the modules and partials directory so we’re only working with one file for each version of the project (html and PDF)

The only other file we’d have to add, normalize.css, is a third party library that I’d rather leave along rather than mess with.

The scsslint task is a wrapper for the scss-lint Ruby Gem that must be installed on your system. It warns you of errors and potential errors in your SCSS stylesheets.

We’ve chosen to force it to run when it finds errors. We want the linting tasks to be used as the developer’s discretion, there may be times when vendor prefixes have to be used or where colors have to be defined multiple times to acommodate older browsers.

      // I've chosen not to fail on errors or warnings.
      scsslint: {
        allFiles: [
          'scss/*.scss',
          'scss/modules/_mixins.scss',
          'scss/modules/_variables.scss',
          'scss/partials/*.scss'
        ],
        options: {
          config: '.scss-lint.yml',
          force: true,
          colorizeOutput: true
        }
      },

Grunt’s autoprefixer task uses the CanIUse database to determine if properties need a vendor prefix and add the prefix if they do.

This becomes important for older browsers or when vendors drop their prefix for a given property. Rather than having to keep up to date on all vendor prefixed properties you can tell autoprefixer what browsers to test for (last 2 versions in this case) and let it worry about what needs to be prefixed or not.

      autoprefixer: {
        options: {
          browsers: ['last 2']
        },

        files: {
          expand: true,
          flatten: true,
          src: 'scss/*.scss',
          dest: 'css/'
        }
      },

The last css task is the most complicated one. Uncss takes out whatever CSS rules are not used in our target HTML files.

      // CSS TASKS TO RUN AFTER CONVERSION
      // Cleans the CSS based on what's used in the specified files
      // See https://github.com/addyosmani/grunt-uncss for more
      // information
      uncss: {
        dist: {
          files: {
            'css/tidy.css': ['*.html', '!docs.html']
          }
        }
      },

This is not a big deal for our workflow as most, if not all, the CSS is designed for the tags and classes we’ve implemented but it’s impossible for the SASS/CSS libraries to grow over time and become bloated.

This will also become and issue when you decide to include third part libraries in projects implemented on top of our workflow. By running Uncss on all our HTML files except the file we’ll pass to our PDF generator (docs.html) we can be assured that we’ll get the smallest css possible.

We skip out PDF source html file because I’m not 100% certain that Uncss can work with Paged Media CSS extensions. Better safe than sorry.

Optional tasks

I’ve also created a set of optional tasks that are commented in the Grunt file but have been uncommented here for readability.

The first optional task is a Coffeescript compiler. Coffeescript is a scripting language that provides a set of useful features and that compiles directly to Javascript.

I some times use Coffeescript to create scripts and other interactive content so it’s important to have the compilation option available.

      // OPTIONAL TASKS
      // Tasks below have been set up but are currently not used.
      // If you want them, uncomment the corresponding block below

      // COFFEESCRIPT
      // If you want to use coffeescript (http://coffeescript.org/)
      // instead of vanilla JS, uncoment the block below and change
      // the cwd value to the locations of your coffee files
      coffee: {
        target1: {
          expand: true,
          flatten: true,
          cwd: 'src/',
          src: ['*.coffee'],
          dest: 'build/',
          ext: '.js'
      },

The following two tasks are for managing file transfers and uploads to different targets.

One of the things I love from working on Github is that your project automatically gets an ssl-enabled site for free. Github Pages work with any kind of static website; Github even offers an automatic site generator as part of our your project site.

For the puposes of our workflow validation we’ll make a package of our content in a build directory and push it to the gh-pages branch of our repository. We’ll look at building our app directory when we look at copying files.

      // GH-PAGES TASK
      // Push the specified content into the repository's gh-pages branch
      'gh-pages': {
        options: {
          message: 'Content committed from Grunt gh-pages',
          base: './build/app',
          dotfiles: true
        },
        // These files will get pushed to the `
        // gh-pages` branch (the default)
        // We have to specifically remove node_modules
        src: ['**/*']
      },

There are times when we are not working with Github or pages. In this case we need to FTP or SFTP (encrypted version of FTP) to push files to remote servers. We use an external json file to store our account information. Ideally we’d encrypt the information but until then using the external file is the first option.

      //SFTP TASK
      //Using grunt-ssh (https://www.npmjs.com/package/grunt-ssh)
      //to store files in a remote SFTP server. Alternative to gh-pages
      secret: grunt.file.readJSON('secret.json'),
      sftp: {
        test: {
          files: {
            "./": "*.json"
          },
          options: {
            path: '/tmp/',
            host: '< %= secret.host %>',
            username: '< %= secret.username %>',
            password: '< %= secret.password %>',
            showProgress: true
          }
        }
      },

File Management

We’ve taken a few file management tasks into Grunt to make our lifes easier. The functions are for:

  • Creating directories
  • Copying files
  • Deleting files and directories

We will use the mkdir and copy tasks to create a build directory and copy all css, js and html files to the build directory. We will then use the gh-pages task (described earlier) to push the content to the repository’s gh-pages branches

      // FILE MANAGEMENT
      // Can't seem to make the copy task create the directory
      // if it doesn't exist so we go to another task to create
      // the fn directory
      mkdir: {
        build: {
          options: {
            create: ['build']
          }
        }
      },

      // Copy the files from our repository into the build directory
      copy: {
        build: {
          files: [{
            expand: true,
            src: ['app/**/*'],
            dest: 'build/'
          }]
        }
      },

      // Clean the build directory
      clean: {
        production: ['build/']
      },

Watch task

Rather than type a command over and over again we can set up watchers so that, any time a file of the indicated type changes, we perform specific tasks.

AS currentlly configured we track Javascript and SASS files.

For Javascript files anytime that the Gruntfile or any file under the Javascript directorie we run the JSHint task to make sure we haven’t made any mistakes.

For our SASS/SCSS files, any files under the scss directory, we run the sass:dev task to translate the files to CSS.

      // WATCH TASK
      // Watch for changes on the js and scss files and perform
      // the specified task
      watch: {
        options: {
          nospawn: true
        },
        // Watch all javascript files and hint them
        js: {
          files: ['Gruntfile.js', 'js/{,*/}*.js'],
          tasks: ['jshint']
        },
        sass: {
          files: ['scss/*.scss'],
          tasks: ['sass:dev']
        }
      },

Compile and Execute

Rather than using Ant, I’ve settled on Grunt’s shell task to run the compilation steps to create HTML and PDF. This reduces teh number of dependecies for our project and makes it easier to consolidate all the work.

We have three different commands:

  • html will create multiple html files using Saxon, a Java XSLT processor
  • single will create a single html file using Saxon
  • prince will create a PDF based on the single html file using PrinceXML

We make sure that we don’t continue if there is an error. Want to make sure that we troubleshoot before we get all the resulting files.

      // COMPILE AND EXECUTE TASKS
      shell: {
        options: {
          failOnError: true,
          stderr: false
        },
        html: {
          command: 'java -jar /usr/local/java/saxon.jar -xsl:xslt/book.xsl docs.xml -o:index.html'
        },
        single: {
          command: 'java -jar /usr/local/java/saxon.jar -xsl:xslt/pm-book.xsl docs.xml -o:docs.html'
        },
        prince: {
          command: 'prince --verbose --javascript docs.html -o docs.pdf'
        }
      }


    }); // closes initConfig

Custom Tasks

The custom task uses one or more of the tasks defined above to accomplish a sequence of tasks.

Look at specific tasks defined above for specific definitions.

    // CUSTOM TASKS
    // Usually a combination of one or more tasks defined above
    grunt.task.registerTask(
      'lint',
      [
        'jshint'
      ]
    )

    grunt.task.registerTask(
      'lint-all',
      [
        'scsslint',
        'jshint'
      ]
    );

    // Prep CSS starting with SASS, autoprefix et. al
    grunt.task.registerTask(
      'prep-css',
      [
        'scsslint',
        'sass:dev',
        'autoprefixer'
      ]
    );

    grunt.task.registerTask(
      'prep-js',
      [
        'jshint',
        'uglify'
      ]
    );

    grunt.task.registerTask(
      'generate-pdf',
      [
        'shell:single',
        'shell:prince'
      ]
    );

    grunt.task.registerTask(
      'generate-pdf-scss',
      [
        'scsslint',
        'sass:dev',
        'shell:single',
        'shell:prince'
      ]
    );

    grunt.task.registerTask(
      'generate-all',
      [
        'shell'
      ]
    );


  }; // closes module.exports
}()); // closes the use strict function

XML Workflows: CSS Styles for Paged Media

This is the generated CSS from the SCSS style sheets (see the scss/ directory for the source material.) I’ve chosen to document the resulting stylesheet here and document the SCSS source in another document to make life simpler for people who don’t want to deal with SASS or who want to see what the style sheets look like.

Typography derived from work done at this URL: http://bit.ly/16N6Y2Q

The following scale (also using minor third progression) may also help: http://bit.ly/1DdVbqK

Feel free to play with these and use them as starting point for your own work 🙂

The project currently uses these fonts:

  • Roboto Slab for headings
  • Roboto for body copy
  • Source Code Pro for code blocks and preformated text

Font Imports

Even though SCSS Lint throws a fit when I put font imports in a stylesheet because they stop asynchronous operations, I’m doing it to keep the HTML files clean and because we are not loading the CSS on the page, we’re just using it to process the PDF file.

Eventually I’ll switch to locally hosted fonts using bulletproof font syntax (discussed here and available for use at Font Squirrel.

At this point we are not dealing with font subsetting but we may in case we need to.

@import url(http://fonts.googleapis.com/css?family=Roboto:100italic,100,400italic,700italic,300,700,300italic,400);
@import url(http://fonts.googleapis.com/css?family=Roboto+Slab:400,700);
@import url(http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400);

Defaults

Now that we’ve loaded the fonts we can create our defaults for the document. The html element defines vertical overflow and text size adjustment for Safari and Windows browsers.

html {
overflow-y: scroll;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}

The body selector will handle most of the base formatting for the the document.

The selector sets up the following aspects of the page:

  • background and font color
  • font family, size and weight
  • line height
  • left and right padding (overrides the base document’s padding)
  • orphans and widows
body {
background-color: #fff;
color: #554c4d;
font-family: 'Roboto', 'Helvetica Neue', Helvetica, sans-serif;
font-size: 1em;
font-weight: 100;
line-height: 1.1;
orphans: 4;
padding-left: 0;
padding-right: 0;
widows: 2;
}

Blockquotes, Pullquotes and Marginalia

It’s fairly easy to create sidebars in HTML so I’ve played a lot with pull quotes, blockquotes and asides as a way to move the content around with basic CSS. We can do further work by tuning the CSS

aside {
border-bottom: 3px double #ddd;
border-top: 3px double #ddd;
color: #666;
line-height: 1.4em;
padding-bottom: .5em;
padding-top: .5em;
width: 100%;
}

aside .pull {
margin-bottom: .5em;
margin-left: -20%;
margin-top: .2em;
}

The magin-notes* and content* move the content to the corresponding side of the page without having to create specific CSS to do so. The downside is that, as with many things in CSS, you are stuck with the provided values and will have to modify them to suit your needs.

.margin-notes,
.content-left {
font-size: .75em;
margin-left: -230px;
margin-right: 20px;
text-align: right;
width: 230px;
}

.margin-notes-right,
.content-right {
font-size: .75em;
margin-left: 760px;
margin-right: -20px;
position: absolute;
text-align: left;
width: 230px;
}

.content-right {
font-size: .75em;
margin-left: 760px;
margin-right: -20px;
position: absolute;
text-align: left;
width: 230px;
}

.content-right ul,
.content-left ul {
list-style: none;
}

The opening class style creates a large distinguishing block container for opening text. This is useful when you have a summary paragraph at the beginning of your document or some other opening piece of text to go at the top of your document

.opening {
border-bottom: 3px double #ddd;
border-top: 3px double #ddd;
font-size: 2em;
margin-bottom: 10em;
padding-bottom: 2em;
padding-top: 2em;
text-align: center;
}

Blockquotes present the enclosed text in larger italic font with a solid bar to the left of the content. Because the font is larger I’ve added

blockquote {
border-left: 5px solid #ccc;
color: #222023;
font-size: 1.5em;
font-style: italic;
font-weight: 100;
margin-bottom: 2em;
margin-left: 4em;
margin-right: 4em;
margin-top: 2em;
}
blockquote p {
padding-left: .5em;
}

The pullquote classes were modeled after an ESPN article and look something like this:

example pullquote

The original was hardcoded to pixels. Where possible I’ve changed the values to em to provide a more responsive

.pullquote {
border-bottom: 18px solid #000;
border-top: 18px solid #000;
font-size: 2.25em;
font-weight: 700;
letter-spacing: -.02em;
line-height: 2.125em;
margin-right: 2.5em;
padding: 1.25em 0;
position: relative;
width: 200px;
}
.pullquote p {
color: #00298a;
font-weight: 700;
text-transform: uppercase;
z-index: 1;
}
.pullquote p:last-child {
line-height: 1.25em;
padding-top: 2px;
}
.pullquote cite {
color: #333;
font-size: 1.125em;
font-weight: 400;
}

Paragraphs

The paragraph selector creates the default paragraph formatting with a size of 1em (equivalent to 16 pixels) and a line height of 1.3 em (20.8 pixels)

p {
font-size: 1em;
margin-bottom: 1.3em;
}

To indent all paragraphs but the first we use the sibling selector we indent all paragraphs that are the next sibling of another paragraph element (that is: the next child of the same parent).

The first paragraph doesn’t have a paragraph sibling so the indent doesn’t happen but all other paragraphs are indented

p + p {
text-indent: 2em;
}

Rather than use pseudo elements (:first-line and :first-letter) we use classes to give authors the option to use these elements.

.first-line {
font-size: 1.1em;
text-indent: 0;
text-transform: uppercase;
}

.first-letter {
float: left;
font-size: 7em;
line-height: .8em;
margin-bottom: -.1em;
padding-right: .1em;
}

Lists

The only thing we do for list and list items is to indicate what type of list we’ll use as our default square for unordered list and Arabic decimals for our numbered lists.

ul li {
list-style: square;
}

ol li {
list-style: decimal;
}

Figures and captions

The only interesting aspect of the CSS we use for figures is the counter. The figure figcaption::before selector creates automatic text that is inserted before each caption. This text is the string “Figure”, the value of our figure counter and the string “: “.

This makes it easier to insert figures without having to change the captions for all figures after the one we inserted. The figure counter is reset for every chapter. I’m researching ways to get the figure numbering across chapters.

figure {
counter-increment: figure_count;
margin-bottom: 1em;
margin-top: 1em;
}
figure figcaption {
font-weight: 700;
padding-bottom: 1em;
padding-top: .2em;
}

figure figcaption::before {
content: "Figure " counter(figure_count) ": ";
}

Headings

Headings are configured in two parts. The first one sets common attributes to all headings: font-family, font-weight, hyphes, line-height, margins and text-transform.

It’s this attribute that needs a little more discussion. Using text-transform we make all headings uppercase without having to write them that way

h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Roboto Slab', sans-serif;
font-weight: 400;
hyphens: none;
line-height: 1.2;
margin: 1.414em 0 .5em;
text-transform: uppercase;
}

In the second part of our heading styles we work on rules that only apply to one heading at a time. Things such as size and specific attributes (like removing the top margin on the h1 elements) need to be handled need to be handled individually

h1 {
font-size: 3.157em;
margin-top: 0;
}

h2 {
font-size: 2.369em;
}

h3 {
font-size: 1.777em;
}

h4 {
font-size: 1.333em;
}

h4,
h5,
h6 {
text-align: inherit;
}

Different parts of the book

There are certains aspects of the book that need different formatting from our defaults.

We use the element[attribute=name] syntax to identify which section we want to work with and then tell it the element within the section that we want to change.

For example, in the bibliography (a section with the data-type='bibliography attribute) we want all paragraphs to be left aligned and all paragraphs to have no margin (basicallwe we are undoing the indentation for paragraphs with sibling paragraphs within the bibliography section)

section[data-type='bibliography'] p {
text-align: left;
}
section[data-type='bibliography'] p + p {
text-indent: 0 !important;
}

The same logic applies to the other sections that we’re customizing. We tell it what type of section we are working with and what element inside that sectin we want to change.

section[data-type='titlepage'] h1,
section[data-type='titlepage'] h2,
section[data-type='titlepage'] p {
text-align: center;
}

section[data-type='dedication'] h1,
section[data-type='dedication'] h2 {
text-align: center;
}
section[data-type='dedication'] p {
text-align: left;
}
section[data-type='dedication'] p + p {
text-indent: 0 !important;
}

Preformatted code blocks

A lot of what I write is technical and requires code examples. We take a two pronged approach to the fenced code blocks.

We format some aspects our content (wrap, font-family, size, line height and wether to do page breaks inside the content) locally and hand off syntax highlighting to highlight.js with a style to mark the content differently.

pre {
overflow-wrap: break-word;
white-space: pre-line !important;
word-wrap: break-word;
}
pre code {
font-family: 'Source Code Pro', monospace;
font-size: 1em;
line-height: 1.2em;
page-break-inside: avoid;
}

Miscelaneous classes

Rather than for people to justify text we provide a class to make it so. I normally justify at the div or section level but it’s not always necessary or desirable.

Code will be used in a future iteration of the code to highlight inline snippets (think of it as an inline version of the <pre><code> tag combination)

.justified {
text-align: justify;
}

.code {
background-color: #e6e6e7;
opacity: .75;
}

Columns

The last portion of the stylesheet deals with columns. I’ve set up 2 set of rules for 2 and 3 column with similar attributes. In the SCSS source these are created with a column mixin.

.columns2 {
column-count: 2;
column-gap: 3em;
column-fill: balance;
column-span: none;
line-height: 1.25em;
width: 100%;
}
.columns2 p:first-of-type {
margin-top: 0;
}
.columns2 p + p {
text-indent: 2em;
}
.columns2 p:last-of-type {
margin-bottom: 1.25em;
}

.columns3 {
column-count: 3;
column-gap: 10px;
column-fill: balance;
column-span: none;
width: 100%;
}
.columns3 p:first-of-type {
margin-top: 0;
}
.columns3 p:not:first-of-type {
text-indent: 2em;
}
.columns3 p:last-of-type {
margin-bottom: 1.25em;
}

XML Wokflows: From XML to PDF: Part 2: CSS

With the HTML ready, we can no look at the CSS stylesheet to process it into PDF.

The extensions, pseudo elements and attributes we use are all part of the CSS Paged Media or Generated Content for Paged Media specifications. Where appropriate I’ve translated them to work on both PDF and HTML.

Book defaults

The first step in creating the default structure for the book using @page at-element.

Our base definition does the following:

  1. Size the page to letter (8.5 by 11 inches), width first
  2. Use CSS notation for margins. In this case the top and bottom margin are 0.5 inches and left and right are 1 inch
  3. Reset the footnote counter.
  4. Using the @footnote attribute do the following
    1. Increment the footnote counter
    2. Place footnote at the bottom using another value for the float attribute
    3. Span all columns
    4. Make the height as tall as necessary
/* STEP 1: DEFINE THE DEFAULT PAGE */
@page {
  size: 8.5in 11in; (1)
  margin: 0.5in 1in; (2)
  /* Footnote related attributes */
  counter-reset: footnote; (3)
  @footnote {
    counter-increment: footnote; (4.1)
    float: bottom; (4.2)
    column-span: all; (4.3)
    height: auto; (4.4)
    }
  }

In later sections we’ll create named page templates and associate them to different portions of our written content.

Page counters

We define two conditions under which we reset the page counter: When we have a book followed by a part and when we have a book followed by the a first chapter.

We do not reset the content when the path if from book to chapter to part.

body[data-type='book'] > div[data-type='part']:first-of-type,
body[data-type='book'] > section[data-type='chapter']:first-of-type { counter-reset: page; }
body[data-type='book'] > section[data-type='chapter']+div[data-type='part'] { counter-reset: none }

Matching content sections to page types

The next section of the style sheet is to match the content on our book to pages in our style sheet.

The book is broken into sections with data-type attributes to indicate the type of content; we match the section[data-type] element to a page type along with some basic style definitions.

We will further define the types of pages later in the style sheet.

/* Title Page*/
section[data-type='titlepage'] { page: titlepage }

/* Copyright page */
section[data-type='copyright'] { page: copyright }

/* Dedication */
section[data-type='dedication'] {
  page: dedication;
  page-break-before: always;
}

/* TOC */
section[data-type='toc'] {
  page: toc;
  page-break-before: always;
}
/* Leader for toc page */
section[data-type='toc'] nav ol li a:after {
  content: leader(dotted) ' ' target-counter(attr(href, url), page);
}

/* Foreword  */
section[data-type='foreword'] { page: foreword }

/* Preface*/
section[data-type='preface'] { page: preface }

/* Part */
div[data-type='part'] { page: part }

/* Chapter */
section[data-type='chapter'] {
  page: chapter;
  page-break-before: always;
}

/* Appendix */
section[data-type='appendix'] {
  page: appendix;
  page-break-before: always;
}

/* Glossary*/
section[data-type='glossary'] { page: glossary }

/* Bibliography */
section[data-type='bibliography'] { page: bibliography }

/* Index */
section[data-type='index'] { page: index }

/* Colophon */
section[data-type='colophon'] { page: colophon }

Front matter formatting

For each page of front matter contnt (toc, foreword and preface) we define two pages: left and right. We do it this way to acommodate facing pages with numbers on ooposite sides (for two sided printout)

For the front matter we chose to use Roman numerals on the bottom of the page

/* Comon Front Mater Page Numbering in lowercase ROMAN numerals*/
@page toc:right {
  @bottom-right-corner { content: counter(page, lower-roman) }
  @bottom-left-corner { content: normal }
}

@page toc:left  {
  @bottom-left-corner { content: counter(page, lower-roman) }
  @bottom-right-corner { content: normal }
}


@page foreword:right {
  @bottom-center { content: counter(page, lower-roman) }
  @bottom-left-corner { content: normal }
}

@page foreword:left  {
  @bottom-left-corner { content: counter(page, lower-roman) }
  @bottom-right-corner { content: normal }
}


@page preface:right {
  @bottom-center {content: counter(page, lower-roman)}
  @bottom-right-corner { content: normal }
  @bottom-left-corner { content: normal }
}

@page preface:left  {
  @bottom-center {content: counter(page, lower-roman)}
  @bottom-right-corner { content: normal }
  @bottom-left-corner { content: normal }
}

Pages formatting

We use the same system we used in the front matter to do a few things with our content.

We first remove page numbering from the title page and dedication by setting the numbering on both bottom corners to normal.

/* Common Content Page Numbering  in Arabic numerals 1... 199 */
@page titlepage{ /* Need this to clean up page numbers in titlepage in Prince*/
  margin-top: 18em;
  @bottom-right-corner { content: normal }
  @bottom-left-corner { content: normal }
}

@page dedication { /* Need this to clean up page numbers in titlepage in Prince*/
  page-break-before: always;
  margin-top: 18em;
  @bottom-right-corner { content: normal }
  @bottom-left-corner { content: normal }

}

Now we start working on our chapter pages. The first thing we do is to place our running header content in the bottom middle of the page, regardless of whether it’s left or right.

@page chapter {
  @bottom-center {
    vertical-align: middle;
    text-align: center;
    content: element(heading);
  }
}

We next setup a blank page for our chapters and tell the reader that the page was intentionally left blank to prevent confusion

@page chapter:blank { /* Need this to clean up page numbers in titlepage in Prince*/
  @top-center { content: "This page is intentionally left blank" }
  @bottom-left-corner { content: normal;}
  @bottom-right-corner {content:normal;}
}

Then we number the pages the same way that we did for our front matter except that we use narabic numerals instead of Roman. 

```css
@page chapter:right  {
  @bottom-right-corner { content: counter(page) }
  @bottom-left-corner { content: normal }
}

@page chapter:left {
  @bottom-left-corner { content: counter(page) }
  @bottom-right-corner { content: normal }
}

@page appendix:right  {
  @bottom-right-corner { content: counter(page) }
  @bottom-left-corner { content: normal }
}

@page appendix:left {
  @bottom-left-corner { content: counter(page) }
  @bottom-right-corner { content: normal }
}

@page glossary:right,  {
  @bottom-right-corner { content: counter(page) }
  @bottom-left-corner { content: normal }
}

@page glossary:left, {
  @bottom-left-corner { content: counter(page) }
  @bottom-right-corner { content: normal }
}

@page bibliography:right  {
  @bottom-right-corner { content: counter(page) }
  @bottom-left-corner { content: normal }
}

@page bibliography:left {
  @bottom-left-corner { content: counter(page) }
  @bottom-right-corner { content: normal }
}

@page index:right  {
  @bottom-right-corner { content: counter(page) }
  @bottom-left-corner { content: normal }
}

@page index:left {
  @bottom-left-corner { content: counter(page) }
  @bottom-right-corner { content: normal }
}

Running footer

We now style the running footer.

p.rh {
  position: running(heading);
  text-align: center;
  font-style: italic;
}

Footnotes and cross references

Footnotes are tricky, they consist of two parts, the footnote-call and the footnote content itself. I’m still trying to figure out what the correct markup should be for marking up footnotes.

We’ve also defined a special class of links that appends a string and the the destination’s page number.

/* Footnotes */
span.footnote {
  float: footnote;
}

::footnote-marker {
  content: counter(footnote);
  list-style-position: inside;
}

::footnote-marker::after {
  content: '. ';
}

::footnote-call {
  content: counter(footnote);
  vertical-align: super;
  font-size: 65%;
}

/* XReferences */
a.xref[href]::after {
    content: ' [See page ' target-counter(attr(href), page) ']'
}

PDF Bookmarks

PDF bookmarks allow you to navigate your content form the left side bookmark menu as show in the image below

PDF Bookmarks

For each heading level we do the following things for both Antenna House and PrinceXML:

  • Set up the bookmark level
  • Set up whether it’s open or closed
  • Set up the label for the bookmark

Only heading 1, 2 and 3 are set up, level 4, 5 and 6 are only set up as bookmarks only.

section[data-type='chapter'] h1 {
  -ah-bookmark-level: 1;
  -ah-bookmark-state: open;
  -ah-bookmark-label: content();
  prince-bookmark-level: 1;
  prince-bookmark-state: closed;
  prince-bookmark-label: content();
}

section[data-type='chapter'] h2 {
  -ah-bookmark-level: 2;
  -ah-bookmark-state: closed;
  -ah-bookmark-label: content();
  prince-bookmark-level: 2;
  prince-bookmark-state: closed;
  prince-bookmark-label: content();
}

section[data-type='chapter'] h3 {
  -ah-bookmark-level: 3;
  -ah-bookmark-state: closed;
  -ah-bookmark-label: content();
  prince-bookmark-level: 3;
  prince-bookmark-state: closed;
  prince-bookmark-label: content();
}

section[data-type='chapter'] h4 {
  -ah-bookmark-level: 4;
  prince-bookmark-level: 4;
}

section[data-type='chapter'] h5 {
  -ah-bookmark-level: 5;
  prince-bookmark-level: 5;
}

section[data-type='chapter'] h6 {
  -ah-bookmark-level: 6;
  prince-bookmark-level: 6;
}

Running PrinceXML

Once we have the HTML file ready we can run it through PrinceXML to get our PDF using CSS stylesheet for Paged Media we discussed above. The command to run the conversion for a book.html file is:

$ prince --verbose book.html test-book.pdf

Because we added the stylesheet link directly to the HTML document we can skip declaring it in the conversion itself. This is always a cause of errors and frustrations for me so I thought I’d save everyone else the hassle.