Quad:   document processor
1 Installing Quad & Quadwriter
2 What is Quad?
2.1 How does Quad work?
3 What is Quadwriter?
4 Quadwriter quick tour
4.1 Quadwriter & Markdown
4.2 Quadwriter & markup
4.3 Quadwriter & Q-expressions
4.4 Setting top-level attributes
4.5 Invoking Quadwriter as a library
4.6 Combining Quadwriter with Pollen
4.7 Quick tour complete
5 Quadwriter:   developer guide
doc
5.1 Q-expressions
5.2 Markup
5.3 Hard breaks
line-break
column-break
page-break
para-break
5.4 Attributes
5.4.1 Page-level attributes
page-size
page-orientation
page-width
page-height
page-margin-top
page-margin-bottom
page-margin-left
page-margin-right
page-number-start
column-count
column-gap
5.4.2 Block-level attributes
inset-top
inset-bottom
inset-left
inset-right
border-inset-top
border-inset-bottom
border-inset-left
border-inset-right
border-width-top
border-width-bottom
border-width-left
border-width-right
border-color-top
border-color-bottom
border-color-left
border-color-right
background-color
space-before
space-after
keep-first-lines
keep-last-lines
keep-all-lines
keep-with-next
line-align
line-align-last
first-line-indent
line-wrap
hyphenate
clip
image-file
image-alt
image-height
image-width
5.4.3 Other attributes
font-size
font-size-adjust
font-family
font-color
font-bold
font-italic
font-features
font-features-adjust
font-tracking
font-baseline-shift
line-height
5.5 Rendering
render-pdf
5.6 Fonts
5.6.1 Default font families
text
heading
code
blockquote
default
fallback-emoji
fallback-math
fallback
5.7 Colors
5.8 Utility
view-result
6 Quad:   the details
6.1 Data model:   the quad
6.2 Wrapping
6.3 Layout
6.4 Rendering
7 What are your plans for Quad?
7.1 Why is it called Quad?
7.3

Quad: document processor

Matthew Butterick <mb@mbtype.com>

Quad is in progress. It works, but it is unstable — I am still changing things, small and large — and thus I make no commitment to maintain the API in its current state.

1 Installing Quad & Quadwriter

At the command line:

raco pkg install quad

After that, you can update the package like so:

raco pkg update quad

Or, without the command line: Launch Dr­Racket. Use the FileInstall Package ... command to install quad.

Either way, quadwriter is installed as part of the quad package.

If you’re new to Racket and want to configure your system to use the terminal commands, follow the instructions here.

2 What is Quad?

A document processor, which means that it:

  1. Computes the layout of your document from a series of formatting codes (not unlike a web browser)

  2. Renders to PDF (not unlike a word processor).

For instance, LaTeX is a document processor. So are web browsers. Quad borrows from both traditions — it’s an attempt to modernize the good ideas in LaTeX, and generalize the good ideas in web browsers, while bypassing some of the limitations of LaTeX (e.g., no Unicode) and of web browsers (e.g., performance and error recovery are valued above all).

Document processors sit opposite WYSIWYG tools like Microsoft Word and Adobe InDesign. There, the user controls the layout by manipulating a representation of the page on the screen. This is fine as far as it goes. But changes to the layout — for instance, a new page size — often require a new round of manual adjustments.

A document processor, by contrast, relies on markup codes within the text to determine the layout programmatically. Compared to WYSIWYG, this approach offers less granular control. But it also creates a more flexible relationship between the source and its possible layouts.

Another benefit of document processors is that it permits every document to have a high-level, text-based source file that’s independent of any particular output format.

Much of the font-parsing and PDF-rendering code in Quad is adapted from FolioJS by Devon Govett. I thank Mr. Govett for figuring out a lot of details that would’ve made me squeal in agony.

2.1 How does Quad work?

Quad produces PDFs using three ingredients:

  1. A font engine that handles glyph shaping and positioning using standard TTF or OTF font files.

  2. A layout engine that converts typesetting instructions into an output-independent layout — e.g., putting characters into lines, and lines into pages.

  3. A PDF engine that takes this layout and renders it as a finished PDF file.

For the most part, neither Quad nor Quadwriter rely much on racket/draw. In particular, Quad completely ignores Racket’s PDF-drawing functions, which are provided by Pango, because of major shortcomings in the kind of PDFs it produces (for instance, it doesn’t support hyperlinks).

3 What is Quadwriter?

A demo app built with Quad. It takes a text-based source file as input, calculates the typesetting and layout, and then outputs a PDF.

You can fiddle with it & then submit issues and feature requests at the Quad repo.

4 Quadwriter quick tour

Open DrRacket (or whatever editor you prefer) and start a new document with #lang quadwriter/markdown as the first line:

