#lang debug racket (require "attrs.rkt" "struct.rkt" "block.rkt" quad/base) (provide (all-defined-out)) (define q:column (make-quad #:type column-quad #:tag 'col #:from 'ne #:to 'nw)) (define q:column-spacer (make-quad #:type column-spacer-quad #:from 'ne #:to 'nw #:printable only-prints-in-middle)) (define ((column-wrap-finish col-quad) lns q0 ending-q idx [reversed-fn-lines null]) (define fn-lines (from-parent (for/list ([fn-line (in-list reversed-fn-lines)]) ;; position bottom to top, in reverse (quad-update! fn-line [from 'nw] [to 'sw])) 'sw)) (append (match lns [(cons line _) (list (quad-copy column-quad col-quad ;; move block attrs up, so they are visible in page wrap [attrs (copy-block-attrs (quad-attrs line) (hash-copy (quad-attrs col-quad)))] [elems (append (from-parent (insert-blocks lns) 'nw) fn-lines)]))] [_ null]) (match ending-q [(? page-break-quad? page-break) (list page-break)] ; hard page (or section) break [_ null]))) #| constraint wrapping example https://github.com/mbutterick/typesetter/blob/882ec681ad1fa6eaee6287e53bc4320d9656046b/pdf/directory-require.rkt#L51 |# ;; (define (footnote-start? fnq) (quad-ref fnq :fn-text-start)) (define (handle-leftover-footnote ymax leftover-qs fn-qs) (let loop ([ymax ymax][leftover-qs leftover-qs][fn-qs fn-qs]) (define ydist (and (pair? fn-qs) (pt-y (size (car fn-qs))))) ;; take all fn lines that are not footnote-start? ;; and that fit within ymax remaining (if (and ydist (not (footnote-start? (car fn-qs))) (<= ydist ymax)) (loop (- ymax ydist) (cons (car fn-qs) leftover-qs) (cdr fn-qs)) (values ymax leftover-qs fn-qs)))) (define (handle-new-footnote ymax leftover-qs fn-qs fn-ref-q) (define ydist-fn (and (pair? fn-qs) (footnote-start? (car fn-qs)) (pt-y (size (car fn-qs))))) (define ydist-ref (pt-y (size fn-ref-q))) ;; only accept the footnote if both the first line of footnote ;; and the line containing the ref will fit. (if (and ydist-fn (<= (+ ydist-fn ydist-ref) ymax)) (values (- ymax ydist-fn) (cons (car fn-qs) leftover-qs) (cdr fn-qs)) (raise 'boom))) (define (column-wrap lines fn-lines vertical-height column-gap [col-quad-proto q:column]) (unless (positive? vertical-height) (raise-argument-error 'column-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 cols (wrap lines vertical-height #:soft-break #true #:hard-break column-break-quad? #:no-break (λ (q) (quad-ref q :no-colbr)) ; cooperates with make-nobreak #:distance (λ (q dist-so-far wrap-qs) ;; do trial block insertions (sum-y (insert-blocks (reverse wrap-qs)))) #:finish-wrap (column-wrap-finish col-quad-proto) #:footnote-qs fn-lines #:footnote-leftover-proc handle-leftover-footnote #:footnote-new-proc handle-new-footnote)) (define reversed-fn-lines (from-parent (for/list ([fn-line (in-list (reverse fn-lines))]) ;; position bottom to top, in reverse (quad-update! fn-line [from 'nw] [to 'sw])) 'sw)) (when (pair? cols) (quad-update! (car cols) [elems (append (quad-elems (car cols)) reversed-fn-lines)])) (define col-spacer (quad-copy column-spacer-quad q:column-spacer [size (pt column-gap (and 'arbitrary-irrelevant-value 100))])) (add-between cols col-spacer))