You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
typesetting/quad/quad/position.rkt

181 lines
8.3 KiB
Racket

#lang debug br
(require "quad.rkt" "param.rkt" fontland)
(provide (all-defined-out))
(define pt-x first)
(define pt-y second)
(define (pt x y) (list x y))
(define (pt+ . pts) (apply map + pts))
(define (pt- . pts) (apply map - pts))
6 years ago
6 years ago
(define valid-anchors '(nw n ne w c e sw s se bi bo))
6 years ago
(define (coerce-int x) (if (integer? x) (inexact->exact x) x))
5 years ago
(define font-cache (make-hash))
(define (get-font font-name)
(hash-ref! font-cache font-name (λ () (open-font font-name))))
6 years ago
(define font-path-key 'font-path)
6 years ago
(define ascender-cache (make-hash))
5 years ago
(define (ascender q)
(define font-key-val (quad-ref q font-path-key "Courier"))
(unless font-key-val
6 years ago
(error 'ascender-no-font-key))
(hash-ref! ascender-cache font-key-val (λ () (font-ascent (get-font font-key-val)))))
6 years ago
6 years ago
(define units-cache (make-hash))
5 years ago
(define (units-per-em q)
(define font-key-val (quad-ref q font-path-key "Courier"))
(unless font-key-val
6 years ago
(error 'units-per-em-no-font-key))
(hash-ref! units-cache font-key-val (λ () (font-units-per-em (get-font font-key-val)))))
6 years ago
(define (fontsize q)
(define val (quad-ref q 'font-size current-default-font-size))
((if (number? val) values string->number) val))
6 years ago
6 years ago
(define (vertical-baseline-offset q)
(cond
[(quad-ref q font-path-key #f)
(* (/ (ascender q) (units-per-em q) 1.0) (fontsize q))]
[else 0]))
6 years ago
5 years ago
(define (anchor->local-point q anchor)
;; calculate the location of the anchor on the bounding box relative to '(0 0) (aka "locally")
(match-define (list x-fac y-fac)
6 years ago
(case anchor
[(nw) '(0 0 )] [(n) '(0.5 0 )] [(ne) '(1 0 )]
[( w) '(0 0.5)] [(c) '(0.5 0.5)] [( e) '(1 0.5)]
[(sw) '(0 1 )] [(s) '(0.5 1 )] [(se) '(1 1 )]
[(bi) '(0 0 )] [(bo) '(1 0 )]
[else (raise-argument-error 'anchor->local-point (format "anchor value in ~v" valid-anchors) anchor)]))
(match-define (list x y) (size q))
6 years ago
(pt (coerce-int (* x x-fac))
(coerce-int (+ (* y y-fac) (if (memq anchor '(bi bo)) (vertical-baseline-offset q) 0)))))
5 years ago
(define (inner-point q)
6 years ago
;; calculate absolute location of inner-point
;; based on current origin and point type.
;; include offset, because it's intended to adjust inner
5 years ago
(pt+ (quad-origin q) (anchor->local-point q (or (quad-inner q) (quad-in q))) (quad-offset q)))
5 years ago
(define (in-point q)
6 years ago
;; calculate absolute location of in-point
;; based on current origin and point type.
;; don't include offset, so location is on bounding box
5 years ago
(pt+ (quad-origin q) (anchor->local-point q (quad-in q))))
5 years ago
(define (out-point q)
6 years ago
;; calculate absolute location of out-point
;; based on current origin and point type.
;; don't include offset, so location is on bounding box
5 years ago
(pt+ (quad-origin q) (anchor->local-point q (quad-out q))))
(define (position q [previous-end-pt (pt 0 0)])
6 years ago
;; recursively calculates coordinates for quad & subquads
;; based on starting origin point
(define new-origin (pt- previous-end-pt (in-point q)))
(let ([q (struct-copy quad q [origin new-origin])])
(let loop ([pt (inner-point q)] [acc null] [elems (quad-elems q)])
(match elems
[(== empty) (struct-copy quad q [elems (reverse acc)])]
[(cons (? quad? q) rest)
(define new-q (position q pt))
(loop (out-point new-q) (cons new-q acc) rest)]
[(cons x rest) (loop pt (cons x acc) rest)]))))
5 years ago
(define (distance q)
(match (pt- (out-point q) (in-point q))
[(list-no-order 0 val) val]
[(list ∆x ∆y) (sqrt (+ (expt ∆x 2) (expt ∆y 2)))]))
(module+ test
(require rackunit)
5 years ago
(test-case
"origins"
(define size (pt 10 10))
(define orig (pt 5 5))
(check-equal? (quad-origin (position (q #:in 'nw #:size size) orig)) (pt 5 5))
(check-equal? (quad-origin (position (q #:in 'n #:size size) orig)) (pt 0 5))
(check-equal? (quad-origin (position (q #:in 'ne #:size size) orig)) (pt -5 5))
(check-equal? (quad-origin (position (q #:in 'e #:size size) orig)) (pt -5 0))
(check-equal? (quad-origin (position (q #:in 'se #:size size) orig)) (pt -5 -5))
(check-equal? (quad-origin (position (q #:in 's #:size size) orig)) (pt 0 -5))
(check-equal? (quad-origin (position (q #:in 'sw #:size size) orig)) (pt 5 -5))
(check-equal? (quad-origin (position (q #:in 'w #:size size) orig)) (pt 5 0)))
(test-case
5 years ago
"in points"
(define size '(10 10))
(define origin '(5 5))
5 years ago
(check-equal? (in-point (q #:in 'nw #:size size #:origin origin)) (pt 5 5))
(check-equal? (in-point (q #:in 'n #:size size #:origin origin)) (pt 10 5))
(check-equal? (in-point (q #:in 'ne #:size size #:origin origin)) (pt 15 5))
(check-equal? (in-point (q #:in 'w #:size size #:origin origin)) (pt 5 10))
(check-equal? (in-point (q #:in 'c #:size size #:origin origin)) (pt 10 10))
(check-equal? (in-point (q #:in 'e #:size size #:origin origin)) (pt 15 10))
(check-equal? (in-point (q #:in 'sw #:size size #:origin origin)) (pt 5 15))
(check-equal? (in-point (q #:in 's #:size size #:origin origin)) (pt 10 15))
(check-equal? (in-point (q #:in 'se #:size size #:origin origin)) (pt 15 15)))
(test-case
5 years ago
"out points"
(define size (pt 10 10))
(define origin (pt 5 5))
(check-equal? (out-point (q #:out 'nw #:size size #:origin origin)) (pt 5 5))
(check-equal? (out-point (q #:out 'n #:size size #:origin origin)) (pt 10 5))
(check-equal? (out-point (q #:out 'ne #:size size #:origin origin)) (pt 15 5))
(check-equal? (out-point (q #:out 'w #:size size #:origin origin)) (pt 5 10))
(check-equal? (out-point (q #:out 'c #:size size #:origin origin)) (pt 10 10))
(check-equal? (out-point (q #:out 'e #:size size #:origin origin)) (pt 15 10))
(check-equal? (out-point (q #:out 'sw #:size size #:origin origin)) (pt 5 15))
(check-equal? (out-point (q #:out 's #:size size #:origin origin)) (pt 10 15))
(check-equal? (out-point (q #:out 'se #:size size #:origin origin)) (pt 15 15)))
(test-case
5 years ago
"inner points"
(define size '(20 20))
(define orig '(10 10))
5 years ago
(check-equal? (inner-point (position (q #:size size #:inner 'nw) orig)) (pt 10 10))
(check-equal? (inner-point (position (q #:size size #:inner 'n) orig)) (pt 20 10))
(check-equal? (inner-point (position (q #:size size #:inner 'ne) orig)) (pt 30 10))
(check-equal? (inner-point (position (q #:size size #:inner 'e) orig)) (pt 30 20))
(check-equal? (inner-point (position (q #:size size #:inner 'se) orig)) (pt 30 30))
(check-equal? (inner-point (position (q #:size size #:inner 's) orig)) (pt 20 30))
(check-equal? (inner-point (position (q #:size size #:inner 'sw) orig)) (pt 10 30))
(check-equal? (inner-point (position (q #:size size #:inner 'w) orig)) (pt 10 20)))
(test-case
5 years ago
"inner points with offsets"
(define size (pt 10 10))
(define orig (pt 0 0))
(define off (pt (random 100) (random 100)))
(check-equal? (inner-point (position (q #:size size #:inner 'nw #:offset off) orig)) (pt+ '(0 0) off))
(check-equal? (inner-point (position (q #:size size #:inner 'n #:offset off) orig)) (pt+ '(5 0) off))
(check-equal? (inner-point (position (q #:size size #:inner 'ne #:offset off) orig)) (pt+ '(10 0) off))
(check-equal? (inner-point (position (q #:size size #:inner 'e #:offset off) orig)) (pt+ '(10 5) off))
(check-equal? (inner-point (position (q #:size size #:inner 'se #:offset off) orig)) (pt+ '(10 10) off))
(check-equal? (inner-point (position (q #:size size #:inner 's #:offset off) orig)) (pt+ '(5 10) off))
(check-equal? (inner-point (position (q #:size size #:inner 'sw #:offset off) orig)) (pt+ '(0 10) off))
(check-equal? (inner-point (position (q #:size size #:inner 'w #:offset off) orig)) (pt+ '(0 5) off))))
6 years ago
5 years ago
#;(module+ test
(require racket/runtime-path fontland/font)
(define-runtime-path fira "fira.ttf")
6 years ago
(define q1 (q (list 'in 'bi 'out 'bo 'size '(10 10) font-path-key fira 'fontsize 12)))
(define q2 (q (list 'in 'bi 'out 'bo 'size '(10 10) font-path-key fira 'fontsize 24)))
(define q3 (q (list 'in 'bi 'out 'bo 'size '(10 10) font-path-key fira 'fontsize 6)))
#;(position (q #f q1 q2 q3)))
6 years ago
5 years ago
#;(module+ test
5 years ago
(require rackunit)
(define q (q (list 'in 'bi 'out 'bo 'size '(10 10) font-path-key fira 'fontsize 12)))
5 years ago
(check-equal? (ascender q) 935)
(check-equal? (units-per-em q) 1000)
(define ascender-scaled (* (/ (ascender q) (units-per-em q)) (quad-ref q 'fontsize) 1.0))
5 years ago
(check-equal? (in-point q) (list 0 ascender-scaled))
(check-equal? (out-point q) (list 10 ascender-scaled)))