"test.rkt"
#lang quadwriter/markdown
Brennan and Dale like fancy sauce.

Save the document. Any place, any name is fine.

Run the document. You’ll get REPL output like this:

quadwriter: atomize: 2ms
quadwriter: hyphenate: 1ms
quadwriter: line-wrap: 21ms
quadwriter: col-wrap: 0ms
quadwriter: page-wrap: 0ms
quadwriter: position: 1ms
quadwriter: draw: 75ms
quadwriter: wrote PDF to /Users/Desktop/test.pdf

Congratulations — you just made your first PDF. If you want to have a look, either open the file manually, or enter this command on the REPL, which will open the PDF in your default viewer:

> (view-result)

Next, on the REPL enter this:

> doc

You will see the actual input to Quadwriter, which is called a Q-expression:

'(q () (q ((page-margin-left "120") (page-margin-top "80") (page-margin-bottom "120") (font-family "text") (line-height "17")) (q ((keep-first-lines "2") (keep-last-lines "3") (font-size-adjust "100%") (character-tracking "0") (hyphenate "true") (display "g49598")) "Brennan and Dale like fancy sauce.")))

In the demos that follow, the input language will change slightly. But the PDF will be rendered the same way (by running the source file) and you can always look at doc or use view-result.

4.1 Quadwriter & Markdown

I don’t recommend that writers adopt Markdown for serious projects. But for goofing around, why not.

Our first version of "test.rkt" had one line of plain text:

"test.rkt"
#lang quadwriter/markdown
Brennan and Dale like fancy sauce.

Behind the scenes, quadwriter/markdown is doing more heavy lifting than this sample suggests. We can type our source in Markdown notation, and it will automatically be converted to the appropriate Quad formatting commands to make things look right.

For instance, try this sample, which combines a Markdown heading, bullet list, code block, and bold and italic formatting:

"test.rkt"
#lang quadwriter/markdown
# Did you know?
 
__Brennan__ and **Dale** like:
 
* *Fancy* sauce
* _Chicken_ fingers
 
```
And they love to code
```

You’re welcome to paste in bigger Markdown files that you have laying around and see what happens. As a demo language, I’m sure there are tortured agglomerations of Markdown notation that will confuse quadwriter/markdown. But vanilla files should be fine.

Back to the demo. Curious characters can do this:

> doc

To see this:

'(q
()
(q
((page-margin-left "120") (page-margin-top "80") (page-margin-bottom "120") (font-family "text") (line-height "17"))
(q ((break "para")))
(q ((font-family "heading") (first-line-indent "0") (display "block") (font-size "20") (line-height "24.0") (border-width-top "0.5") (border-inset-top "9") (inset-bottom "-3") (inset-top "6") (keep-with-next "true") (id "did-you-know")) "Did you know?")
···

This is the first part of the Q-expression that the source file produces when it runs and exports via doc. This Q-expression is passed to Quadwriter for layout and rendering.

Yes, you can generate your own Q-expressions by other means and pass them to quadwriter for layout & rendering. See render-pdf.

Mac OS note: I have no connection to the Skim PDF reader, but it has an auto-refresh feature that monitors a PDF for changes. This cooperates nicely with Quadwriter during editing sessions: you can have a window on the PDF that updates automatically when you recompile the source file (say, in DrRacket).

4.2 Quadwriter & markup

Suppose Markdown is just not your thing. You prefer to enter your markup the old-fashioned way — by hand. I hear you. So let’s switch to the quadwriter/markup dialect. First we try our simple test:

"test.rkt"
#lang quadwriter/markup
Brennan and Dale like fancy sauce.

We get the same PDF result as before, again because a short line of plain text is the same in this dialect as the last.

But if we want to reproduce the result of the Markdown notation, this time we use the equivalent HTML-ish markup tags:

"test.rkt"
#lang quadwriter/markup
h1{Did you know?}
 
strong{Brennan} and strong{Dale} like:
 
ul{
li{em{Fancy} sauce}
li{em{Chicken} fingers}
}
 
pre{
code{
And they love to code
}
}

The special character is called a lozenge. It introduces markup tags. Instructions for typing it, but for now it suffices to copy & paste, or use the Insert Command Char button in the DrRacket toolbar.

Under the hood, the quadwriter/markdown dialect is converting the Markdown surface notation into markup tags that look like this. So the quadwriter/markup dialect just lets us start with those tags.

Curious characters can prove that this is so by again typing at the REPL:

> doc

This Q-expression is exactly the same as the one that resulted with the quadwriter/markdown source file.

4.3 Quadwriter & Q-expressions

quadwriter/markdown showed high-level notation (= a generous way of describing Markdown) that generated a Q-expression. Then quadwriter/markup showed a mid-level notation that generated another (identical) Q-expression.

If we wish, we can also skip the notational foofaraw and just write Q-expressions directly in our source file. We do this with the basic quadwriter language.

Recall our very first example:

"test.rkt"
#lang quadwriter/markup
Brennan and Dale like fancy sauce.

In the REPL, the doc was this Q-expression:

'(q () (q ((page-margin-left "120") (page-margin-top "80") (page-margin-bottom "120") (font-family "text") (line-height "17")) "Brennan and Dale like fancy sauce."))

Let’s copy this Q-expression and use it as our new source code. This time, however, we’ll switch to plain #lang quadwriter (instead of the markup or markdown dialects):

"test.rkt"
#lang quadwriter
'(q () (q ((page-margin-left "120") (page-margin-top "80")
(page-margin-bottom "120") (font-family "text")
(line-height "17")) "Brennan and Dale like fancy sauce."))

