463 lines
18 KiB
LilyPond
463 lines
18 KiB
LilyPond
#(define song-list '())
|
|
#(define song-number 0)
|
|
|
|
#(define (files-in-directory dirname)
|
|
;;; Generate list containing filenames
|
|
(let ((dir (opendir dirname)))
|
|
(let next ((f (readdir dir))
|
|
(files '()))
|
|
(cond ((eof-object? f)
|
|
(closedir dir)
|
|
files)
|
|
(else
|
|
(next (readdir dir) (if (ly:regex-match? (ly:regex-exec (ly:make-regex "^(0|\\.)") f)) files (cons f files))))))))
|
|
|
|
#(define (file-to-stats filename)
|
|
(set! song-list (cons filename song-list)))
|
|
|
|
|
|
#(define additional-page-switch-label-list '())
|
|
#(define additional-page-numbers #f)
|
|
|
|
normalPageNumbers =
|
|
#(define-void-function (parser location) ()
|
|
(set! additional-page-numbers #f)
|
|
)
|
|
additionalPageNumbers =
|
|
#(define-void-function (parser location) ()
|
|
(set! additional-page-numbers #t)
|
|
)
|
|
|
|
#(define (real-page-number layout label)
|
|
(let ((table (ly:output-def-lookup layout 'label-page-table)))
|
|
(if (list? table)
|
|
(assoc-get label table)
|
|
#f))
|
|
)
|
|
|
|
#(define display-pages-list '())
|
|
#(define (build-display-pages-list layout)
|
|
(if (null? display-pages-list)
|
|
(let calculate-display-page ((switch-label-list additional-page-switch-label-list))
|
|
(let* ((label (caar switch-label-list))
|
|
(is-additional (cdar switch-label-list))
|
|
(real-page (real-page-number layout label))
|
|
(rest-switch-label-list (cdr switch-label-list))
|
|
(display-page (if (null? rest-switch-label-list)
|
|
(if is-additional (- real-page 1) real-page)
|
|
(let* ((previous-label (caar rest-switch-label-list))
|
|
(previous-is-additional (cdar rest-switch-label-list))
|
|
(previous-display-page (calculate-display-page rest-switch-label-list)))
|
|
(+ previous-display-page
|
|
(if previous-is-additional
|
|
(if is-additional 0 1)
|
|
(if is-additional
|
|
(- (- real-page (real-page-number layout previous-label)) 1)
|
|
(- real-page (real-page-number layout previous-label)))
|
|
)
|
|
)
|
|
)
|
|
))
|
|
)
|
|
(set! display-pages-list (acons label display-page display-pages-list))
|
|
display-page
|
|
)))
|
|
display-pages-list
|
|
)
|
|
|
|
% TODO:
|
|
% Eigentlich können wir das direkt in oddFooderMarkup und evenFooterMarkup aufrufen
|
|
% vermutlich sogar ohne den delay kram. Wir sollten außerdem einfach nur die property
|
|
% page:page-number-string setzen dann klappts auch mit PDF Seiten
|
|
#(define-markup-command (custom-page-number layout props label real-current-page-number)
|
|
(symbol? number?)
|
|
#:category other
|
|
"
|
|
@cindex referencing page number, in text
|
|
|
|
Reference to a page number. @var{label} is the label set on the referenced
|
|
page (using the @code{\\label} command), @var{gauge} a markup used to estimate
|
|
the maximum width of the page number, and @var{default} the value to display
|
|
when @var{label} is not found.
|
|
|
|
(If the current book or bookpart is set to use roman numerals for page numbers,
|
|
the reference will be formatted accordingly -- in which case the @var{gauge}'s
|
|
width may require additional tweaking.)"
|
|
(let* ((gauge-stencil (interpret-markup layout props "XXX"))
|
|
(x-ext (ly:stencil-extent gauge-stencil X))
|
|
(y-ext (ly:stencil-extent gauge-stencil Y)))
|
|
(ly:stencil-outline
|
|
(ly:make-stencil
|
|
`(delay-stencil-evaluation
|
|
,(delay (ly:stencil-expr
|
|
(let* ((display-page (assq-ref (build-display-pages-list layout) label))
|
|
(real-current-page (if (negative? real-current-page-number) (real-page-number layout label) real-current-page-number))
|
|
(number-type (ly:output-def-lookup layout 'page-number-type))
|
|
(page-markup
|
|
(if (assq-ref additional-page-switch-label-list label)
|
|
(make-concat-markup (list (number-format number-type display-page)
|
|
(make-char-markup (+ 97 (- real-current-page (real-page-number layout
|
|
(let find-earliest-additional-label
|
|
((rest-additional-page-switch-label-list (member (cons label #t) additional-page-switch-label-list)))
|
|
(if (cdadr rest-additional-page-switch-label-list)
|
|
(find-earliest-additional-label (cdr rest-additional-page-switch-label-list))
|
|
(caar rest-additional-page-switch-label-list)))
|
|
))))))
|
|
(number-format number-type (+ display-page (- real-current-page (real-page-number layout label))))
|
|
))
|
|
(page-stencil (interpret-markup layout props page-markup))
|
|
(gap (- (interval-length x-ext)
|
|
(interval-length (ly:stencil-extent page-stencil X)))))
|
|
(interpret-markup layout props
|
|
(make-line-markup
|
|
(list
|
|
(make-hspace-markup gap)
|
|
page-markup)))))))
|
|
x-ext
|
|
y-ext)
|
|
(make-filled-box-stencil x-ext y-ext))))
|
|
|
|
|
|
includeSong =
|
|
#(define-void-function (filename) (string?)
|
|
#{
|
|
\bookOutputName #filename
|
|
#}
|
|
(ly:parser-parse-string (ly:parser-clone)
|
|
(ly:format "\\include \"~a/~a/~a.ly\"" songPath filename filename))
|
|
(let ((label (gensym "index")))
|
|
(set! additional-page-switch-label-list
|
|
(acons label additional-page-numbers additional-page-switch-label-list))
|
|
(set! song-list
|
|
(acons (string->symbol filename)
|
|
`(
|
|
(label . ,label)
|
|
(header . ,HEADER)
|
|
(music . ,MUSIC)
|
|
(layout . ,LAYOUT)
|
|
(text-pages . ,(map (lambda (text)
|
|
#{ \markuplist \setsongfilename $filename $text #})
|
|
TEXT_PAGES))
|
|
)
|
|
song-list))
|
|
))
|
|
|
|
blankpage =
|
|
#(define-void-function () ()
|
|
(set! song-list
|
|
(acons 'emptyPage
|
|
'()
|
|
song-list)))
|
|
|
|
imagepage =
|
|
#(define-void-function (xsize filename) (number? string?)
|
|
(set! song-list
|
|
(acons 'imagePage
|
|
`((xsize . ,xsize)
|
|
(filename . ,filename))
|
|
song-list)))
|
|
|
|
markuppage =
|
|
#(define-void-function (mkup) (markup?)
|
|
(set! song-list
|
|
(acons 'markupPage
|
|
`((markup . ,mkup))
|
|
song-list)))
|
|
|
|
#(define chapters '())
|
|
|
|
songsChapter =
|
|
#(define-void-function (title) (string?)
|
|
(set! chapters
|
|
(cons (cons title (length song-list)) chapters)))
|
|
|
|
#(define-markup-command (pagecenter layout props stuff)(markup?)
|
|
(interpret-markup layout props
|
|
(let ((halfpaperheight (/ (ly:output-def-lookup layout 'paper-height) 2))
|
|
(halfstuffheight (/ (interval-length (ly:stencil-extent (interpret-markup layout props stuff) Y)) 2)))
|
|
(make-fill-line-markup (list (make-pad-to-box-markup '(0 . 0) (cons (- (- halfpaperheight halfstuffheight)) (+ halfpaperheight halfstuffheight)) stuff)))
|
|
)))
|
|
|
|
songs =
|
|
#(define-void-function () ()
|
|
(let* ((ordered (reverse song-list))
|
|
(total (length ordered))
|
|
;; Ein Lied erzeugt nur dann einen PDF-Outline-Eintrag (/OUT), wenn
|
|
;; title oder starttext gesetzt sind (siehe build-full-title). Nur solche
|
|
;; Lieder duerfen als Kapitel-Kinder gezaehlt werden -- sonst wird die
|
|
;; /Count zu hoch und der erste Folgeeintrag (z.B. ein spaeteres Kapitel)
|
|
;; rutscht faelschlich als Kind mit hinein.
|
|
(emits-bookmark?
|
|
(lambda (e)
|
|
(and (not (eq? (car e) 'emptyPage))
|
|
(not (eq? (car e) 'imagePage))
|
|
(not (eq? (car e) 'markupPage))
|
|
(let ((vars (extract-and-check-vars-from-header
|
|
(assq-ref (cdr e) 'header) '(title starttext))))
|
|
(if (or (assq-ref vars 'title) (assq-ref vars 'starttext)) #t #f)))))
|
|
(count-bookmark-songs
|
|
(lambda (start end)
|
|
(let loop ((i 0) (l ordered) (c 0))
|
|
(cond ((or (null? l) (>= i end)) c)
|
|
((< i start) (loop (+ i 1) (cdr l) c))
|
|
(else (loop (+ i 1) (cdr l) (if (emits-bookmark? (car l)) (+ c 1) c)))))))
|
|
;; chapters: (title . start-index). start-index = Anzahl der song-list-
|
|
;; Eintraege beim Aufruf von songsChapter = Index in `ordered`, ab dem
|
|
;; das Kapitel beginnt.
|
|
(chapters-asc (sort chapters (lambda (a b) (< (cdr a) (cdr b)))))
|
|
;; pending: aufsteigende Liste von (start title count); count = Anzahl
|
|
;; der Lesezeichen-Lieder von diesem Kapitel bis zum Beginn des naechsten.
|
|
(pending
|
|
(let loop ((chs chapters-asc) (acc '()))
|
|
(if (null? chs)
|
|
(reverse acc)
|
|
(let* ((title (caar chs))
|
|
(start (cdar chs))
|
|
(end (if (null? (cdr chs)) total (cdadr chs)))
|
|
(cnt (count-bookmark-songs start end)))
|
|
(loop (cdr chs) (cons (list start title cnt) acc))))))
|
|
;; Stellt einer beliebigen Seite (markup-/image-/emptyPage) den Kapitel-
|
|
;; Eintrag (chapter-to-pdf-toc) bei, falls an Position `this-index` ein
|
|
;; Kapitel faellig ist, und entfernt es aus `pending`. So fuehrt die
|
|
;; Kapitel-Referenz zur ersten Seite nach dem \songsChapter -- egal
|
|
;; welcher Seitentyp das ist. Der Eintrag wird als unsichtbares Overlay
|
|
;; (\combine) beigelegt, damit er das Seitenlayout nicht veraendert;
|
|
;; \with-dimensions gibt dem sonst extent-losen pdfmark einen minimalen
|
|
;; Extent, damit er auch auf leeren Seiten nicht verworfen wird.
|
|
(chapter-prefixed-markup
|
|
(lambda (this-index base-markup)
|
|
(if (and (pair? pending) (<= (car (car pending)) this-index))
|
|
(let ((cnt (caddr (car pending)))
|
|
(title (cadr (car pending))))
|
|
(set! pending (cdr pending))
|
|
(make-combine-markup
|
|
base-markup
|
|
(make-with-dimensions-markup '(0 . 0) '(0 . 0.1)
|
|
(make-chapter-to-pdf-toc-markup cnt title))))
|
|
base-markup)))
|
|
(index 0))
|
|
(for-each (lambda (songitems)
|
|
(let ((this-index index))
|
|
(set! index (+ index 1))
|
|
(ly:book-add-bookpart! (ly:parser-lookup '$current-book)
|
|
(let ((filename (car songitems))
|
|
(songvars (cdr songitems)))
|
|
(if (eq? filename 'emptyPage)
|
|
#{ \bookpart { \markup { $(chapter-prefixed-markup this-index (make-null-markup)) } } #}
|
|
(if (eq? filename 'markupPage)
|
|
;; Kapitelanfang auf einer markuppage: Kapitel-Eintrag wird dieser
|
|
;; Seite beigelegt (siehe chapter-prefixed-markup).
|
|
#{ \bookpart { \markup { $(chapter-prefixed-markup this-index (assq-ref songvars 'markup)) } } #}
|
|
(if (eq? filename 'imagePage)
|
|
(let* ((xsize (assq-ref songvars 'xsize))
|
|
(filename (ly:format "~a/~a" imagePagePath (assq-ref songvars 'filename)))
|
|
(img-markup #{ \markup { \pagecenter { \epsfile #X #xsize #filename } } #}))
|
|
#{ \bookpart {
|
|
\paper {
|
|
%{
|
|
inner-margin = 0
|
|
outer-margin = 0
|
|
binding-offset = 0
|
|
top-margin = 0
|
|
bottom-margin = 0
|
|
%}
|
|
print-page-number = ##f
|
|
last-bottom-spacing = #'((basic-distance . 0) (minimum-distance . 0) (padding . 0))
|
|
page-count = 1
|
|
}
|
|
\markup { $(chapter-prefixed-markup this-index img-markup) }
|
|
} #}
|
|
)
|
|
;; Ist ein Kapitel faellig (Start <= aktuelle Position) und dies ein
|
|
;; echtes Lied, wird die Kapitelinfo an dieses Lied gehaengt und aus
|
|
;; `pending` entfernt.
|
|
(let* ((newnumber (+ 1 song-number))
|
|
(chaptertitle (if (and (pair? pending) (<= (car (car pending)) this-index))
|
|
(cadr (car pending)) #f))
|
|
(chaptercount (if chaptertitle (caddr (car pending)) 0))
|
|
(header #{ \bookpart { $(assq-ref songvars 'header) \header {
|
|
songfilename = $(symbol->string filename)
|
|
myindexlabel = #(assq-ref songvars 'label)
|
|
songnumber = #(number->string newnumber)
|
|
chaptertitle = #chaptertitle
|
|
chaptercount = #chaptercount
|
|
} } #})
|
|
(music (assq-ref songvars 'music))
|
|
(layout (assq-ref songvars 'layout))
|
|
(text-pages (assq-ref songvars 'text-pages))
|
|
(label (assq-ref songvars 'label)))
|
|
(if chaptertitle (set! pending (cdr pending)))
|
|
(set! song-number newnumber)
|
|
#{
|
|
\bookpart {
|
|
$header
|
|
\headerToTOC #header #label
|
|
\score { $music \layout { $layout songfilename = $(symbol->string filename) } }
|
|
$(add-text-pages text-pages)
|
|
}
|
|
#}))))))))
|
|
ordered
|
|
)))
|
|
|
|
#(define (boernel-stats)
|
|
(let (
|
|
(songs (map (lambda (song) (symbol->string (car song))) (alist-delete 'emptyPage song-list)))
|
|
(opticalline "---------------------------------------------------------"))
|
|
(ly:warning (string-join (list
|
|
opticalline
|
|
(string-concatenate (list "Inkludiert: " (number->string (length songs)) " Lieder\n"))
|
|
;(string-join songs "\n")
|
|
"Nicht inkludiert:"
|
|
opticalline
|
|
(string-join (sort-list (lset-difference string=? (files-in-directory songPath) songs) string<?) "\n")
|
|
opticalline
|
|
) "\n" )
|
|
)))
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
%% Include Images once and reference them:
|
|
#(define bbox-regexp
|
|
(ly:make-regex "%%BoundingBox:[ \t]+([0-9-]+)[ \t]+([0-9-]+)[ \t]+([0-9-]+)[ \t]+([0-9-]+)"))
|
|
|
|
#(define (get-postscript-bbox string)
|
|
"Extract the bbox from STRING, or return #f if not present."
|
|
(let*
|
|
((match (ly:regex-exec bbox-regexp string)))
|
|
|
|
(if match
|
|
(map (lambda (x)
|
|
(string->number (ly:regex-match-substring match x)))
|
|
(cdr (iota 5)))
|
|
|
|
#f)))
|
|
|
|
|
|
#(define eps-references '())
|
|
|
|
#(define-public (eps-file-ref->stencil axis size just-embed file-name)
|
|
(let*
|
|
((already-embedded (assq (string->symbol file-name) eps-references))
|
|
(counter (if already-embedded (cadr already-embedded) (if (null-list? eps-references) 1 (+ 1 (cadar eps-references)))))
|
|
(form-name (ly:format "IDForm~a" counter))
|
|
(data-name (ly:format "ImageData~a" counter))
|
|
(eps-content (ly:gulp-file file-name))
|
|
(contents (ly:format "~a execform" form-name))
|
|
(bbox (if already-embedded (cddr already-embedded) (get-postscript-bbox (car (string-split eps-content #\nul)))))
|
|
(bbox-size (if (= axis X)
|
|
(- (list-ref bbox 2) (list-ref bbox 0))
|
|
(- (list-ref bbox 3) (list-ref bbox 1))
|
|
))
|
|
(factor (if (< 0 bbox-size)
|
|
(exact->inexact (/ size bbox-size))
|
|
0))
|
|
(scaled-bbox
|
|
(map (lambda (x) (* factor x)) bbox))
|
|
;; We need to shift the whole eps to (0,0), otherwise it will appear
|
|
;; displaced in lilypond (displacement will depend on the scaling!)
|
|
(translate-string (ly:format "~a ~a translate" (- (list-ref bbox 0)) (- (list-ref bbox 1))))
|
|
(clip-rect-string (ly:format
|
|
"~a ~a ~a ~a rectclip"
|
|
(list-ref bbox 0)
|
|
(list-ref bbox 1)
|
|
(- (list-ref bbox 2) (list-ref bbox 0))
|
|
(- (list-ref bbox 3) (list-ref bbox 1)))))
|
|
|
|
(if (not already-embedded) (set! eps-references
|
|
(acons (string->symbol file-name) (cons counter bbox) eps-references)))
|
|
(if bbox
|
|
(ly:make-stencil
|
|
(list
|
|
'embedded-ps
|
|
(string-append
|
|
(if (and already-embedded (not just-embed)) "" (ly:format
|
|
"
|
|
/~a
|
|
currentfile
|
|
<< /Filter /SubFileDecode
|
|
/DecodeParms << /EODCount 0 /EODString (*EOD*) >>
|
|
>> /ReusableStreamDecode filter
|
|
~a
|
|
*EOD*
|
|
def
|
|
|
|
/~a
|
|
<< /FormType 1
|
|
/BBox [~a ~a ~a ~a]
|
|
/Matrix [ 1 0 0 1 0 0]
|
|
/PaintProc
|
|
{ pop
|
|
/ostate save def
|
|
/showpage {} def
|
|
/setpagedevice /pop load def
|
|
~a 0 setfileposition ~a cvx exec
|
|
ostate restore
|
|
} bind
|
|
>> def
|
|
" data-name eps-content form-name (list-ref bbox 0) (list-ref bbox 1) (list-ref bbox 2) (list-ref bbox 3) data-name data-name))
|
|
(if just-embed "" (ly:format
|
|
"
|
|
gsave
|
|
currentpoint translate
|
|
BeginEPSF
|
|
~a dup scale
|
|
~a
|
|
~a
|
|
%%BeginDocument: ~a
|
|
~a
|
|
%%EndDocument
|
|
EndEPSF
|
|
grestore
|
|
" factor translate-string clip-rect-string file-name contents
|
|
))))
|
|
;; Stencil starts at (0,0), since we have shifted the eps, and its
|
|
;; size is exactly the size of the scaled bounding box
|
|
(if just-embed '(0 . 0) (cons 0 (- (list-ref scaled-bbox 2) (list-ref scaled-bbox 0))))
|
|
(if just-embed '(0 . 0) (cons 0 (- (list-ref scaled-bbox 3) (list-ref scaled-bbox 1)))))
|
|
|
|
(ly:make-stencil "" '(0 . 0) '(0 . 0)))
|
|
))
|
|
|
|
#(define-markup-command (epsfileref layout props axis size file-name)
|
|
(number? number? string?)
|
|
#:category graphic
|
|
"
|
|
@cindex inlining an Encapsulated PostScript image
|
|
|
|
Inline an EPS image. The image is scaled along @var{axis} to
|
|
@var{size}.
|
|
|
|
@lilypond[verbatim,quote]
|
|
\\markup {
|
|
\\general-align #Y #DOWN {
|
|
\\epsfile #X #20 #\"context-example.eps\"
|
|
\\epsfile #Y #20 #\"context-example.eps\"
|
|
}
|
|
}
|
|
@end lilypond"
|
|
(if (ly:get-option 'safe)
|
|
(interpret-markup layout props "not allowed in safe")
|
|
(eps-file-ref->stencil axis size #f file-name)
|
|
))
|
|
|
|
#(define-markup-command (epsfileembed layout props file-name)
|
|
(string?)
|
|
#:category graphic
|
|
"
|
|
@cindex inlining an Encapsulated PostScript image
|
|
|
|
Inline an EPS image. The image is scaled along @var{axis} to
|
|
@var{size}.
|
|
|
|
@lilypond[verbatim,quote]
|
|
\\markup {
|
|
\\general-align #Y #DOWN {
|
|
\\epsfile #X #20 #\"context-example.eps\"
|
|
\\epsfile #Y #20 #\"context-example.eps\"
|
|
}
|
|
}
|
|
@end lilypond"
|
|
(if (ly:get-option 'safe)
|
|
(interpret-markup layout props "not allowed in safe")
|
|
(eps-file-ref->stencil X 30 #t file-name)
|
|
))
|