diff --git a/quad/qtest/fark.rkt b/quad/qtest/fark.rkt index 853cc733..e05ce3c1 100644 --- a/quad/qtest/fark.rkt +++ b/quad/qtest/fark.rkt @@ -1,5 +1,13 @@ #lang quadwriter/markdown +#:page-size A3 +#:page-orientation wide +#:column-count 3 +#:column-gap 24 +#:line-align justify +#:line-wrap best +#:page-margin-left 200 +#:page-margin-right 100 A _macro_ is a syntactic form with an associated _transformer_ that _expands_ the original form into existing forms. To put it another way, @@ -7,10 +15,121 @@ a macro is an extension to the Racket compiler. Most of the syntactic forms of `racket/base` and `racket` are actually macros that expand into a small set of core constructs. -Like many languages, Racket provides pattern-based macros that make +Like many languages, Racket provides pattern based macros that make simple transformations easy to implement and reliable to use. Racket also supports arbitrary macro transformers that are implemented in -Racket—or in a macro-extended variant of Racket. +Racket — or in a macro-extended variant of Racket. + +This chapter provides an introduction to Racket macros, but see [_Fear +of Macros_](http://www.greghendershott.com/fear-of-macros/) for an +introduction from a different perspective. + +A _macro_ is a syntactic form with an associated _transformer_ that +_expands_ the original form into existing forms. To put it another way, +a macro is an extension to the Racket compiler. Most of the syntactic +forms of `racket/base` and `racket` are actually macros that expand into +a small set of core constructs. + +Like many languages, Racket provides pattern based macros that make +simple transformations easy to implement and reliable to use. Racket +also supports arbitrary macro transformers that are implemented in +Racket — or in a macro-extended variant of Racket. + +This chapter provides an introduction to Racket macros, but see [_Fear +of Macros_](http://www.greghendershott.com/fear-of-macros/) for an +introduction from a different perspective. + + +A _macro_ is a syntactic form with an associated _transformer_ that +_expands_ the original form into existing forms. To put it another way, +a macro is an extension to the Racket compiler. Most of the syntactic +forms of `racket/base` and `racket` are actually macros that expand into +a small set of core constructs. + +Like many languages, Racket provides pattern based macros that make +simple transformations easy to implement and reliable to use. Racket +also supports arbitrary macro transformers that are implemented in +Racket — or in a macro-extended variant of Racket. + +This chapter provides an introduction to Racket macros, but see [_Fear +of Macros_](http://www.greghendershott.com/fear-of-macros/) for an +introduction from a different perspective. + + +A _macro_ is a syntactic form with an associated _transformer_ that +_expands_ the original form into existing forms. To put it another way, +a macro is an extension to the Racket compiler. Most of the syntactic +forms of `racket/base` and `racket` are actually macros that expand into +a small set of core constructs. + +Like many languages, Racket provides pattern based macros that make +simple transformations easy to implement and reliable to use. Racket +also supports arbitrary macro transformers that are implemented in +Racket — or in a macro-extended variant of Racket. + +This chapter provides an introduction to Racket macros, but see [_Fear +of Macros_](http://www.greghendershott.com/fear-of-macros/) for an +introduction from a different perspective. + + +A _macro_ is a syntactic form with an associated _transformer_ that +_expands_ the original form into existing forms. To put it another way, +a macro is an extension to the Racket compiler. Most of the syntactic +forms of `racket/base` and `racket` are actually macros that expand into +a small set of core constructs. + +Like many languages, Racket provides pattern based macros that make +simple transformations easy to implement and reliable to use. Racket +also supports arbitrary macro transformers that are implemented in +Racket — or in a macro-extended variant of Racket. + +This chapter provides an introduction to Racket macros, but see [_Fear +of Macros_](http://www.greghendershott.com/fear-of-macros/) for an +introduction from a different perspective. + + +A _macro_ is a syntactic form with an associated _transformer_ that +_expands_ the original form into existing forms. To put it another way, +a macro is an extension to the Racket compiler. Most of the syntactic +forms of `racket/base` and `racket` are actually macros that expand into +a small set of core constructs. + +Like many languages, Racket provides pattern based macros that make +simple transformations easy to implement and reliable to use. Racket +also supports arbitrary macro transformers that are implemented in +Racket — or in a macro-extended variant of Racket. + +This chapter provides an introduction to Racket macros, but see [_Fear +of Macros_](http://www.greghendershott.com/fear-of-macros/) for an +introduction from a different perspective. + + +A _macro_ is a syntactic form with an associated _transformer_ that +_expands_ the original form into existing forms. To put it another way, +a macro is an extension to the Racket compiler. Most of the syntactic +forms of `racket/base` and `racket` are actually macros that expand into +a small set of core constructs. + +Like many languages, Racket provides pattern based macros that make +simple transformations easy to implement and reliable to use. Racket +also supports arbitrary macro transformers that are implemented in +Racket — or in a macro-extended variant of Racket. + +This chapter provides an introduction to Racket macros, but see [_Fear +of Macros_](http://www.greghendershott.com/fear-of-macros/) for an +introduction from a different perspective. + + +A _macro_ is a syntactic form with an associated _transformer_ that +_expands_ the original form into existing forms. To put it another way, +a macro is an extension to the Racket compiler. Most of the syntactic +forms of `racket/base` and `racket` are actually macros that expand into +a small set of core constructs. + +Like many languages, Racket provides pattern based macros that make +simple transformations easy to implement and reliable to use. Racket +also supports arbitrary macro transformers that are implemented in +Racket — or in a macro-extended variant of Racket. This chapter provides an introduction to Racket macros, but see [_Fear of Macros_](http://www.greghendershott.com/fear-of-macros/) for an diff --git a/quad/quad/scribblings/quad.scrbl b/quad/quad/scribblings/quad.scrbl index c43afc8b..7cfa7e79 100644 --- a/quad/quad/scribblings/quad.scrbl +++ b/quad/quad/scribblings/quad.scrbl @@ -461,6 +461,10 @@ The unusual way of setting the overall page dimensions of the rendered PDF. Both Inset values from the page edges. Value is given as a @tech{dimension string}. Default values depend on size of the page: they are chosen to be not completely bananas. } +@deftogether[(@defthing[#:kind "attribute" column-count symbol?] + @defthing[#:kind "attribute" column-gap symbol?])]{ +Columns per page. @racket[column-count] is a positive integer; @racket[column-gap] (the space between columns) is a @tech{dimension string}. +} @subsubsection{Block-level attributes} diff --git a/quad/quadwriter/attrs.rkt b/quad/quadwriter/attrs.rkt index 75b4da10..5c14d0ee 100644 --- a/quad/quadwriter/attrs.rkt +++ b/quad/quadwriter/attrs.rkt @@ -62,6 +62,9 @@ Naming guidelines background-color clip ; whether box boundary clips its contents + + column-count + column-gap keep-first-lines keep-last-lines diff --git a/quad/quadwriter/core.rkt b/quad/quadwriter/core.rkt index db9caea0..444737a0 100644 --- a/quad/quadwriter/core.rkt +++ b/quad/quadwriter/core.rkt @@ -16,7 +16,7 @@ "attrs.rkt" "param.rkt" "font.rkt") -(provide para-break line-break page-break bullet-quad hrbr lbr pbr render-pdf) +(provide para-break line-break page-break column-break bullet-quad hrbr lbr pbr render-pdf) (define-quad string-quad quad ()) @@ -174,6 +174,9 @@ (define hrbr (make-q:hr-break #:printable #t #:id 'hrbr)) +(define-quad q:col-break q:line-break ()) +(define colbr (make-q:col-break #:printable #f #:id 'colbr)) + (define-quad q:page-break q:line-break ()) (define pgbr (make-q:page-break #:printable #f #:id 'pgbr)) @@ -364,7 +367,7 @@ #:soft-break soft-break-for-line? #:finish-wrap (finish-line-wrap line-q))))) -(define (make-nobreak! q) (quad-set! q 'no-pbr "true")) +(define (make-nobreak! q) (quad-set! q 'no-colbr "true")) ; cooperates with col-wrap (define (do-keep-with-next! reversed-lines) ;; paints nobreak onto spacers that follow keep-with-next lines @@ -394,13 +397,13 @@ (unless (eq? idx group-len) (cond ;; if we have 'keep-all we can skip 'keep-first and 'keep-last cases - [(quad-ref ln 'keep-all) (make-nobreak! ln)] + [(quad-ref ln 'keep-all-lines) (make-nobreak! ln)] ;; to keep n lines, we only paint the first n - 1 ;; (because each nobr line sticks to the next) - [(let ([keep-first (quad-ref ln 'keep-first)]) + [(let ([keep-first (quad-ref ln 'keep-first-lines)]) (and (number? keep-first) (< idx keep-first))) (make-nobreak! ln)] - [(let ([keep-last (quad-ref ln 'keep-last)]) + [(let ([keep-last (quad-ref ln 'keep-last-lines)]) (and (number? keep-last) (< (- group-len keep-last) idx))) (make-nobreak! ln)])) (cons ln reversed-lines))) @@ -433,6 +436,17 @@ (draw-debug q doc "goldenrod" "goldenrod")) (draw-page-footer q doc)))) +(define q:column (q + #:id 'col + #:from 'ne + #:to 'nw)) + +(struct column-spacer quad () #:transparent) +(define q:column-spacer (q #:type column-spacer + #:from 'ne + #:to 'nw + #:printable (λ (q sig) (not (memq sig '(start end)))))) + (define q:page (q #:id 'page #:from-parent 'nw @@ -514,7 +528,35 @@ (cons (struct-copy quad q [from-parent (or where (quad-from q))]) rest)]) -(define ((page-finish-wrap page-quad path) lns q0 q page-idx) + +(define ((col-finish-wrap col-quad) lns . _) + (list (struct-copy quad col-quad + [elems (from-parent (insert-blocks lns) 'nw)]))) + +(define (col-wrap qs vertical-height col-gap [col-quad q:column]) + (unless (positive? vertical-height) + (raise-argument-error 'col-wrap "positive number" vertical-height)) + + ;; on timing of `insert-blocks`: + ;; can't do it before because it depends on where columns are broken. + ;; could do it after, but it would require going back inside each col quad + ;; which seems overly interdependent, because `insert-blocks` is used to determine break locations. + ;; `col-wrap` should emit quads that are complete. + (define col-spacer (struct-copy quad q:column-spacer + [size (pt col-gap 100)])) + (add-between + (wrap qs vertical-height + #:soft-break (λ (q) #true) + #:hard-break q:col-break? + #:no-break (λ (q) (quad-ref q 'no-colbr)) ; cooperates with make-nobreak + #:distance (λ (q dist-so-far wrap-qs) + ;; do trial block insertions + (for/sum ([x (in-list (insert-blocks wrap-qs))]) + (pt-y (size x)))) + #:finish-wrap (col-finish-wrap col-quad)) + col-spacer)) + +(define ((page-finish-wrap page-quad path) cols q0 q page-idx) (define-values (dir name _) (split-path (path-replace-extension path #""))) (define footer (struct-copy quad q:footer [attrs (let ([h (hash-copy (quad-attrs q:footer))]) @@ -522,26 +564,22 @@ (hash-set! h 'doc-title (string-titlecase (path->string name))) h)])) (list (struct-copy quad page-quad - [elems (cons footer (from-parent (insert-blocks lns) 'nw))]))) + [elems (cons footer (from-parent cols 'nw))]))) -(define (page-wrap xs vertical-height [page-quad q:page]) - (unless (positive? vertical-height) - (raise-argument-error 'page-wrap "positive number" vertical-height)) - ;; on timing of `insert-blocks`: - ;; can't do it before because it depends on where pages are broken. - ;; could do it after, but it would require going back inside each page quad - ;; which seems overly interdependent, because `insert-blocks` is used to determine break locations. - ;; `page-wrap` should emit quads that are complete. - (wrap xs vertical-height +(define (page-wrap qs width [page-quad q:page]) + (unless (positive? width) + (raise-argument-error 'page-wrap "positive number" width)) + (wrap qs width #:soft-break (λ (q) #true) #:hard-break q:page-break? #:no-break (λ (q) (quad-ref q 'no-pbr)) #:distance (λ (q dist-so-far wrap-qs) - ;; do trial block insertions - (for/sum ([x (in-list (insert-blocks wrap-qs))]) - (pt-y (size x)))) + (for/sum ([x (in-list wrap-qs)]) + (pt-x (size x)))) #:finish-wrap (page-finish-wrap page-quad (pdf-output-path (current-pdf))))) + + (define (insert-blocks lines) (define groups-of-lines (contiguous-group-by (λ (x) (quad-ref x 'display)) lines)) (append* (for/list ([line-group (in-list groups-of-lines)]) @@ -583,12 +621,14 @@ (define para-break '(q ((break "para")))) (define line-break '(q ((break "line")))) (define page-break '(q ((break "page")))) +(define column-break '(q ((break "column")))) (define (replace-breaks x) (map-elements (λ (el) (match el [(== para-break) pbr] [(== line-break) lbr] + [(== column-break) colbr] [(== page-break) pgbr] [_ el])) x)) @@ -634,6 +674,8 @@ (define default-side-margin (min (* 72 1.5) (floor (* .20 (pdf-width pdf))))) (define default-top-margin (min 72 (floor (* .10 (pdf-height pdf))))) + (define default-column-count 1) + (define default-column-gap 36) (parameterize ([current-pdf pdf] [verbose-quad-printing? #false]) (let* ([qs (time-name hyphenate (handle-hyphenate qs))] @@ -644,7 +686,13 @@ (quad-ref (car qs) 'page-margin-left (λ () (quad-ref (car qs) 'page-margin-right default-side-margin))))] [right-margin (or (debug-x-margin) (quad-ref (car qs) 'page-margin-right (λ () (quad-ref (car qs) 'page-margin-left default-side-margin))))] - [line-wrap-size (- (pdf-width pdf) left-margin right-margin)] + [column-count (let ([cc (or (debug-column-count) (quad-ref (car qs) 'column-count default-column-count))]) + (unless (exact-nonnegative-integer? cc) + (raise-argument-error 'render-pdf "positive integer" cc)) + cc)] + [column-gap (or (debug-column-gap) (quad-ref (car qs) 'column-gap default-column-gap))] + [printable-width (- (pdf-width pdf) left-margin right-margin)] + [line-wrap-size (/ (- printable-width (* (sub1 column-count) column-gap)) column-count)] [qs (time-name line-wrap (line-wrap qs line-wrap-size))] [qs (apply-keeps qs)] ;; if only top or bottom margin is provided, copy other value in preference to default margin @@ -653,17 +701,22 @@ [bottom-margin (let ([vert-optical-adjustment 10]) (or (debug-y-margin) (quad-ref (car qs) 'page-margin-bottom (λ () (+ vert-optical-adjustment (quad-ref (car qs) 'page-margin-top (* default-top-margin 1.4)))))))] - [page-wrap-size (- (pdf-height pdf) top-margin bottom-margin)] + [col-wrap-size (- (pdf-height pdf) top-margin bottom-margin)] + [col-quad (struct-copy quad q:column + [size (pt line-wrap-size col-wrap-size)])] + [cols (time-name col-wrap (col-wrap qs col-wrap-size column-gap col-quad))] + + [printable-height (- (pdf-height pdf) top-margin bottom-margin)] [page-quad (struct-copy quad q:page [shift (pt left-margin top-margin)] - [size (pt line-wrap-size page-wrap-size)])] - [qs (time-name page-wrap (page-wrap qs page-wrap-size page-quad))] + [size (pt line-wrap-size printable-height)])] + + [qs (time-name page-wrap (page-wrap cols printable-width page-quad))] [qs (time-name position (position (struct-copy quad q:doc [elems qs])))]) - (time-name draw (draw qs pdf)) - (when pdf-path-arg - (displayln (format "wrote PDF to ~a" pdf-path))))) - - (unless pdf-path-arg - (begin0 - (file->bytes pdf-path) - (delete-file pdf-path)))) + (time-name draw (draw qs pdf)))) + + (if pdf-path-arg + (displayln (format "wrote PDF to ~a" pdf-path)) + (begin0 + (file->bytes pdf-path) + (delete-file pdf-path)))) diff --git a/quad/quadwriter/param.rkt b/quad/quadwriter/param.rkt index 003320d9..6fff02f1 100644 --- a/quad/quadwriter/param.rkt +++ b/quad/quadwriter/param.rkt @@ -21,6 +21,8 @@ (define debug-page-height (make-parameter 400)) (define debug-x-margin (make-parameter 40)) (define debug-y-margin (make-parameter 40)) + (define debug-column-count (make-parameter 2)) + (define debug-column-gap (make-parameter 36)) (define zoom-factor (make-parameter 1.5)))] [_ #'(begin @@ -39,6 +41,8 @@ (define debug-page-height (make-parameter #f)) (define debug-x-margin (make-parameter #f)) (define debug-y-margin (make-parameter #f)) + (define debug-column-count (make-parameter #f)) + (define debug-column-gap (make-parameter #f)) (define zoom-factor (make-parameter 1)))])) (go) \ No newline at end of file diff --git a/quad/quadwriter/tags.rkt b/quad/quadwriter/tags.rkt index b9abe72e..400fbc0e 100644 --- a/quad/quadwriter/tags.rkt +++ b/quad/quadwriter/tags.rkt @@ -32,7 +32,7 @@ (border-width-bottom "0.5") (border-color-bottom "gray") (border-inset-bottom "-2") (border-width-right "0.5") (border-color-right "gray") (border-inset-right "20") (inset-top "10") (inset-bottom "8") (inset-left "30") (inset-right "30") - (keep-all "yes")) + (keep-all-lines "yes")) attrs) exprs)) (define id (default-tag-function 'id))