#lang scribble/manual @(require racket/runtime-path scribble/example quadwriter pitfall/page pitfall/color racket/format (for-label txexpr (except-in pollen #%module-begin) xml racket/base racket/draw quadwriter) pollen/scribblings/mb-tools quad/pict) @(define my-eval (make-base-eval)) @(my-eval `(require quad quad/pict)) @title[#:style 'toc]{Quad: document processor} @author[(author+email "Matthew Butterick" "mb@mbtype.com")] @(define-runtime-path quads.png "quads.png") @(image quads.png #:scale 0.4) @margin-note{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.} @section{Installing Quad & Quadwriter} At the command line: @verbatim{raco pkg install quad} After that, you can update the package like so: @verbatim{raco pkg update quad} Or, without the command line: Launch Dr­Racket. Use the @onscreen{File} → @onscreen{Install Package ...} command to install @racketmodname[quad]. Either way, @racketmodname[quadwriter] is installed as part of the @racketmodname[quad] package. @margin-note{If you're new to Racket and want to configure your system to use the terminal commands, follow the @link["https://beautifulracket.com/setup.html#full-setup"]{instructions here}.} @section{What is Quad?} A document processor, which means that it: @itemlist[#:style 'ordered @item{Computes the layout of your document from a series of formatting codes (not unlike a web browser)} @item{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 @link["http://github.com/foliojs/"]{FolioJS} by Devon Govett. I thank Mr. Govett for figuring out a lot of details that would've made me squeal in agony. @subsection{How does Quad work?} Quad produces PDFs using three ingredients: @itemlist[#:style 'ordered @item{A @bold{font engine} that handles glyph shaping and positioning using standard TTF or OTF font files.} @item{A @bold{layout engine} that converts typesetting instructions into an output-independent layout — e.g., putting characters into lines, and lines into pages.} @item{A @bold{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 @racketmodname[racket/draw]. In particular, Quad completely ignores Racket's PDF-drawing functions, which are provided by @link["https://www.pango.org/"]{Pango}, because of major shortcomings in the kind of PDFs it produces (for instance, it doesn't support hyperlinks). @section{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 @link["https://git.matthewbutterick.com/mbutterick/typesetting/src/branch/main/quad"]{Quad repo}. @section{Quadwriter quick tour} Open DrRacket (or whatever editor you prefer) and start a new document with @code{#lang quadwriter/markdown} as the first line: @fileblock["test.rkt" @codeblock|{ #lang quadwriter/markdown Brennan and Dale like fancy sauce. }| ] Save the document. Any place, any name is fine. @onscreen{Run} the document. You'll get REPL output like this: @repl-output{ 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: @terminal{ > (view-output) } Next, on the REPL enter this: @terminal{ > doc } You will see the actual input to Quadwriter, which is called a @tech{Q-expression}: @repl-output{ '(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 @racket[doc] or use @racket[view-output]. @subsection{Quadwriter & Markdown} I @link["https://docs.racket-lang.org/pollen/second-tutorial.html#%28part._the-case-against-markdown%29"]{don't recommend} that writers adopt Markdown for serious projects. But for goofing around, why not. Our first version of @racket["test.rkt"] had one line of plain text: @fileblock["test.rkt" @codeblock|{ #lang quadwriter/markdown Brennan and Dale like fancy sauce. }| ] Behind the scenes, @racketmodname[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: @fileblock["test.rkt" @codeblock|{ #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 @racketmodname[quadwriter/markdown]. But vanilla files should be fine. Back to the demo. Curious characters can do this: @terminal{ > doc } To see this: @repl-output{ '(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 @tech{Q-expression} that the source file produces when it runs and exports via @racket[doc]. This @tech{Q-expression} is passed to Quadwriter for layout and rendering. @margin-note{Yes, you can generate your own @tech{Q-expressions} by other means and pass them to @racketmodname[quadwriter] for layout & rendering. See @racket[render-pdf].} @margin-note{Mac OS note: I have no connection to the @link["https://skim-app.sourceforge.io/"]{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).} @subsection{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 @racket[quadwriter/html] dialect. First we try our simple test: @fileblock["test.rkt" @codeblock|{ #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: @fileblock["test.rkt" @codeblock|{ #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 @litchar{◊} character is called a @deftech{lozenge}. It introduces markup tags. @link["https://docs.racket-lang.org/pollen/pollen-command-syntax.html#%28part._the-lozenge%29"]{Instructions for typing it}, but for now it suffices to copy & paste, or use the @onscreen{Insert Command Char} button in the DrRacket toolbar. Under the hood, the @racketmodname[quadwriter/markdown] dialect is converting the Markdown surface notation into markup tags that look like this. So the @racket[quadwriter/html] dialect just lets us start with those tags. Curious characters can prove that this is so by again typing at the REPL: @terminal{ > doc } This @tech{Q-expression} is exactly the same as the one that resulted with the @racketmodname[quadwriter/markdown] source file. @subsection{Quadwriter & Q-expressions} @racketmodname[quadwriter/markdown] showed high-level notation (= a generous way of describing Markdown) that generated a @tech{Q-expression}. Then @racketmodname[quadwriter/html] showed a mid-level notation that generated another (identical) @tech{Q-expression}. If we wish, we can also skip the notational foofaraw and just write @tech{Q-expressions} directly in our source file. We do this with the basic @racketmodname[quadwriter] language. Recall our very first example: @fileblock["test.rkt" @codeblock|{ #lang quadwriter/html Brennan and Dale like fancy sauce. }| ] In the REPL, the @racket[doc] was this @tech{Q-expression}: @repl-output{ '(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 @tech{Q-expression} and use it as our new source code. This time, however, we'll switch to plain @code{#lang quadwriter} (instead of the @racket[markup] or @racket[markdown] dialects): @fileblock["test.rkt" @codeblock|{ #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 @racket[doc] from our more complex example: @codeblock|{ #lang quadwriter/markdown # Did you know? __Brennan__ and **Dale** like: * *Fancy* sauce * _Chicken_ fingers ``` And they love to code ``` }| And again, use the resulting @tech{Q-expression} in @racket[doc] as the source for a new @racket[quadwriter] program, which will result in the same PDF. It is also possible to mix @code{#lang quadwriter/html} and @tech{Q-expressions}, allowing us to escape to lower-level @tech{Q-expressions} when needed. @codeblock|{ #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"]{} }| @subsection{Setting section-level attributes} Even if you're using a @racketmodname[quadwriter] dialect, you can still set section-level formatting attributes for the document. For instance, suppose we wanted to make our original @racketmodname[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: @fileblock["test.rkt" @codeblock|{ #lang quadwriter/markdown #:page-size "tabloid" #:page-orientation "wide" #:font-size 18 #:font-color "red" Brennan and Dale like fancy sauce. }| ] Any of the @secref{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 @litchar{#:} to make it a keyword, followed by the value. This keyword syntax works in the @racketmodname[quadwriter], @racketmodname[quadwriter/markdown], and @racketmodname[quadwriter/html] languages. The idea is to make it easy to adjust the default layout behavior without going outside the source file. @subsection{Invoking Quadwriter as a library} Part of the idea of @racketmodname[quad] and @racketmodname[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 @racketmodname[racket/base]: @fileblock["test.rkt" @codeblock|{ #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 @tech{Q-expression}, which we pass to @racket[render-pdf] with a @racket[pdf-path] argument. @subsection{Combining Quadwriter with Pollen} Fans of @racketmodname[pollen] might be glad to hear that @racketmodname[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 @racket[pdf.pm] extension to indicate that it's a Pollen markup file that will produce a PDF: @fileblock["test.pdf.pm" @codeblock|{ #lang pollen Brennan likes fancy sauce. Dale hates fancy sauce. }| ] Then we add a simple @racket["pollen.rkt"] that converts the output of our source file into a @tech{Q-expression}: @fileblock["pollen.rkt" @codeblock|{ #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 @racket[q] tags (rather than the default @racket[p] tags) and then adding explicit Quadwriter paragraph breaks between them (see @racket[para-break]). Finally, we add a @racket["template.pdf.p"] that passes the @racket[doc] from the Pollen source to @racket[render-pdf]: @fileblock["template.pdf.p" @codeblock|{ ◊(render-pdf doc #false) }| ] In this case, we pass @racket[#false] as the path argument to @racket[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. @margin-note{Though a @racketmodname[quadwriter] source file and a @racketmodname[pollen] source file both export something called @racket[doc], these exports don't share any deeper connection. (The name was chosen to be consistent with Scribble, which also exports a @racket[doc].)} @subsection{Quick tour complete} In the usual Racket tradition, @racket[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 @racketmodname[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 @racketmodname[pollen] author who wanted to generate a PDF could design tag functions that emit Q-expressions, and then pass the result to @racket[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 @tech{Q-expression} using whatever interface you prefer. @section{Quadwriter: developer guide} @defmodule[quadwriter #:link-target? #f #:no-declare] @defmodulelang*/no-declare[(quadwriter quadwriter/markdown quadwriter/html)] @declare-exporting[quadwriter] @defthing[doc qexpr?]{ Every source file written in a @racketmodname[quadwriter] dialect exports an identifier called @racket[doc] that contains the @tech{Q-expression} that results from running the source. } @subsection{Q-expressions} A @deftech{Q-expression} is an @seclink["X-expressions" #:doc '(lib "pollen/scribblings/pollen.scrbl")]{X-expression}, but more restricted: @racketgrammar[ #:literals (list q) qexpr string (list q (list (list attr-name attr-val) ...) qexpr ...) (list q (list qexpr ...)) ] This grammar means that a @tech{Q-expression} is either a) a string, b) an X-expression whose tag is @racket[q] and whose elements are themselves Q-expressions. @examples[#:eval my-eval (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"))) (code:comment @#,t{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 @racketmodname[txexpr] library). @margin-note{Unlike X-expressions, Q-expressions do not support character entities or CDATA, because those are inherent to XML-ish markup.} @subsection{Markup} @subsection{Hard breaks} @deftogether[(@defthing[line-break qexpr?] @defthing[column-break qexpr?] @defthing[page-break qexpr?])]{ The Q-expressions @racketresult['#,line-break], @racketresult['#,column-break], and @racketresult['#,page-break], respectively. Quadwriter will automatically insert these breaks as needed. But you can also add them explicitly (aka ``hard'' breaks) by inserting the @tech{Q-expression} denoting the break. } @defthing[para-break qexpr?]{ The @tech{Q-expression} @racketresult['#,para-break]. Used to denote the start of a new paragraph. The default @racket[space-before] a paragraph is 0; the default @racket[space-after] is 60% of the paragraph's @racket[line-height]. } @defthing[section-break qexpr?]{ The @tech{Q-expression} @racketresult['#,section-break]. Used to denote the start of a new section. A section is a contiguous series of pages. Each section has its own @secref{Section-level_attributes}. A document without any explicit section breaks still has one section (that includes all the pages). } @subsection{Drawing quads} @defmodule[quadwriter/draw] @deftech{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. @defthing[line qexpr?]{ Draws a line. Four attributes are required: @racket[x1] and @racket[y1] (which determine the starting point) and @racket[x2] and @racket[y2] (which determine the ending point). Each of these values is a @tech{dimension string}. These distances are interpreted as relative to the upper left corner of the quad. Optional attributes are @racket[stroke] (a @tech{dimension string} that controls the thickness of the line) and @racket[color] (a a @tech{hex color} string or @tech{named color} string that controls the color of the line). } @defthing[text qexpr?]{ Draws a single text string without line wrapping. One attribute is required: @racket[string], which is the string to be drawn. A text-drawing quad will also inherit the current @secref["Font_attributes"], which can also be set separately. } @subsection{Attributes} These are the attributes that can be used inside a @tech{Q-expression} passed to @racketmodname[quadwriter]. Inside a Q-expression, every attribute is a @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{symbol}, and every attribute value is a @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{string}. A @deftech{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 @racket[in], @racket[cm], or @racket[mm] as a suffix, it is treated as inches, centimeters, or millimeters respectively. If the number has @racket[p] or @racket[pica] as a suffix or infix, it is treated as a pica / point measurement. A pica is 12 points, so @racket[12p] is 72 points, and @racket[3p9] is 45 points. If the number has @racket[em] as a suffix, it is treated as an em measurement, which is a multiple of the current font size. @subsubsection{Document-level attributes} Attributes that can only be set once for the whole document. @defthing[#:kind "attribute" output-path symbol?]{ Output path for the rendered PDF. Default is the name of the source file with its extension changed to @racket[.pdf]. For instance, @racket["my-source.rkt"] would become @racket["my-source.pdf"]. Unsaved source files are rendered as @racket["untitled.pdf"]. } @deftogether[(@defthing[#:kind "attribute" pdf-title symbol?] @defthing[#:kind "attribute" pdf-author symbol?] @defthing[#:kind "attribute" pdf-subject symbol?] @defthing[#:kind "attribute" pdf-keywords symbol?])]{ Strings that are used to fill in the corresponding @link["https://helpx.adobe.com/acrobat/using/pdf-properties-metadata.html"]{PDF metadata} fields. Default for each is the empty string. } @subsubsection{Section-level attributes} Attributes that can be set for each section. @deftogether[(@defthing[#:kind "attribute" page-size symbol?] @defthing[#:kind "attribute" page-orientation symbol?])]{ The usual way of setting the overall page dimensions of the rendered PDF. The value of @racket[page-size] is a @tech{named page size}. The value of @racket[page-orientation] can be either @racket["tall"] or @racket["portrait"] (which both put the longer edge vertically) or @racket["wide"] or @racket["landscape"] (which put the longer edge horizontally). The @deftech{named page sizes} are listed below. Names are case-insensitive. Dimensions below are in points. @(tabular #:sep @hspace[2] (cons (list @bold{name} @bold{short edge} @bold{long edge}) (map (λ (args) (map (λ (x) (tt (~a x))) args)) (sort (hash->list page-sizes) stringhexstring triple) (apply string-append "#" (map (λ(x) (~r x #:base 16 #:min-width 2 #:pad-string "0")) triple))) @(tabular #:sep @hspace[2] (cons (list @bold{name} @bold{hex color equivalent}) (map (λ (args) (list (tt (car args)) (tt (tuple->hexstring (cdr args))))) (sort (hash->list named-colors) stringpict (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 @emph{both} anchor points? Shouldn't the @racket[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. @subsection[#:tag "quad-rendering"]{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. @section{What are your plans for Quad?} Some things I personally plan to use Quad for: @itemlist[#:style 'ordered @item{@bold{A simple word processor}. Quadwriter is the demo of this.} @item{@bold{Font sample documents}. In my work as a @link["https://mbtype.com"]{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.} @item{@bold{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.} ] @subsection{Getting more help} Questions about Quad and Quadwriter can be posted in the @link["https://forums.matthewbutterick.com/c/quad/"]{Quad forum.} @subsection{Why is it called Quad?} In letterpress printing, a @italic{quad} was a piece of metal used as spacing material within a line. @(linebreak) @(linebreak) @(linebreak) @italic{``A way of doing something original is by trying something so painstaking that nobody else has ever bothered with it.'' — Brian Eno}