+;;;; LilyPond is free software: you can redistribute it and/or modify
+;;;; it under the terms of the GNU General Public License as published by
+;;;; the Free Software Foundation, either version 3 of the License, or
+;;;; (at your option) any later version.
+;;;;
+;;;; LilyPond is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
+
+;; Utility functions
+
+(define (string-x-extent start-point end-point)
+ "Return the x-extent of a string that goes from start-point
+to end-point."
+ (let ((x1 (car start-point))
+ (x2 (car end-point)))
+ (if (> x1 x2)
+ (cons x2 x1)
+ (cons x1 x2))))
+
+(define (string-y-extent start-point end-point)
+ "Return the y-extent of a string that goes from start-point
+to end-point."
+ (let ((y1 (cdr start-point))
+ (y2 (cdr end-point)))
+ (if (> y1 y2)
+ (cons y2 y1)
+ (cons y1 y2))))
+
+
+(define (cons-fret new-value old-list)
+ "Put together a fret-list in the format desired by parse-string"
+ (if (eq? old-list '())
+ (list new-value)
+ (cons* new-value old-list)))
+
+(define (get-numeric-from-key keystring)
+ "Get the numeric value from a key of the form k:val"
+ (string->number (substring keystring 2 (string-length keystring))))
+
+(define (numerify mylist)
+ "Convert string values to numeric or character"
+ (if (null? mylist)
+ '()
+ (let ((numeric-value (string->number (car mylist))))
+ (if numeric-value
+ (cons* numeric-value (numerify (cdr mylist)))
+ (cons* (car (string->list (car mylist)))
+ (numerify (cdr mylist)))))))
+
+(define (stepmag mag)
+ "Calculate the font step necessary to get a desired magnification"
+ (* 6 (/ (log mag) (log 2))))
+
+(define (fret-count fret-range)
+ "Calculate the fret count for the diagram given the range of frets in the diagram."
+ (1+ (- (cdr fret-range) (car fret-range))))
+
+(define (dot-has-color dot-settings)
+ "Return a color-name as symbol, if found in @var{dot-settings} otherwise @code{#f}"
+ (cond ((null? dot-settings)
+ #f)
+ ;; Don't bother the user with quote/unquote.
+ ;; We use the name-symbol for the color, looking up in 'x11-color-list'
+ ((member (car dot-settings) (map car x11-color-list))
+ (car dot-settings))
+ (else (dot-has-color (cdr dot-settings)))))
+
+(define (dot-is-inverted dot-settings)
+ "Return @code{'inverted}, if found in @var{dot-settings} otherwise @code{'()}"
+ (let ((inverted (member 'inverted dot-settings)))
+ (if inverted
+ (car inverted)
+ '())))
+
+(define (dot-is-parenthesized dot-settings)
+ "Return @code{'parenthesized}, if found in @var{dot-settings} otherwise @code{'()}"
+ (let ((parenthesized (member 'parenthesized dot-settings)))
+ (if parenthesized
+ (car parenthesized)
+ '())))
+
+;; If @code{'default-paren-color} is not set, the parenthesis will take their
+;; color from the dot.
+;; Setting @code{'default-paren-color} will result in taking the color from
+;; `what-color', see below.
+(define (default-paren-color dot-settings)
+ "Return @code{'default-paren-color}, if found in @var{dot-settings} otherwise @code{'()}"
+ (let ((default-color (member 'default-paren-color dot-settings)))
+ (if default-color
+ (car default-color)
+ '())))
+
+(define (subtract-base-fret base-fret dot-list)
+ "Subtract @var{base-fret} from every fret in @var{dot-list}"
+ (if (null? dot-list)
+ '()
+ (let ((this-list (car dot-list)))
+ (cons* (list
+ ;; string
+ (car this-list)
+ ;; fret
+ (- (second this-list) base-fret)
+ ;; finger
+ (if (or (null? (cddr this-list))
+ (not (number? (caddr this-list))))
+ '()
+ (third this-list))
+ ;; inverted
+ (dot-is-inverted this-list)
+ ;; parenthesis
+ (dot-is-parenthesized this-list)
+ ;; color modifiers
+ ;; parenthesis
+ (default-paren-color this-list)
+ ;; dots
+ (let ((colored (dot-has-color this-list)))
+ (if colored
+ colored
+ '())))
+ (subtract-base-fret base-fret (cdr dot-list))))))
+
+(define (drop-paren item-list)
+ "Drop a final parentheses from a fret indication list
+@code{item-list} resulting from a terse string specification of barre."
+ (if (> (length item-list) 0)
+ (let* ((max-index (- (length item-list) 1))
+ (last-element (car (list-tail item-list max-index))))
+ (if (or (equal? last-element ")") (equal? last-element "("))
+ (list-head item-list max-index)
+ item-list))
+ item-list))
+
+(define (get-sub-list value master-list)
+ "Get a sub-list whose cadr is equal to @var{value} from @var{master-list}"
+ (if (eq? master-list '())
+ #f
+ (let ((sublist (car master-list)))
+ (if (equal? (cadr sublist) value)
+ sublist
+ (get-sub-list value (cdr master-list))))))
+
+(define (merge-details key alist-list . default)
+ "Return @code{alist-list} entries for @code{key}, in one combined alist.
+There can be two @code{alist-list} entries for a given key. The first
+comes from the override-markup function, the second comes
+from property settings during a regular override.
+This is necessary because some details can be set in one
+place, while others are set in the other. Both details
+lists must be merged into a single alist.
+Return @code{default} (optional, else #f) if not
+found."
+
+ (define (helper key alist-list default)
+ (if (null? alist-list)
+ default
+ (let* ((entry (assoc-get key (car alist-list))))
+ (if entry
+ (append entry (chain-assoc-get key (cdr alist-list) '()))
+ (helper key (cdr alist-list) default)))))
+
+ (helper key alist-list
+ (if (pair? default) (car default) #f)))
+
+;; Conversions between fret/string coordinate system and x-y coordinate
+;; system.
+;;
+;; Fret coordinates are measured down the fretboard from the nut,
+;; starting at 0.
+;;
+;; String coordinates are measured from the lowest string, starting at 0.
+;;
+;; The x-y origin is at the intersection of the nut and the lowest string.
+;;
+;; X coordinates are positive to the right.
+;; Y coordinates are positive up.
+
+(define (negate-extent extent)
+ "Return the extent in an axis opposite to the axis of @code{extent}."
+ (cons (- (cdr extent)) (- (car extent))))
+
+(define (stencil-fretboard-extent stencil fretboard-axis orientation)
+ "Return the extent of @code{stencil} in the @code{fretboard-axis}
+direction."
+ (if (eq? fretboard-axis 'fret)
+ (cond ((eq? orientation 'landscape)
+ (ly:stencil-extent stencil X))
+ ((eq? orientation 'opposing-landscape)
+ (negate-extent (ly:stencil-extent stencil X)))
+ (else
+ (negate-extent (ly:stencil-extent stencil Y))))
+ ;; else -- eq? fretboard-axis 'string
+ (cond ((eq? orientation 'landscape)
+ (ly:stencil-extent stencil Y))
+ ((eq? orientation 'opposing-landscape)
+ (negate-extent (ly:stencil-extent stencil Y)))
+ (else
+ (ly:stencil-extent stencil Y)))))
+
+
+(define (stencil-fretboard-offset stencil fretboard-axis orientation)
+ "Return a the stencil coordinates of the center of @code{stencil}
+in the @code{fretboard-axis} direction."
+ (* 0.5 (interval-length
+ (stencil-fretboard-extent stencil fretboard-axis orientation))))
+
+
+(define (string-thickness string thickness-factor)
+ (expt (1+ thickness-factor) (1- string)))
+
+;; Functions that create stencils used in the fret diagram
+
+(define (sans-serif-stencil layout props mag text)
+ "Create a stencil in sans-serif font based on @var{layout} and @var{props}
+with magnification @var{mag} of the string @var{text}."
+ (let* ((my-props
+ (prepend-alist-chain
+ 'font-size (stepmag mag)
+ (prepend-alist-chain 'font-family 'sans props))))
+ (interpret-markup layout my-props text)))
+
+;; markup commands and associated functions