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 & HTML
4.3 Quadwriter & Q-expressions
4.4 Setting section-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
section-break
5.4 Drawing quads
line
text
5.5 Attributes
5.5.1 Document-level attributes
output-path
pdf-title
pdf-author
pdf-subject
pdf-keywords
5.5.2 Section-level attributes
page-size
page-orientation
page-width
page-height
page-margin-top
page-margin-bottom
page-margin-left
page-margin-right
page-margin-gutter
page-number-start
page-side-start
column-count
column-gap
footer-display
footer-text
5.5.3 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-with-next
line-align
first-line-indent
line-wrap
hyphenate
clip
image-file
image-alt
image-height
image-width
5.5.4 Positioning attributes
repeat
parent
anchor-from-parent
anchor-to
5.5.5 Font attributes
font-size
font-family
font-color
font-bold
font-italic
font-underline
font-features
font-tracking
font-baseline-shift
font-case
5.5.6 Other attributes
display
line-height
draw-debug
5.6 Query strings
5.7 Rendering
render-pdf
5.8 Fonts
5.8.1 Default font families
text
heading
code
blockquote
default
fallback-emoji
fallback-math
fallback
5.8.2 System fonts
5.9 Colors
5.10 Utility
view-output
6 Quad:   the details
6.1 Data model:   the quad
6.2 Wrapping
6.3 Layout model
6.4 Rendering
7 What are your plans for Quad?
7.1 Getting more help
7.2 Why is it called Quad?
8.12

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-output)

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 "1em") (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-output.

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"))
(para-break)
(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 & HTML🔗ℹ

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

"test.rkt"
#lang quadwriter/html
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/html
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/html 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/html 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/html
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.

It is also possible to mix #lang quadwriter/html and Q-expressions, allowing us to escape to lower-level Q-expressions when needed.

#lang quadwriter/html
 
h1{A nice image}
 
Check out this b{nice} image:
 
q[#:line-height "false" #:image-width "400" #:line-align "center"
   #:image-file "nice-image.png" #:image-alt "A nice image"
   #:display "block"]{}

4.4 Setting section-level attributes🔗ℹ

Even if you’re using a quadwriter dialect, you can still set section-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 section-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/html 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/html

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"))

#t

> (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 '(line-break), '(column-break), and '(page-break), 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 '(para-break). Used to denote the start of a new paragraph. The default space-before a paragraph is 0; the default space-after is 60% of the paragraph’s line-height.

value

section-break : qexpr?

The Q-expression '(section-break). Used to denote the start of a new section.

A section is a contiguous series of pages. Each section has its own Section-level attributes. A document without any explicit section breaks still has one section (that includes all the pages).

5.4 Drawing quads🔗ℹ

 (require quadwriter/draw) package: quad

Drawing quads can be used to put arbitrary text or shapes in the document, either in the midst of the text flow, or at arbitrary locations. Drawing quads can be used to implement headers and footers, line numbers, title pages, and so on.

value

line : qexpr?

Draws a line. Four attributes are required: x1 and y1 (which determine the starting point) and x2 and y2 (which determine the ending point). Each of these values is a dimension string. These distances are interpreted as relative to the upper left corner of the quad.

Optional attributes are stroke (a dimension string that controls the thickness of the line) and color (a a hex color string or named color string that controls the color of the line).

value

text : qexpr?

Draws a single text string without line wrapping. One attribute is required: string, which is the string to be drawn.

A text-drawing quad will also inherit the current Font attributes, which can also be set separately.

5.5 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. If the number has p or pica as a suffix or infix, it is treated as a pica / point measurement. A pica is 12 points, so 12p is 72 points, and 3p9 is 45 points. If the number has em as a suffix, it is treated as an em measurement, which is a multiple of the current font size.

5.5.1 Document-level attributes🔗ℹ

Attributes that can only be set once for the whole document.

attribute

output-path : symbol?

Output path for the rendered PDF. Default is the name of the source file with its extension changed to .pdf. For instance, "my-source.rkt" would become "my-source.pdf". Unsaved source files are rendered as "untitled.pdf".

attribute

pdf-title : symbol?

attribute

pdf-author : symbol?

attribute

pdf-subject : symbol?

attribute

pdf-keywords : symbol?

Strings that are used to fill in the corresponding PDF metadata fields. Default for each is the empty string.

5.5.2 Section-level attributes🔗ℹ

Attributes that can be set for each section.

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 a dimension string. Default values depend on size of the page: they are chosen to be not completely bananas.

attribute

page-margin-gutter : symbol?

Extra space added to the inner margin of page. Value is a dimension string. On right-hand pages, the gutter will be added to the left margin. On left-hand pages, it will be added to the right margin. Default is 0.

attribute

page-number-start : symbol?

First page number used. Value is an integer. Default is 1.

attribute

page-side-start : symbol?

Side that first page appears on. Value is "left", "right", or "next". Default is "right". If the value is "left" or "right", a blank page will be inserted if necessary.

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.

attribute

footer-display : symbol?

Whether footer is displayed. Default is "false". Footer is suppressed if this value is "none" or "false".

attribute

footer-text : symbol?

Text displayed in footer. Default is "false", which will lead to default text being used for the footer (= a combination of page number, document name, and time / date).

5.5.3 Block-level attributes🔗ℹ

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

Block-level attributes are ignored unless the quad is a block-level element. To explicitly promote a quad to a block-level element, use the display attribute with value "block".

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 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 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 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 a dimension string.

attribute

keep-first-lines : symbol?

attribute

keep-last-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 both cases, they take a non-negative integer string as a value, or "all".

If the value (of one or both attributes) is "all", then all the lines of the quad are kept on the same page. Be careful with this option — it’s possible to make a single quad that’s longer than one page, in which case quadwriter will ignore the setting to prevent the annihilation of the universe.

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?

How the lines are aligned horizontally in the quad. Possibilities are "left", "center", "left", "justify", "inner", and "outer".

"inner" and "outer" align the line toward (or away from) the gutter. So on right-hand pages, "inner" alignment is the same as "left", and "outer" is the same as "right". On left-hand pages, vice versa.

The last line of a paragraph with "justify" alignment will only be justified if the space left over is reasonably small. Otherwise it will be left-aligned. This is because the last line of the paragraph may only have a few words on it.

attribute

first-line-indent : symbol?

The indent of the first line in the quad. Value is 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.5.4 Positioning attributes🔗ℹ

attribute

repeat : symbol?

Moves the quad from its existing position and repeats it within the document according to the attribute value, which is a query string like "section[this]:page[*]". This attribute is resolved before parent, so a parent value like "page[this]:line[first]" will refer to the first line on the page that the quad has landed on as a result of the repeat operation.

attribute

parent : symbol?

Moves the quad from its existing position in the layout into a new parent quad. Value is a query string like "page[this]:line[2]".

attribute

anchor-from-parent : symbol?

Forces the quad to attach to its parent using the designated anchor on the parent. Value is an anchor string. See Layout model for an explanation of the anchoring system.

attribute

anchor-to : symbol?

Forces the current quad to attach to its antecedent using the designated anchor on the current quad. Value is an anchor string. See Layout model for an explanation of the anchoring system.

5.5.5 Font attributes🔗ℹ

attribute

font-size : symbol?

Sets the point size for text. Value is a dimension string.

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-underline : symbol?

Whether the quad has an underline applied. Activated only when value is "true".

attribute

font-features : symbol?

Sets 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. If the feature string is prefixed with "+", rather than replacing the current feature settings, it amends the features that would otherwise apply.

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 a dimension string.

attribute

font-baseline-shift : symbol?

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

attribute

font-case : symbol?

Case transformation of string. Possibilities are "uppercase", "lowercase", or "capitalize" (= first letter of each word is uppercase, the rest is lowercase).

5.5.6 Other attributes🔗ℹ

attribute

display : symbol?

Sets the display type. Value is a string. Supply "block" as a value of this attribute to make the quad behave as a block-level element.

attribute

line-height : symbol?

Sets the distance between baselines. Value is a dimension string.

attribute

draw-debug : symbol?

Controls whether debug boxes are drawn. Value can be "false" (default) or "true".

TK: OT feature attributes, bullet attributes

5.6 Query strings🔗ℹ

Certain quad attributes (like repeat and parent) accept a query string as a value. A query string lets us refer to quads that will eventually exist in the layout (like lines, pages, and sections) while we’re still in the markup.

A query string consists of query pieces chained together with colons : in between. Each query piece has a target and a subscript.

A query target can be doc, section, page, column, block, or line, referring to the corresponding layout entities.

A query subscript follows the query target in square brackets. Some subscripts refer to single quads; some refer to multiples. If any query piece contains a subscript that refers to multiple quads, then the result of the whole query will be a list of quads, or #false if no matches were found. Similarly, if no subscript refers to multiple quads, then the result of the whole query will be a single quad, or #false if no match is found.

Possible query subscripts are:

TK: querying quads by name, and left and right

5.7 Rendering🔗ℹ

procedure

(render-pdf qx    
  [pdf-path    
  base-dir    
  #:replace replace?    
  #:compress compress?])  (or/c void? bytes?)
  qx : qexpr?
  pdf-path : (or/c path? path-string? #false) = #false
  base-dir : (or/c path? path-string?) = (current-directory)
  replace? : any/c = #true
  compress? : 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 default is #false.

The optional base-dir argument sets a base directory for resolution of any relative path names passed as attribute values. The default is (current-directory).

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

The optional compress? argument controls whether data inside the resulting PDF is compressed. The default is #true.

5.8 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, prefers to rely 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 can travel 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".

  5. Oh, you dislike making subdirectories? Then you can also specify styled variants by putting a set of four font files at the top level of the family subdirectory whose filenames include "bold" and "italic" (denoting the bold italic style), "bold" (denoting bold), "italic" (denoting italic), and neither "bold" nor "italic" (denoting regular).

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.8.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. Default 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.8.2 System fonts🔗ℹ

Yes, if you’re feeling lazy, you can use the name of a built-in system font family in any field that takes a family name, and Quad will comply.

If you do this, bear in mind that your source file will no longer necessarily be portable between systems, because it depends on a certain font already being available on that system. (Portable = one can run the source file on a different machine and get an equivalent result.) I include this option because I can imagine plenty of uses for Quad where ease outweighs portability. In which case, have at it.

The concern about portability pertains only to the source file. Once you generate an output PDF from the source file, the PDF itself will always be portable. Regardless of whether you invoke a system font or project font in your source file, the font will be embedded within the PDF. The PDF will then display correctly on any platform, with the correct fonts.

5.9 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.10 Utility🔗ℹ

procedure

(view-output)  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. There are no restrictions on 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 model🔗ℹ

The heart of Quad’s layout model 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 center) '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 typeset text to be aligned according to adjacent baselines. The exact location of these points depends on the direction of the script and the internal ascender value of the font. 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 'baseline-in and 'baseline-out anchors are vertically aligned with the southern edge. In that case, again supposing a left-to-right language, they would occupy the same positions as 'sw and 'se.

(As an attribute value, these anchor points become anchor strings: "n" or "e" or "bi" and so on.)

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 parent (make-quad #:size '(25 25)))
> (define child (make-quad #:size '(15 15)))
> (quad->pict (position (attach-to parent 'e child 'w)))

image

> (quad->pict (position (attach-to parent 'nw child 'se)))

image

> (quad->pict (position (attach-to parent 'w child 'e)))

image

> (quad->pict (position (attach-to parent 's child 'n)))

image

> (quad->pict (position (attach-to parent 'e child 'n)))

image

“Wait a minute — why is the child quad specifying both anchor points? Shouldn’t the from anchor be specified by the parent quad?” It could, but it would make the layout system less flexible, because all the child quads hanging onto a certain parent quad would have to emanate from a single point. This way, every child quad can attach to its parent (or its neighbor) 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. 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 Getting more help🔗ℹ

Questions about Quad and Quadwriter can be posted in the Quad forum.

7.2 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