From 9d47c31b65810aa28725c2b78d028cf8c585398c Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Tue, 13 Aug 2019 12:04:42 -0700 Subject: [PATCH] add `page-margin-gutter` attribute (closes #25) --- quad/qtest/test-sections-tester.pdf | Bin 10982 -> 11644 bytes quad/qtest/test-sections.rkt | 16 +++- quad/quad/scribblings/quad.scrbl | 24 +++--- quad/quadwriter/attrs.rkt | 2 + quad/quadwriter/layout.rkt | 10 ++- quad/quadwriter/param.rkt | 4 +- quad/quadwriter/render.rkt | 111 ++++++++++++++++------------ 7 files changed, 101 insertions(+), 66 deletions(-) diff --git a/quad/qtest/test-sections-tester.pdf b/quad/qtest/test-sections-tester.pdf index bfddb1a7fe340fa99fd35df6311eb4fe2740d484..e88506f5b43762660779e659a00e6ad19d66ce4e 100644 GIT binary patch delta 8662 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 delta 8285 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 F_&?~}P#ORL diff --git a/quad/qtest/test-sections.rkt b/quad/qtest/test-sections.rkt index 0a582d54..358076f2 100644 --- a/quad/qtest/test-sections.rkt +++ b/quad/qtest/test-sections.rkt @@ -1,15 +1,23 @@ #lang quadwriter +'(q ((page-margin-gutter "1.5in")(page-margin-left "0.5in")(page-margin-right "0.5in")) + "Section 1 Page 1 on right" -'(q ((break "section"))) +(q ((break "section"))) -'(q ((page-width "5in")(page-height "5in")(page-side-start "right")) "Section 2 Page 1 on right" +(q ((page-width "5in")(page-height "5in")(page-side-start "right")) "Section 2 Page 1 on right" (q ((break "page"))) "Section 2 Page 2 on left") -'(q ((break "section"))) +(q ((break "section"))) + +(q ((page-width "5in")(page-height "5in")(page-side-start "left")) "Section 3 Page 1 on left" + +(q ((break "page"))) + +"Section 3 Page 2 on right") -'(q ((page-width "5in")(page-height "5in")(page-side-start "left")) "Section 3 Page 1 on left") +) \ No newline at end of file diff --git a/quad/quad/scribblings/quad.scrbl b/quad/quad/scribblings/quad.scrbl index f86c5d28..10e0b66e 100644 --- a/quad/quad/scribblings/quad.scrbl +++ b/quad/quad/scribblings/quad.scrbl @@ -484,15 +484,19 @@ The unusual way of setting the overall page dimensions of the rendered PDF. Both @defthing[#:kind "attribute" page-margin-bottom symbol?] @defthing[#:kind "attribute" page-margin-left symbol?] @defthing[#:kind "attribute" page-margin-right symbol?])]{ -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. +Inset values from the page edges. Value is a @tech{dimension string}. Default values depend on size of the page: they are chosen to be not completely bananas. +} + +@defthing[#:kind "attribute" page-margin-gutter symbol?]{ +Extra space added to the inner margin of page. Value is a @tech{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 @racket[0]. } @defthing[#:kind "attribute" page-number-start symbol?]{ -First page number used. Default is @racket[1]. +First page number used. Value is an integer. Default is @racket[1]. } @defthing[#:kind "attribute" page-side-start symbol?]{ -Side that first page appears on. Can be @racket['left] or @racket['right]. A blank page will be inserted if necessary. Default is @racket['right]. +Side that first page appears on. Value is @racket['left] or @racket['right]. A blank page will be inserted if necessary. Default is @racket['right]. } @deftogether[(@defthing[#:kind "attribute" column-count symbol?] @@ -508,21 +512,21 @@ A block is a paragraph or other rectangular item (say, a blockquote or code bloc @defthing[#:kind "attribute" inset-bottom symbol?] @defthing[#:kind "attribute" inset-left symbol?] @defthing[#:kind "attribute" inset-right symbol?])]{ -Inset values increase the layout boundary of the quad. Value is given as a @tech{dimension string}. @racket["0"] by default. +Inset values increase the layout boundary of the quad. Value is a @tech{dimension string}. @racket["0"] by default. } @deftogether[(@defthing[#:kind "attribute" border-inset-top symbol?] @defthing[#:kind "attribute" border-inset-bottom symbol?] @defthing[#:kind "attribute" border-inset-left symbol?] @defthing[#:kind "attribute" border-inset-right symbol?])]{ -Border-inset values do not change the layout boundary of the quad. Rather, they change the position of the border (if any) relative to the layout boundary. Value is given as a @tech{dimension string}. @racket["0"] by default (meaning, the border sits on the layout boundary). +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 @tech{dimension string}. @racket["0"] by default (meaning, the border sits on the layout boundary). } @deftogether[(@defthing[#:kind "attribute" border-width-top symbol?] @defthing[#:kind "attribute" border-width-bottom symbol?] @defthing[#:kind "attribute" border-width-left symbol?] @defthing[#:kind "attribute" border-width-right symbol?])]{ -Width of the border on each edge of the quad. Value is given as a @tech{dimension string}. @racket["0"] by default (meaning no border). +Width of the border on each edge of the quad. Value is a @tech{dimension string}. @racket["0"] by default (meaning no border). } @deftogether[(@defthing[#:kind "attribute" border-color-top symbol?] @@ -538,7 +542,7 @@ Color of the background of the quad. Value is a @tech{hex color} string or @tech @deftogether[(@defthing[#:kind "attribute" space-before symbol?] @defthing[#:kind "attribute" space-after symbol?])]{ -Vertical space added around a block. Value is given as a @tech{dimension string}. +Vertical space added around a block. Value is a @tech{dimension string}. } @deftogether[(@defthing[#:kind "attribute" keep-first-lines symbol?] @@ -559,7 +563,7 @@ How the lines are aligned horizontally in the quad. Possibilities are @racket["l } @defthing[#:kind "attribute" first-line-indent symbol?]{ -The indent of the first line in the quad. Value is given as a @tech{dimension string}. +The indent of the first line in the quad. Value is a @tech{dimension string}. } @@ -616,11 +620,11 @@ Two ways of setting OpenType layout features. @racket[font-features] takes a @de @defthing[#:kind "attribute" font-tracking symbol?]{ -Space between characters. Value is given as a @tech{dimension string}. +Space between characters. Value is a @tech{dimension string}. } @defthing[#:kind "attribute" font-baseline-shift symbol?]{ -Vertical offset of font baseline (positive values move the baseline up, negative down). Value is given as a @tech{dimension string}. +Vertical offset of font baseline (positive values move the baseline up, negative down). Value is a @tech{dimension string}. } @defthing[#:kind "attribute" line-height symbol?]{ diff --git a/quad/quadwriter/attrs.rkt b/quad/quadwriter/attrs.rkt index 0329b91e..3cc368f9 100644 --- a/quad/quadwriter/attrs.rkt +++ b/quad/quadwriter/attrs.rkt @@ -155,6 +155,7 @@ Naming guidelines page-margin-bottom page-margin-left page-margin-right + page-margin-gutter footer-display)) @@ -165,6 +166,7 @@ Naming guidelines :page-margin-bottom :page-margin-left :page-margin-right + :page-margin-gutter :column-gap :inset-top :inset-bottom diff --git a/quad/quadwriter/layout.rkt b/quad/quadwriter/layout.rkt index c9f2505c..2a56761a 100644 --- a/quad/quadwriter/layout.rkt +++ b/quad/quadwriter/layout.rkt @@ -512,7 +512,8 @@ (define (make-footer-quad col-q page-idx path) (define-values (dir name _) (split-path (path-replace-extension path #""))) (q #:size (pt 50 default-line-height) - #:attrs (hasheq :page-number (+ (quad-ref col-q :page-number-start (add1 (current-page-count))) (sub1 page-idx)) + #:attrs (hasheq :page-number (+ (quad-ref col-q :page-number-start (add1 (section-pages-used))) + (sub1 page-idx)) :doc-title (string-titlecase (path->string name))) #:from-parent 'sw #:to 'nw @@ -661,7 +662,8 @@ #:finish-wrap (col-finish-wrap column-quad)) col-spacer)) -(define ((page-finish-wrap page-quad path) cols q0 q page-idx) +(define ((page-finish-wrap make-page-quad path) cols q0 q page-idx) + (define page-quad (make-page-quad (+ (section-pages-used) page-idx))) (define elems (match (quad-ref (car cols) :footer-display #true) [(or #false "none") (from-parent cols 'nw)] @@ -671,7 +673,7 @@ [attrs (copy-block-attrs (quad-attrs (car cols)) (hash-copy (quad-attrs page-quad)))]))) -(define (page-wrap qs width [page-quad q:page]) +(define (page-wrap qs width [make-page-quad (λ _ q:page)]) (unless (positive? width) (raise-argument-error 'page-wrap "positive number" width)) (wrap qs width @@ -681,7 +683,7 @@ #:distance (λ (q dist-so-far wrap-qs) (for/sum ([x (in-list wrap-qs)]) (pt-x (size x)))) - #:finish-wrap (page-finish-wrap page-quad (pdf-output-path (current-pdf))))) + #:finish-wrap (page-finish-wrap make-page-quad (pdf-output-path (current-pdf))))) (define (insert-blocks lines) (define groups-of-lines (contiguous-group-by (λ (x) (quad-ref x :display)) lines)) diff --git a/quad/quadwriter/param.rkt b/quad/quadwriter/param.rkt index c885ba3c..01927a56 100644 --- a/quad/quadwriter/param.rkt +++ b/quad/quadwriter/param.rkt @@ -10,7 +10,7 @@ (define current-doc (make-parameter #false)) (define current-pdf (make-parameter #false)) (define current-line-wrap (make-parameter #f)) ; because kp is slow and maybe we want to disable for "draft" mode - (define current-page-count (make-parameter 0)) + (define section-pages-used (make-parameter 0)) (define quadwriter-test-mode (make-parameter #f)) ; used during rackunit to suppress nondeterministic elements, like timestamp in header @@ -34,7 +34,7 @@ (define current-doc (make-parameter #false)) (define current-pdf (make-parameter #false)) (define current-line-wrap (make-parameter #f)) - (define current-page-count (make-parameter 0)) + (define section-pages-used (make-parameter 0)) (define quadwriter-test-mode (make-parameter #f)) diff --git a/quad/quadwriter/render.rkt b/quad/quadwriter/render.rkt index 6e17074b..1133865c 100644 --- a/quad/quadwriter/render.rkt +++ b/quad/quadwriter/render.rkt @@ -34,10 +34,10 @@ [(_ ALL-BREAKS-ID . TYPES) (with-syntax ([((TYPE-BREAK TYPE-STR Q:TYPE-BREAK) ...) (for/list ([type (in-list (syntax->list #'TYPES))]) - (list - (format-id #'TYPES "~a-break" type) - (symbol->string (syntax->datum type)) - (format-id #'TYPES "q:~a-break" type)))]) + (list + (format-id #'TYPES "~a-break" type) + (symbol->string (syntax->datum type)) + (format-id #'TYPES "q:~a-break" type)))]) #'(begin (define TYPE-BREAK '(q ((break TYPE-STR)))) ... (define ALL-BREAKS-ID (list (cons TYPE-BREAK Q:TYPE-BREAK) ...))))])) @@ -56,22 +56,22 @@ ;; do this before ->string-quad so that it can handle the sizing promises (apply append (for/list ([q (in-list qs)]) - (match (quad-ref q :hyphenate) - [#true #:when (and (pair? (quad-elems q)) - (andmap string? (quad-elems q))) - (for*/list ([str (in-list (quad-elems q))] - [hyphen-char (in-value #\u00AD)] - [hstr (in-value (hyphenate str hyphen-char - #:min-left-length 3 - #:min-right-length 3))] - [substr (in-list (regexp-match* (regexp (string hyphen-char)) hstr #:gap-select? #t))]) - (struct-copy quad q [elems (list substr)]))] - [_ (list q)])))) + (match (quad-ref q :hyphenate) + [#true #:when (and (pair? (quad-elems q)) + (andmap string? (quad-elems q))) + (for*/list ([str (in-list (quad-elems q))] + [hyphen-char (in-value #\u00AD)] + [hstr (in-value (hyphenate str hyphen-char + #:min-left-length 3 + #:min-right-length 3))] + [substr (in-list (regexp-match* (regexp (string hyphen-char)) hstr #:gap-select? #t))]) + (struct-copy quad q [elems (list substr)]))] + [_ (list q)])))) (define (string->feature-list str) (for/list ([kv (in-slice 2 (string-split str))]) - (cons (string->bytes/utf-8 (first kv)) (string->number (second kv))))) + (cons (string->bytes/utf-8 (first kv)) (string->number (second kv))))) (define (parse-font-features! attrs) (cond @@ -93,14 +93,14 @@ (define (parse-dimension-strings! attrs) (for ([k (in-hash-keys attrs)] #:when (takes-dimension-string? k)) - (hash-update! attrs k parse-dimension)) + (hash-update! attrs k parse-dimension)) attrs) (define (complete-every-path! attrs) ;; relies on `current-directory` being parameterized to source file's dir (for ([k (in-hash-keys attrs)] #:when (takes-path? k)) - (hash-update! attrs k (compose1 path->string path->complete-path))) + (hash-update! attrs k (compose1 path->string path->complete-path))) attrs) (define (handle-cascading-attrs attrs) @@ -135,27 +135,39 @@ indented-qs) (define (setup-margins qs page-width page-height) + ;; if only left or right margin is provided, copy other value in preference to default margin + (define q (car qs)) (define default-side-margin (min (* 72 1.5) (floor (* .20 page-width)))) (define default-top-margin (min 72 (floor (* .10 page-height)))) - - ;; if only left or right margin is provided, copy other value in preference to default margin - (define left - (or (debug-x-margin) - (quad-ref (car qs) :page-margin-left - (λ () (quad-ref (car qs) :page-margin-right default-side-margin))))) - (define right - (or (debug-x-margin) - (quad-ref (car qs) :page-margin-right - (λ () (quad-ref (car qs) :page-margin-left default-side-margin))))) - (define top - (or (debug-y-margin) - (quad-ref (car qs) :page-margin-top - (λ () (quad-ref (car qs) :page-margin-bottom default-top-margin))))) - (define vert-optical-adjustment 10) - (define bottom - (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))))))) + + (define left (cond + [(debug-x-margin)] + [(quad-ref q :page-margin-left)] + [(quad-ref q :page-margin-right)] + [else default-side-margin])) + + (define right (cond + [(debug-x-margin)] + [(quad-ref q :page-margin-right)] + [(quad-ref q :page-margin-left)] + [else default-side-margin])) + + (define top (cond + [(debug-y-margin)] + [(quad-ref q :page-margin-top)] + [(quad-ref q :page-margin-bottom)] + [else default-top-margin])) + + (define bottom (cond + [(debug-y-margin)] + [(quad-ref q :page-margin-bottom)] + [else + (define vert-optical-adjustment 10) + (+ vert-optical-adjustment + (cond + [(quad-ref q :page-margin-top)] + [else (* default-top-margin 1.4)]))])) + (list left top right bottom)) (define default-column-count 1) @@ -195,7 +207,7 @@ ;; set `current-directory` so that ops like `path->complete-path` ;; will be handled relative to the original directory [current-directory base-dir] - [current-page-count 0] + [section-pages-used 0] [verbose-quad-printing? #false]) (define qs (time-log setup-qs (setup-qs qx-arg pdf-path))) (define sections @@ -205,7 +217,8 @@ (match-define (list page-width page-height) (parse-page-size (and (pair? qs) (car qs)))) (match-define (list left-margin top-margin right-margin bottom-margin) (setup-margins qs page-width page-height)) - (define printable-width (- page-width left-margin right-margin)) + (define maybe-gutter-margin (and (pair? qs) (quad-ref (car qs) :page-margin-gutter))) + (define printable-width (- page-width left-margin right-margin (or maybe-gutter-margin 0))) (define printable-height (- page-height top-margin bottom-margin)) (define column-count (setup-column-count qs)) (define column-gap (setup-column-gap qs)) @@ -216,20 +229,26 @@ (define col-quad-prototype (struct-copy quad q:column [size (pt line-wrap-size printable-height)])) (define column-qs (time-log column-wrap (column-wrap line-qs printable-height column-gap col-quad-prototype))) - - (define page-quad-prototype (struct-copy quad q:page - [shift (pt left-margin top-margin)] - [size (pt line-wrap-size printable-height)])) + + (define page-quad-prototype + (λ (page-count) + (define left-shift (+ left-margin + (cond + [(and (odd? page-count) maybe-gutter-margin)] + [else 0]))) + (struct-copy quad q:page + [shift (pt left-shift top-margin)] + [size (pt line-wrap-size printable-height)]))) (define section-starting-side (string->symbol (quad-ref (car qs) :page-side-start "right"))) (define insert-blank-page? (and (pair? qs) ;; if we need a 'left page and will get 'right (or vice versa) then insert page - (let ([next-page-side (if (even? (add1 (current-page-count))) 'left 'right)]) + (let ([next-page-side (if (even? (add1 (section-pages-used))) 'left 'right)]) (not (eq? section-starting-side next-page-side))))) ;; update page count before starting page wrap (when insert-blank-page? - (current-page-count (add1 (current-page-count)))) + (section-pages-used (add1 (section-pages-used)))) (define section-pages (time-log page-wrap (page-wrap column-qs printable-width page-quad-prototype))) @@ -258,7 +277,7 @@ [_ (list new-section)])])] [else (define new-section (struct-copy quad q:section [elems section-pages]) ) (cons new-section sections-acc)]) - (current-page-count (+ (current-page-count) (length section-pages)))))) + (section-pages-used (+ (section-pages-used) (length section-pages)))))) (define doc (time-log position (position (struct-copy quad q:doc [elems sections])))) (time-log draw (draw doc (current-pdf))))