This produces the same one-line PDF as before.

Likewise, we can pick up the doc from our more complex example:

#lang quadwriter/markdown
# Did you know?
 
__Brennan__ and **Dale** like:
 
* *Fancy* sauce
* _Chicken_ fingers
 
```
And they love to code
```

And again, use the resulting Q-expression in doc as the source for a new quadwriter program, which will result in the same PDF.

4.4 Setting top-level attributes

Even if you’re using a quadwriter dialect, you can still set top-level formatting attributes for the document. For instance, suppose we wanted to make our original quadwriter/markdown example 24 points and red, and put the PDF on wide tabloid (17in × 11in) paper. We can add these top-level attributes to the beginning of our source file as keyword arguments:

"test.rkt"
#lang quadwriter/markdown
 
#:page-size "tabloid"
#:page-orientation "wide"
#:font-size 18
#:font-color "red"
 
Brennan and Dale like fancy sauce.

Any of the Markup attributes documented below can be used as keyword arguments. The syntax follows the pattern above: one attribute + value pair per line, with the attribute prefixed with #: to make it a keyword, followed by the value.

This keyword syntax works in the quadwriter, quadwriter/markdown, and quadwriter/markup languages. The idea is to make it easy to adjust the default layout behavior without going outside the source file.

4.5 Invoking Quadwriter as a library

Part of the idea of quad and quadwriter is to make typographic layout & PDF generation a service that can be built into other Racket apps and languages.

Let’s see how this works by doing document layout and rendering from within good old racket/base:

