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.
raco pkg install quad
raco pkg update quad
Or, without the command line: Launch DrRacket. Use the File → Install Package ... command to install quad.
If you’re new to Racket and want to configure your system to use the terminal commands, follow the instructions here.
A document processor, which means that it:
Computes the layout of your document from a series of formatting codes (not unlike a web browser)
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.
Quad produces PDFs using three ingredients:
A font engine that handles glyph shaping and positioning using standard TTF or OTF font files.
A layout engine that converts typesetting instructions into an output-independent layout — e.g., putting characters into lines, and lines into pages.
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).
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.
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:
Next, on the REPL enter this:
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.
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:
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:
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:
To see this:
((page-margin-left "120") (page-margin-top "80") (page-margin-bottom "120") (font-family "text") (line-height "17"))
(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?")
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).
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:
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:
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:
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.
Recall our very first example:
'(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 ```
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:
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.
Let’s see how this works by doing document layout and rendering from within good old racket/base:
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:
Then we add a simple "pollen.rkt" that converts the output of our source file into a Q-expression:
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).
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.)
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.
|(require quadwriter)||package: quad|
|#lang quadwriter||package: quad|
doc : qexpr?
A Q-expression is an X-expression, but more restricted:
||||(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.
> (qexpr? "Hello world")
> (qexpr? '(q "Hello world"))
> (qexpr? '(q () "Hello world"))
> (qexpr? '(q ((font-color "pink")) "Hello world"))
> (qexpr? '(q ((font-color "pink")) (q "Hello world")))
; malformed Q-expressions > (qexpr? 42)
> (qexpr? '(div "Hello world"))
> (qexpr? '(q (("pink" font-color)) "Hello world"))
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.
para-break : qexpr?
section-break : qexpr?
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).
|(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.
A text-drawing quad will also inherit the current Font attributes, which can also be set separately.
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.
Attributes that can only be set once for the whole document.
output-path : symbol?
Attributes that can be set for each section.
page-margin-gutter : symbol?
page-number-start : symbol?
page-side-start : symbol?
footer-display : symbol?
footer-text : symbol?
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".
background-color : symbol?
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.
keep-with-next : symbol?
line-align : symbol?
"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.
first-line-indent : symbol?
line-wrap : symbol?
hyphenate : symbol?
clip : symbol?
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.
repeat : symbol?
parent : symbol?
anchor-from-parent : symbol?
anchor-to : symbol?
font-size : symbol?
font-family : symbol?
font-color : symbol?
font-bold : symbol?
font-italic : symbol?
font-underline : symbol?
font-features : symbol?
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.
font-tracking : symbol?
font-baseline-shift : symbol?
font-case : symbol?
line-height : symbol?
draw-debug : symbol?
TK: OT feature attributes, bullet attributes
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:
this: refers to the current target, relative to the location of the quad in the layout. So "page[this]" means the current page, and "section[this]" means the current section.
prev and next: respectively refer to the previous or next target, relative to the location of the quad in the layout. So "page[prev]" means the page before the current page, and "section[next]" means the section after the current one.
a positive integer, which is interpreted as the nth item (counting from 1) within the previous quad in the query. So the second page of the document would be "doc[this]:page".
a negative integer, which is interpreted as the nth item (counting from the end) within the previous quad in the query. So the next-to-last page of the current section would be "section[this]:page[-2]".
i..j, where i and j are positive or negative integers, with the meanings given above. The .. in between means the range of items from the ith to the jth, inclusive. So the second through fourth pages of the previous section would be "section[prev]:page[2..4]".
first and last, which are the same as 1 and -1, respectively.
all or *, which refers to all matching items. So all the pages in the previous section would be "section[prev]:pages[all]" or "section[prev]:pages[*]". All the lines on the third page of every section would be "section[*]:page:line[*]".
rest, which refers to all items after the first. So all the pages in the document that do not fall on the first page of a section would be "section[*]:page[rest]".
odd or even, which respectively refer to all the items with an odd or even numerical subscript. So if a page has five lines, "page[this]:line[odd]" would refer to the first, third, and fifth, and "page[this]:line[even]" to the second and fourth.
TK: querying quads by name, and left and right
(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
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.
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:
Within your project directory, create a subdirectory called "fonts".
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.
If there is only one font file in the family subdirectory, then it is used every time the font family is requested.
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
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.
text : path-string?
heading : path-string?
code : path-string?
blockquote : path-string?
default : path-string?
fallback-emoji : path-string?
fallback-math : path-string?
fallback : path-string?
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.
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".
hex color equivalent
(view-output) → void?
|(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).
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.
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.
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.
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))) > (quad->pict (position (attach-to parent 'nw child 'se))) > (quad->pict (position (attach-to parent 'w child 'e))) > (quad->pict (position (attach-to parent 's child 'n))) > (quad->pict (position (attach-to parent 'e child 'n)))
“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.
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.
Some things I personally plan to use Quad for:
A simple word processor. Quadwriter is the demo of this.
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.
Racket documentation. The PDFs of Racket documentation are currently generated by LaTeX. I would like to make Quad good enough to handle them.
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.
Questions about Quad and Quadwriter can be posted in the pollen-users discussion area.
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