aboutsummaryrefslogtreecommitdiff
path: root/module/text/flow.scm
blob: 77f39a9f87d5eb5d6be734029d4407b9a154c7ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
;;; Commentary:
;;; @var{flow-text} take a (multiline) string, and justifies
;;; its contents. All linebreaks are treated as hard line breaks.
;;; Code:

(define-module (text flow)
  :use-module (hnh util)
  :use-module (text util)
  :use-module (srfi srfi-1)
  :use-module (srfi srfi-71)
  :use-module (srfi srfi-88)
  :export (flow-text))


;; str -> (str)
(define* (flow-text str key: (width 70))
  (flatten
   (map (lambda (line) (justify-line line width: width))
        (lines str))))



;; Splits and justifies the given line to @var{width:}.
;; Returns a list of justified strings.
;; str -> (str)
(define* (justify-line line key: (width 70))
  (let recur ((lst (words line)))
    (let ((head tail (span
                      (let ((w 0))
                        (lambda (word)    ; Take words until we are above the limit.
                          (< (set/r! w = (+ 1 (true-string-length word)))
                             width)))
                      lst)))
      (cond ((null? tail) (list (unwords head))) ; Don't justify last line.
            ((null? head)
             ;; an empty head implies that we found a word longer
             ;; than our max width. Add it as is and continue
             ;; (while crying).
             (cons (car tail) (recur (cdr tail))))
            (else (cons (justify-line-helper head width: width)
                        (recur tail)))))))

;; (str) -> str
(define* (justify-line-helper words key: (width 70))
  (let* ((phrase-length (true-string-length (string-concatenate/shared words)))
         (needed-spaces (- width phrase-length))
         (slots (1- (length words)))
         (space-list
          (let loop ((n needed-spaces) (d slots))
            (unless (zero? d)
              (let ((v (round (/ n d))))
                (cons v (loop (- n v)
                              (1- d))))))))
    (string-concatenate/shared
     (merge words (map (lambda (n) (make-string n #\space))
                       space-list)
            (let ((f #t)) (lambda _ (set/r! f = not)))))))