"test.rkt"
#lang racket/base
(require quadwriter)
(define qx `(q "Brennan likes fancy sauce."
                ,para-break
                "Dale hates fancy sauce."))
(define pdf-path "~/Desktop/new.pdf")
(render-pdf qx pdf-path)

Here, we create a little Q-expression, which we pass to render-pdf with a pdf-path argument.

4.6 Combining Quadwriter with Pollen

Fans of pollen might be glad to hear that quadwriter can be used to handle layout and PDF rendering for Pollen source files. As usual we start with a Pollen source file, this time with the pdf.pm extension to indicate that it’s a Pollen markup file that will produce a PDF:

"test.pdf.pm"
#lang pollen
 
Brennan likes fancy sauce.
 
Dale hates fancy sauce.
 

Then we add a simple "pollen.rkt" that converts the output of our source file into a Q-expression:

"pollen.rkt"
#lang racket
(require pollen/decode quadwriter)
(provide root render-pdf)
 
(define (root . xs)
  `(q ,@(add-between (decode-paragraphs xs 'q) para-break)))

All we’re doing here is wrapping our paragraphs in q tags (rather than the default p tags) and then adding explicit Quadwriter paragraph breaks between them (see para-break).

Finally, we add a "template.pdf.p" that passes the doc from the Pollen source to render-pdf:

"template.pdf.p"
(render-pdf doc #false)

In this case, we pass #false as the path argument to render-pdf so that it returns the actual bytes, which the Pollen renderer will put in the right place.

You can fire up the Pollen project server and see how this works. As usual with Pollen sources, when you make changes to the source file, the rendered PDF will be dynamically updated.

Though a quadwriter source file and a pollen source file both export something called doc, these exports don’t share any deeper connection. (The name was chosen to be consistent with Scribble, which also exports a doc.)

4.7 Quick tour complete

In the usual Racket tradition, quadwriter and its dialects are just compiling a document from a higher-level representation to a lower-level representation.

If you’re a writer, you might prefer to use the high-level representation (like quadwriter/markdown) so that your experience is optimized for ease of use.

If you’re a developer, you might prefer to use the lower-level representation for precision. For instance, a pollen author who wanted to generate a PDF could design tag functions that emit Q-expressions, and then pass the result to render-pdf.

Or, you can aim somewhere in between. Like everything else in Racket, you can design functions & macros to emit the pieces of a Q-expression using whatever interface you prefer.

5 Quadwriter: developer guide

 (require quadwriter) package: quad

 #lang quadwriter package: quad
 #lang quadwriter/markdown
 #lang quadwriter/markup

value

doc : qexpr?

Every source file written in a quadwriter dialect exports an identifier called doc that contains the Q-expression that results from running the source.

5.1 Q-expressions

A Q-expression is an X-expression, but more restricted:

  qexpr = string
  | (list q (list (list attr-name attr-val) ...) qexpr ...)
  | (list q (list qexpr ...))

This grammar means that a Q-expression is either a) a string, b) an X-expression whose tag is q and whose elements are themselves Q-expressions.

Examples:
> (qexpr? "Hello world")

#t

> (qexpr? '(q "Hello world"))

#t

> (qexpr? '(q () "Hello world"))

#t

> (qexpr? '(q ((font-color "pink")) "Hello world"))

#t

> (qexpr? '(q ((font-color "pink")) (q "Hello world")))

#t

; malformed Q-expressions
> (qexpr? 42)

#f

> (qexpr? '(div "Hello world"))

#f

> (qexpr? '(q (("pink" font-color)) "Hello world"))

#f

Because Q-expressions are a subset of X-expressions, you can apply any tools that work with X-expressions (for instance, the txexpr library).

Unlike X-expressions, Q-expressions do not support character entities or CDATA, because those are inherent to XML-ish markup.

5.2 Markup

5.3 Hard breaks

value

line-break : qexpr?

value

column-break : qexpr?

value

page-break : qexpr?

The Q-expressions '(q ((break "line"))), '(q ((break "column"))), and '(q ((break "page"))), respectively. Quadwriter will automatically insert these breaks as needed. But you can also add them explicitly (aka “hard” breaks) by inserting the Q-expression denoting the break.

value

para-break : qexpr?

The Q-expression '(q ((break "para"))). Used to denote the start of a new paragraph.

5.4 Attributes

These are the attributes that can be used inside a Q-expression passed to quadwriter. Inside a Q-expression, every attribute is a symbol, and every attribute value is a string.

A dimension string represents a distance in the plane. If unitless, it is treated as points (where 1 point = 1/72 of an inch). If the number has in, cm, or mm as a suffix, it is treated as inches, centimeters, or millimeters respectively.

5.4.1 Page-level attributes

attribute

page-size : symbol?

attribute

page-orientation : symbol?

The usual way of setting the overall page dimensions of the rendered PDF. The value of page-size is a named page size. The value of page-orientation can be either "tall" or "portrait" (which both put the longer edge vertically) or "wide" or "landscape" (which put the longer edge horizontally).

The named page sizes are listed below. Names are case-insensitive. Dimensions below are in points.

name

  

short edge

  

long edge

2A0

  

3370.39

  

4767.87

4A0

  

4767.87

  

6740.79

A0

  

2383.94

  

3370.39

A1

  

1683.78

  

2383.94

A10

  

73.7

  

104.88

A2

  

1190.55

  

1683.78

A3

  

841.89

  

1190.55

A4

  

595.28

  

841.89

A5

  

419.53

  

595.28

A6

  

297.64

  

419.53

A7

  

209.76

  

297.64

A8

  

147.4

  

209.76

A9

  

104.88

  

147.4

B0

  

2834.65

  

4008.19

B1

  

2004.09

  

2834.65

B10

  

87.87

  

124.72

B2

  

1417.32

  

2004.09

B3

  

1000.63

  

1417.32

B4

  

708.66

  

1000.63

B5

  

498.9

  

708.66

B6

  

354.33

  

498.9

B7

  

249.45

  

354.33

B8

  

175.75

  

249.45

B9

  

124.72

  

175.75

C0

  

2599.37

  

3676.54

C1

  

1836.85

  

2599.37

C10

  

79.37

  

113.39

C2

  

1298.27

  

1836.85

C3

  

918.43

  

1298.27

C4

  

649.13

  

918.43

C5

  

459.21

  

649.13

C6

  

323.15

  

459.21

C7

  

229.61

  

323.15

C8

  

161.57

  

229.61

C9

  

113.39

  

161.57

EXECUTIVE

  

521.86

  

756.0

FOLIO

  

612.0

  

936.0

LEGAL

  

612.0

  

1008.0

LETTER

  

612.0

  

792.0

RA0

  

2437.8

  

3458.27

RA1

  

1729.13

  

2437.8

RA2

  

1218.9

  

1729.13

RA3

  

864.57

  

1218.9

RA4

  

609.45

  

864.57

SRA0

  

2551.18

  

3628.35

SRA1

  

1814.17

  

2551.18

SRA2

  

1275.59

  

1814.17

SRA3

  

907.09

  

1275.59

SRA4

  

637.8

  

907.09

TABLOID

  

792.0

  

1224.0

attribute

page-width : symbol?

attribute

page-height : symbol?

The unusual way of setting the overall page dimensions of the rendered PDF. Both values are given as a dimension string.

attribute

page-margin-top : symbol?

attribute

page-margin-bottom : symbol?

attribute

page-margin-left : symbol?

attribute

page-margin-right : symbol?

Inset values from the page edges. Value is given as a dimension string. Default values depend on size of the page: they are chosen to be not completely bananas.

attribute

page-number-start : symbol?

First page number used in document.

attribute

column-count : symbol?

attribute

column-gap : symbol?

Columns per page. column-count is a positive integer; column-gap (the space between columns) is a dimension string.

5.4.2 Block-level attributes

A block is a paragraph or other rectangular item (say, a blockquote or code block) with paragraph breaks around it.

attribute

inset-top : symbol?

attribute

inset-bottom : symbol?

attribute

inset-left : symbol?

attribute

inset-right : symbol?

Inset values increase the layout boundary of the quad. Value is given as a dimension string. "0" by default.

attribute

border-inset-top : symbol?

attribute

border-inset-bottom : symbol?

attribute

border-inset-left : symbol?

attribute

border-inset-right : symbol?

Border-inset values do not change the layout boundary of the quad. Rather, they change the position of the border (if any) relative to the layout boundary. Value is given as a dimension string. "0" by default (meaning, the border sits on the layout boundary).

attribute

border-width-top : symbol?

attribute

border-width-bottom : symbol?

attribute

border-width-left : symbol?

attribute

border-width-right : symbol?

Width of the border on each edge of the quad. Value is given as a dimension string. "0" by default (meaning no border).

attribute

border-color-top : symbol?

attribute

border-color-bottom : symbol?

attribute

border-color-left : symbol?

attribute

border-color-right : symbol?

Color of the border on each edge of the quad. Value is a hex color string or named color string.

attribute

background-color : symbol?

Color of the background of the quad. Value is a hex color string or named color string.

attribute

space-before : symbol?

attribute

space-after : symbol?

Vertical space added around a block. Value is given as a dimension string.

attribute

keep-first-lines : symbol?

attribute

keep-last-lines : symbol?

attribute

keep-all-lines : symbol?

How many lines of the quad are kept together near a page break. keep-first-lines sets the minimum number of lines that appear before a page break; keep-last-lines sets the minimum number that appear after. In bother cases, they take a non-negative integer string as a value.

keep-all-lines keeps all the lines of a quad on the same page. Activated only when value is "true". Be careful with this option — it’s possible to make a single quad that is longer than one page, in which case quadwriter will ignore the setting to prevent an impossible situation.

attribute

keep-with-next : symbol?

Whether a quad appears on the same page with the following quad. Activated only when value is "true". Essentially this is the “nonbreaking paragraph space”.

attribute

line-align : symbol?

attribute

line-align-last : symbol?

How the lines are aligned horizontally in the quad. Possibilities are "left", "center", "left", and "justify". line-align-last controls the alignment of the last line; line-align controls the others.

attribute

first-line-indent : symbol?

The indent of the first line in the quad. Value is given as a dimension string.

attribute

line-wrap : symbol?

Selects the linebreak algorithm. A value of "best" or "kp" invokes the Knuth–Plass linebreaking algorithm, which finds the optimal set of linebreaks (defined as the set that gives the most even spacing throughout the paragraph). Otherwise, you get the ordinary linebreak algorithm, which just puts as many words as it can on each line. The Knuth–Plass algorithm is slower, of course.

attribute

hyphenate : symbol?

Whether the block is hyphenated. Activated only when value is "true".

attribute

clip : symbol?

Whether the contents of the block are clipped to its boundary. Activated only when value is "true".

attribute

image-file : symbol?

attribute

image-alt : symbol?

attribute

image-height : symbol?

attribute

image-width : symbol?

Specify a quad with an image (either ".png" or ".jpeg"). image-file is a string containg the path to the image file. image-alt is optional text.

image-height and image-width are optional sizing values, each of which is a dimension string. If neither image-height nor image-width are provided, the image is displayed at “full size” (meaning one pixel = one point, or 72 dpi). If both image-height and image-width are provided, the image is displayed at exactly that size. If only image-height or image-width is provided, the image is scaled by the proportion implied by the value. That is, if image-height is "50" and the image is 200 pixels high by 100 pixels wide, then the image will be displayed 50 pixels high by 25 pixels wide.

5.4.3 Other attributes

attribute

font-size : symbol?

attribute

font-size-adjust : symbol?

Two ways of setting the point size for text. font-size takes a dimension string. font-size-adjust takes a string representing a percentage (like "120%" or "1.2") and sets the font size to the size of the parent, multiplied by the percentage.

attribute

font-family : symbol?

Name of the font family. Value is a string with the font-family name. See Fonts for where these names come from.

attribute

font-color : symbol?

The color of the rendered font. Value is a hex color string or named color string.

attribute

font-bold : symbol?

Whether the quad has bold styling applied. Activated only when value is "true".

attribute

font-italic : symbol?

Whether the quad has italic styling applied. Activated only when value is "true".

attribute

font-features : symbol?

attribute

font-features-adjust : symbol?

Two ways of setting OpenType layout features. font-features takes a feature string, which is an alternating list of OT feature tags and values, separated by white space. For instance, "liga 0 smcp 1" would deactivate the ligature feature and activate the small-cap feature. font-features-adjust works the same way, but rather than replacing the current feature settings, it amends them.

Fonts with OpenType layout features may be configured so that certain features, like ligatures, are activated by default. Your font will display these layout features even though there is no font-features attribute in your Q-expression. You can, however, still turn them off with font-features.

attribute

font-tracking : symbol?

Space between characters. Value is given as a dimension string.

attribute

font-baseline-shift : symbol?

Vertical offset of font baseline (positive values move the baseline up, negative down). Value is given as a dimension string.

attribute

line-height : symbol?

Distance between baselines. Value is a dimension string.

TK: OT feature attributes, bullet attributes

5.5 Rendering

procedure

(render-pdf qx pdf-path [#:replace replace?])  (or/c void? bytes?)

  qx : qexpr?
  pdf-path : (or/c path? path-string? #false)
  replace? : any/c = #true
Compute the layout for qx and render it as a PDF to pdf-path. If pdf-path is #false, then the rendered PDF is returned as a byte string. Otherwise it is written to pdf-path.

The optional replace? argument controls whether an existing file is automatically overwritten. The default is #true.

5.6 Fonts

A design goal of Quadwriter is to treat document layout as the result of a program. Along those lines, fonts are handled differently than usual. When you use a word processor, you choose from whatever fonts might be installed on your system.

Quadwriter, by contrast, relies only on fonts that are in the same directory as your other project source files. This is a feature: it means that everything necessary to render the document travels together in the same directory. You can re-render it anywhere with identical results. You never have the problem — still with us after 35 years of desktop word processing — that “oh, you need to install such-and-such font in your system before it will work.” Bah!

Quadwriter supports the usual TrueType (.ttf) and OpenType (.otf) font files. It also supports WOFF files (.woff). To add fonts to your Quadwriter experience:

  1. Within your project directory, create a subdirectory called "fonts".

  2. Within "fonts", create a subdirectory for each font family you want to use in your Quadwriter document. The names of these subdirectories will become the acceptable values for the font-family attribute in your documents.

  3. If there is only one font file in the family subdirectory, then it is used every time the font family is requested.

  4. Alternatively, you can specify styled variants by creating within the family directory style subdirectories called "regular", "bold", "italic", and "bold-italic".

Though this system may seem like a lot of housekeeping, it’s nice for two reasons. First, we use the filesystem to map font names to font files, and avoid having another configuration file floating around our project. Second, we create a layer of abstraction between font names and files. This makes it easy to change the fonts in the document: you just put new fonts in the appropriate font-family directory, and you don’t need to faff about with the source file itself.

TK: example of font setup

5.6.1 Default font families

Quadwriter typesets documents by looking up families with the names below. You can override the default selections by providing a family in the fonts folder of your project that has the same name.

font directory

text : path-string?

Used for all body text. Default is a serif font.

font directory

heading : path-string?

Used for headings. Default is a sans serif font..

font directory

code : path-string?

Used for code. Default is a monospaced font.

font directory

blockquote : path-string?

Used for blockquote boxes. Defaults is a sans serif font.

font directory

default : path-string?

Used for any miscellaneous elements. Default is same as text.

font directory

fallback-emoji : path-string?

Fallback only. Used for emoji not present in the currently selected font.

font directory

fallback-math : path-string?

Fallback only. Used for math symbols not present in the currently selected font.

font directory

fallback : path-string?

Fallback only. Used for other glyphs not present in the currently selected font.

5.7 Colors

A hex color is a case-insensitive string of six hex digits prefixed with #, such as "#fe456a" or "#cc6633". The pairs of digits represent the red, green, and blue components of the color respectively, each pair taking on hex values between 0 ("00") and 255 (ff), inclusive. As optional shorthand, a three-digit hex color such as "#c63" is equivalent to "#cc6633".

A named color is a hex color with a pre-existing name.

name

  

hex color equivalent

aliceblue

  

#f0f8ff

antiquewhite

  

#faebd7

aqua

  

#00ffff

aquamarine

  

#7fffd4

azure

  

#f0ffff

beige

  

#f5f5dc

bisque

  

#ffe4c4

black

  

#000000

blanchedalmond

  

#ffebcd

blue

  

#0000ff

blueviolet

  

#8a2be2

brown

  

#a52a2a

burlywood

  

#deb887

cadetblue

  

#5f9ea0

chartreuse

  

#7fff00

chocolate

  

#d2691e

coral

  

#ff7f50

cornflowerblue

  

#6495ed

cornsilk

  

#fff8dc

crimson

  

#dc143c

cyan

  

#00ffff

darkblue

  

#00008b

darkcyan

  

#008b8b

darkgoldenrod

  

#b8860b

darkgray

  

#a9a9a9

darkgreen

  

#006400

darkgrey

  

#a9a9a9

darkkhaki

  

#bdb76b

darkmagenta

  

#8b008b

darkolivegreen

  

#556b2f

darkorange

  

#ff8c00

darkorchid

  

#9932cc

darkred

  

#8b0000

darksalmon

  

#e9967a

darkseagreen

  

#8fbc8f

darkslateblue

  

#483d8b

darkslategray

  

#2f4f4f

darkslategrey

  

#2f4f4f

darkturquoise

  

#00ced1

darkviolet

  

#9400d3

deeppink

  

#ff1493

deepskyblue

  

#00bfff

dimgray

  

#696969

dimgrey

  

#696969

dodgerblue

  

#1e90ff

firebrick

  

#b22222

floralwhite

  

#fffaf0

forestgreen

  

#228b22

fuchsia

  

#ff00ff

gainsboro

  

#dcdcdc

ghostwhite

  

#f8f8ff

gold

  

#ffd700

goldenrod

  

#daa520

gray

  

#808080

green

  

#008000

greenyellow

  

#adff2f

grey

  

#808080

honeydew

  

#f0fff0

hotpink

  

#ff69b4

indianred

  

#cd5c5c

indigo

  

#4b0082

ivory

  

#fffff0

khaki

  

#f0e68c

lavender

  

#e6e6fa

lavenderblush

  

#fff0f5

lawngreen

  

#7cfc00

lemonchiffon

  

#fffacd

lightblue

  

#add8e6

lightcoral

  

#f08080

lightcyan

  

#e0ffff

lightgoldenrodyellow

  

#fafad2

lightgray

  

#d3d3d3

lightgreen

  

#90ee90

lightgrey

  

#d3d3d3

lightpink

  

#ffb6c1

lightsalmon

  

#ffa07a

lightseagreen

  

#20b2aa

lightskyblue

  

#87cefa

lightslategray

  

#778899

lightslategrey

  

#778899

lightsteelblue

  

#b0c4de

lightyellow

  

#ffffe0

lime

  

#00ff00

limegreen

  

#32cd32

linen

  

#faf0e6

magenta

  

#ff00ff

maroon

  

#800000

mediumaquamarine

  

#66cdaa

mediumblue

  

#0000cd

mediumorchid

  

#ba55d3

mediumpurple

  

#9370db

mediumseagreen

  

#3cb371

mediumslateblue

  

#7b68ee

mediumspringgreen

  

#00fa9a

mediumturquoise

  

#48d1cc

mediumvioletred

  

#c71585

midnightblue

  

#191970

mintcream

  

#f5fffa

mistyrose

  

#ffe4e1

moccasin

  

#ffe4b5

navajowhite

  

#ffdead

navy

  

#000080

oldlace

  

#fdf5e6

olive

  

#808000

olivedrab

  

#6b8e23

orange

  

#ffa500

orangered

  

#ff4500

orchid

  

#da70d6

palegoldenrod

  

#eee8aa

palegreen

  

#98fb98

paleturquoise

  

#afeeee

palevioletred

  

#db7093

papayawhip

  

#ffefd5

peachpuff

  

#ffdab9

peru

  

#cd853f

pink

  

#ffc0cb

plum

  

#dda0dd

powderblue

  

#b0e0e6

purple

  

#800080

red

  

#ff0000

rosybrown

  

#bc8f8f

royalblue

  

#4169e1

saddlebrown

  

#8b4513

salmon

  

#fa8072

sandybrown

  

#f4a460

seagreen

  

#2e8b57

seashell

  

#fff5ee

sienna

  

#a0522d

silver

  

#c0c0c0

skyblue

  

#87ceeb

slateblue

  

#6a5acd

slategray

  

#708090

slategrey

  

#708090

snow

  

#fffafa

springgreen

  

#00ff7f

steelblue

  

#4682b4

tan

  

#d2b48c

teal

  

#008080

thistle

  

#d8bfd8

tomato

  

#ff6347

turquoise

  

#40e0d0

violet

  

#ee82ee

wheat

  

#f5deb3

white

  

#ffffff

whitesmoke

  

#f5f5f5

yellow

  

#ffff00

yellowgreen

  

#9acd32

5.8 Utility

procedure

(view-result)  void?

On the REPL, after running a quadwriter dialect and generating a PDF, this function will open the PDF.

6 Quad: the details

 (require quad) package: quad

As mentioned above, The quad library itself knows as little as it can about typography and fonts and pictures. Nor does it even assert a document model like Scribble. Rather, it offers a generic geometric represntation of layout elements. In turn, these elements can be combined into more useful pieces (e.g., quadwriter).

6.1 Data model: the quad

The eponymous quad is a structure type that represents a rectangular layout area. This rectangle is used for layout purposes only. It is not enforced during the rendering phase. Meaning, once positioned, a quad’s drawing function can access this rectangle, but does not need to stay within it.

Each quad has nested elements, which is a (possibly empty) list of subquads. Given a certain element, the quad containing it is called its parent quad.

Quads can be freely nested. There are no rules about what kind of quad can be nested in another.

6.2 Wrapping

Wrapping is a optional phase where lists of quads are broken into sublists of a certain size. In quadwriter, the list of words is wrapped to produce a list of lines of a certain horizontal width. In turn, the list of lines is wrapped to produce a list of pages of a certain vertical height.

6.3 Layout

The heart of Quad’s layout logic is its system of anchor points. A quad is positioned in a layout by aligning its anchor point to an anchor point on the previous quad.

Each quad has a set of 11 anchor points on its perimeter.

Eight points are named for the compass directions: 'n (= top center) 'e (= right center) 's (= bottom center) 'w (= left ceter) 'ne (= upper right) 'se (= lower right) 'sw (= lower left) 'nw (= upper left).

The center of the quad is 'c.

The other two anchor points are 'baseline-in and 'baseline-out or just 'bi and 'bo. These points are also on the quad perimeter. They allow quads containing type to be aligned according to adjacent baselines. The exact location of these points depends on the direction of the script. For instance, in left-to-right languages, 'baseline-in is on the left edge, and 'baseline-out is on the right. The vertical position of these points depends on the font associated with the quad. If no font is specified, the 'bi and 'bo points are vertically positioned at the southern edge.

By default, each subquad will ultimately be positioned relative to the immediately preceding subquad (or, if it’s the first subquad, the parent). Optionally, a subquad can attach to the parent.

How does a quad know which anchor points to use? Each quad specifies a to anchor on its own perimeter, and a from anchor on the previous quad’s perimeter. The quad is positioned by moving it until its to anchor matches the position of the (already positioned) from anchor. Think of it like two tiny magnets clicking together.

A key benefit of the anchor-point system is that it gets rid of notions of “horizontal”, “vertical”, “up”, “down”, etc. Quads flow in whatever direction is implied by their anchor points.

> (define q1 (make-quad #:size '(25 25)))
> (define q2 (make-quad #:size '(15 15)))
> (quad->pict (position (attach-to q1 'e q2 'w)))

image

> (quad->pict (position (attach-to q1 'nw q2 'se)))

image

> (quad->pict (position (attach-to q1 'w q2 'e)))

image

> (quad->pict (position (attach-to q1 's q2 'n)))

image

> (quad->pict (position (attach-to q1 'e q2 'n)))

image

“Wait a minute — why is the new quad specifying both anchor points? Shouldn’t the from anchor be specified by the previous quad?” It could, but it would make the layout system less flexible, because all the subquads hanging onto a certain quad would have to emanate from a single point. This way, every subquad can attach to its neighbor (or the parent) in whatever way it prefers.

6.4 Rendering

Once the quads have been positioned, they are passed to the renderer, which recursively visits each quad and calls its drawing function.

Though every quad has a size field, this is just the size used during layout and positioning. Quad doesn’t know (or care) about whether the drawing stays within those bounds.

7 What are your plans for Quad?

Some things I personally plan to use Quad for:

  1. A simple word processor. Quadwriter is the demo of this.

  2. Font sample documents. In my work as a type designer, I have to put together PDFs of fonts. To date, I have done them by hand, but I would like to just write programs to generate them.

  3. Racket documentation. The PDFs of Racket documentation are currently generated by LaTeX. I would like to make Quad good enough to handle them.

  4. Book publishing. My wife is a lawyer and wants to publish a book about a certain area of the law that involves a zillion fiddly charts. If I had to do it by hand, it would take months. But with a Quad program, it could be easy.

7.1 Why is it called Quad?

In letterpress printing, a quad was a piece of metal used as spacing material within a line.




“A way of doing something original is by trying something so painstaking that nobody else has ever bothered with it.” — Brian Eno