April 6, 2019

Lessons learned trying to build an InDesign clone in React

Many magazine layouts still can't be expressed in pure HTML and CSSMany magazine layouts still can't be expressed in pure HTML and CSS

CSS supports some pretty powerful layout capabilities these days, and I thought it would be fun to try build a simplified clone of InDesign (Adobe’s document layout app) using React, rendering everything to standard HTML and CSS. I realised pretty quickly that some medium-complexity print layouts are still not possible. Below I’ve outlined a few gaps I encountered.

Text wrap in CSS is limited

Let’s say I wanted to flow a <div>s text around a circular shape or image like this:

Flowing text around both sides of a shape – not yet that easy in CSSFlowing text around both sides of a shape – not yet that easy in CSS

It turns out that CSS features for flowing text around an arbitrary shape are still nascent and not universally supported.

CSS Shapes, proposed by Adobe in 2012, is supported by the major browsers (except IE/Edge), but it comes with limitations: it can only be applied to floated elements, and it only supports wrapping text around one side of an image. Thus, the example layout above would not be possible using CSS Shapes.

CSS Exclusions would be able to achieve this layout, but it’s only supported in IE/Edge.

Linking text boxes is not yet possible

Linking text boxes to flow text from one to the otherLinking text boxes to flow text from one to the other

When laying out a document in InDesign, it’s common to link text boxes so that text overflows from the first into the second, allowing you to flow content across different regions and pages. This is not yet possible in pure HTML and CSS, so you would have to write JavaScript that assigns the correct portions of text to each box.

The CSS Regions spec aims to add this functionality, but it was created in 2014 and the major browsers still don’t support it.

contenteditable is hard — use a plugin like Slate

To make text boxes editable on the web, you add the contenteditable attribute:

    <div contenteditable="true">
        A browser would let the user edit this content.
    </div>

Managing a contenteditable element in React is more complex than a simple <input> element, but thankfully there is an excellent library called Slate which makes it super easy. It also has an extensive set of plugin points to do things like enforce document schemas, perform rich-text formatting, and intercept keyboard shortcuts. I would highly recommend this library for anyone using contenteditable elements in React.

Apple’s iCloud Pages uses a custom rendering engine

iCloud Pages can lay out documents that can't be laid out using pure HTML and CSSiCloud Pages can lay out documents that can't be laid out using pure HTML and CSS

Apple’s web-based implementation of Pages allows most functionality you’ll find in the desktop, including flowing text across boxes and complex word wrap. They can do this because they’ve built a custom rendering engine that renders everything to SVGs. This is the page source behind the layout above:

iCloud Pages uses SVGs to lay out the document and UIiCloud Pages uses SVGs to lay out the document and UI

Browser updates can break your document layout

Even in the short time span that I was working on this project, one update of Chrome led to one text box on a page shifting by one pixel. One pixel might not be much on some blog webpage layout, but if you’re about to get thousands of copies of your new book printed, you want to have faith that your pages are laid out exactly as you intended. I didn’t dig into the root cause of this particular issue, but it highlighted how risky it could be to build a document layout application where you don’t control the rendering layer.

To be continued…

I only scratched the surface of trying to port print layout features onto the web, but it showed that pure HTML and CSS are not quite ready for building a document layout application. Here’s hoping Adobe pushes more of these CSS specs into reality.


Cover photo by Jamie Street on Unsplash


React TypeScript


Previous post
One year of Car Next Door vs. owning a car
Next post
How to forward emails via Zapier to create new contacts in Airtable