Autor Thema: Befehl columns (aus LSR 464) erweitert  (Gelesen 1994 mal)

martinmagtenor

  • Member
Befehl columns (aus LSR 464) erweitert
« am: Sonntag, 12. April 2015, 18:58 »
Hallo Lilypond-Fans,

im LSR-Beitrag 464 ist der Befehl \columns beschrieben, der es erlaubt mehrere Markups als Spalten gleicher Breite nebeneinander zu setzen. Das schöne daran ist, dass die Breite der Seite automatisch gleichmäßig verteilt wird, so dass man nicht rechnen muss und auch bei Formatänderungen nicht nachjustieren braucht.

Anlässlich des Setzens verschiedener Orgelsätze (mit book/bookpart) wollte ich auf der Rückseite des Deckblattes Vorwort und Inhaltsverzeichnis nebeneinander stellen. Dabei fiel mir auf, dass zwischen den Spalten, die \columns erzeugt, kein Zwischenraum ist. Das war der Anlass nach einer Lösung zu suchen.

Das Ergebnis wird im folgenden vorgestellt. Selbstverständlich mit einem kompilierbaren Beispiel.

Der Code ist mit Lilypond 2.16.2 entwickelt. Mutmaßlich läuft der auch mit 2.14 und 2.18.

Das sind die von mir vorgenommenen Erweiterungen:

  • let durch let* ersetzt, damit garantiert ist, dass die Zeilenlänge zuerst berechnet wird!
  • Berechnung der neuen Zeilenlänge für die Spalten so modifiziert, dass ein Abstand geeignet abgezogen wird.
  • Abstand als lokale Variable ergänzt.
  • Der map-Ausdruck im Argument von make-line-markup erzeugt aus jeder Spalte (in Argument args) ein markup definierter Breite. Zwischen jedes Markup wird jetzt noch ein Zwischenraum "dazwischen geklemmt".

Hier nun der erweiterte Code aus dem LSR:

% Inspiriert von http://lsr.di.unimi.it/LSR/Snippet?id=464
% Mit eigenen Ergänzungen: Abstand zwischen den Spalten.
#(define-markup-command (columns layout props args) (markup-list?)
   (let* ((col-sep (ly:cm 0.5))
     (line-width (- (/ (chain-assoc-get 'line-width props
                         (ly:output-def-lookup layout 'line-width))
(max (length args) 1))
; Teillänge L1=L0/n - (1-1/n)*Lt
(* (- 1 (/ 1 (max (length args) 1))) col-sep))))
     (interpret-markup layout props
       (make-line-markup
((lambda (between list)
    (letrec ((p (lambda (b l)
(if (null? (cdr l)) (cons b l)
    (cons b (cons (car l) (p b (cdr l))))))))
     ; check cases
     (cond
      ((and (list? between) (null? between)) list)
      ((or (null? between) (null? list)) list)
      ; list contains only one element
      ((null? (cdr list)) list)
      ; process list and skip first between
      (else (cdr (p between list)))
     ) ; cond
    ) ; letrec
   ) (markup #:pad-to-box `(0 . ,col-sep) '(0 . 0)
#:override `(col-sep . ,col-sep) " ")
(map (lambda (line)
(markup #:pad-to-box `(0 . ,line-width) '(0 . 0)
#:override `(line-width . ,line-width)
line))
args))))))

Selbstverständlich ist noch Raum für Verbesserungen:
  • Der Spaltenabstand kann derzeit nicht als Parameter übergeben werden. Das bedeutet, man muss diesen Programmtext immer in die  jeweilige Lilypond-Quell-Datei kopieren.
  • Vielleicht bietet sich für diesen Abstand auch ein System-Parameter an, der bereits existiert und eine entsprechende Semantik hat.
  • Der markup-Ausdruck für den Zwischenrarum lässt sich möglicherweise noch weiter vereinfachen.

Verbesserungsvorschläge und sonstige Anmerkungen immer gerne.

Und hier noch das versprochene kompilierbare Beispiel:
\version "2.16.2"

#(set-default-paper-size "a4" 'landscape)
#(ly:set-option 'point-and-click #f)

% Inspiriert von http://lsr.di.unimi.it/LSR/Snippet?id=464
% Mit eigenen Ergänzungen: Abstand zwischen den Spalten, um zu verhindern, dass
% der Text "aneinander klebt".

#(define-markup-command (columns layout props args) (markup-list?)
   (let* ((col-sep (ly:cm 0.5))
     (line-width (- (/ (chain-assoc-get 'line-width props
                         (ly:output-def-lookup layout 'line-width))
                        (max (length args) 1))
                        (* (- 1 (/ 1 (max (length args) 1))) col-sep))))
     (interpret-markup layout props                                                                                               
       (make-line-markup           
         ((lambda (between list)
            (letrec ((p (lambda (b l)
                (if (null? (cdr l)) (cons b l)
                    (cons b (cons (car l) (p b (cdr l))))))))
             ; check cases
             (cond
              ((and (list? between) (null? between)) list)
              ((or (null? between) (null? list)) list)
              ; list contains only one element
              ((null? (cdr list)) list)
              ; process list and skip first between
              (else (cdr (p between list)))
             ) ; cond
            ) ; letrec
           ) (markup #:pad-to-box `(0 . ,col-sep) '(0 . 0)
                        #:override `(col-sep . ,col-sep) " ")
                (map (lambda (line)
                                (markup #:pad-to-box `(0 . ,line-width) '(0 . 0)
                                        #:override `(line-width . ,line-width)
                                        line))
                        args))))))

\book {
  \bookpart {
    \markup {
      \columns {
        \column {
          \fill-line \huge { \null Lorem \null }
          \vspace #1
          \justify { Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
          sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
          erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
          rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum
          dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
          diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
          sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
          Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
          amet.
          }
        }
        \column {
          \fill-line \huge { \null ipsum \null }
          \vspace #1
          \justify { Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
          sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
          erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
          rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum
          dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
          diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
          sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
          Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
          amet.
          }
        }
      }
    }

    \paper {
      oddHeaderMarkup = \markup { \null }
      evenHeaderMarkup = \markup { \null }
      oddFooterMarkup = \markup { \null }
      evenFooterMarkup = \markup { \null }
    }
  }
}


Grüße
  Martin

harm6

  • Member
Re: Befehl columns (aus LSR 464) erweitert
« Antwort #1 am: Montag, 13. April 2015, 01:20 »
Hallo Martin,

schöne Arbeit!
Ich hab' mir Deinen Code mal etwas genauer angesehen. ;)

Eine große Vereinfachung ist möglich, wenn Du markup-join verwendest. Da dieser Befehl aber nicht public ist (keine Ahnung warum ...) habe ich ihn aus markup.scm rauskopiert und in den code kopiert.

sep-col kann man als property setzen und mit \override #'(sep-col . <was-auch-immer>) verwenden (ein auskommentiertes Beispiel findest Du im code.

Das größte Manko ist die Berechnung der line-width, sichtbar wenn Du mal versuchst (viel) mehr Kolumnen auszugeben.
Meine eigene Berechnung ist nur etwas genauer, fliegt aber auch aus der Kurve sobald Du das auskommentierte #:box reinnimmst.
Irgendetwas ist da noch buggy. Bin aber heute zu müde, um da weiter zu denken ...

Die Anwendungsbeispiele habe ich abstrakt codiert (das file wird sonst zu unübersichtlich).


Hier die Gegenüberstellung meines und Deines Codes:

\version "2.16.2"

#(set-default-paper-size "a3" )
\pointAndClickOff

% Inspiriert von http://lsr.di.unimi.it/LSR/Snippet?id=464
% Mit eigenen Ergänzungen: Abstand zwischen den Spalten, um zu verhindern, dass
% der Text "aneinander klebt".

#(define-markup-command (columns layout props args) (markup-list?)
   (let* ((col-sep (ly:cm 0.5))
     (line-width (- (/ (chain-assoc-get 'line-width props
                         (ly:output-def-lookup layout 'line-width))
                        (max (length args) 1))
                        (* (- 1 (/ 1 (max (length args) 1))) col-sep))))
     (interpret-markup layout props                                                                                               
       (make-line-markup           
         ((lambda (between list)
            (letrec ((p (lambda (b l)
                (if (null? (cdr l)) (cons b l)
                    (cons b (cons (car l) (p b (cdr l))))))))
             ; check cases
             (cond
              ((and (list? between) (null? between)) list)
              ((or (null? between) (null? list)) list)
              ; list contains only one element
              ((null? (cdr list)) list)
              ; process list and skip first between
              (else (cdr (p between list)))
             ) ; cond
            ) ; letrec
           ) (markup  #:pad-to-box `(0 . ,col-sep) '(0 . 0)
                        #:override `(col-sep . ,col-sep) " ")
                (map (lambda (line)
                                (markup #:pad-to-box `(0 . ,line-width) '(0 . 0)
                                        #:override `(line-width . ,line-width)
                                        line))
                        args))))))
                       
                       
txt =
\markup
  \justify { Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
  sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
  erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
  rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum
  dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
  diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
  sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
  Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
  amet.
  }
 
mrkp =
\markup {
  \column {
    \fill-line \huge { \null Lorem \null }
    \vspace #1
    \txt
  }
}

\markup \fill-line { \fontsize #8 "martinmagtenor" }

\markup \columns #(make-list 1 mrkp)
%\markup \columns #(make-list 2 mrkp)
%\markup \columns #(make-list 3 mrkp)
%\markup \columns #(make-list 4 mrkp)
%\markup \columns #(make-list 5 mrkp)
\markup \columns #(make-list 8 mrkp)

\pageBreak

%% c/p from scm/markup.scm
#(define (markup-join markups sep)
  "Return line-markup of MARKUPS, joining them with markup SEP"
  (if (pair? markups)
      (make-line-markup (list-insert-separator markups sep))
      empty-markup))
     
#(define-markup-command (columns-harm layout props args) (markup-list?)
   #:properties ((col-sep  0.5))
   (let* ((line-thickness (ly:output-def-lookup layout 'line-thickness))
          (line-width (/ (- (chain-assoc-get 'line-width props
                               (ly:output-def-lookup layout 'line-width))
                            (* (- (max (length args) 1) 1) (ly:cm col-sep))
                            (* (- (max (length args) 1) 1) 0.5))
                         (max (length args) 1))))

     (interpret-markup layout props
       (make-line-markup (list
         (markup-join
           (map
             (lambda (arg)
               (markup
                 ;#:box
                 #:pad-to-box `(0 . ,line-width) '(0 . 0)
                 #:override `(line-width . ,line-width)
                 arg))
              args)
           (markup #:hspace (ly:cm col-sep))))))))

\markup \fill-line { \fontsize #8 "Harm" }

\markup \columns-harm #(make-list 1 mrkp)
%\markup \override #'(col-sep . 1) \columns-harm  #(make-list 2 mrkp)
%\markup \columns-harm #(make-list 3 mrkp)
%\markup \columns-harm #(make-list 4 mrkp)
%\markup \columns-harm #(make-list 5 mrkp)
\markup \columns-harm #(make-list 8 mrkp)

Gruß,
  Harm

P.S.:
Bist Du wirklich noch auf 2.16.2?
Ich finde die aktuelle stable (2.18.2) schon zu outdated ...

martinmagtenor

  • Member
Re: Befehl columns (aus LSR 464) erweitert
« Antwort #2 am: Montag, 13. April 2015, 19:50 »
Hallo Harm,

danke für die Blumen.   ;-)

Ich habe die Berechnung der Breite überprüft und etwas berechnungsfreundlicher umformuliert, die stimmt, da bin ich jetzt sicher. Aber bei meinen Versuchen habe ich noch beobachtet, dass mein "Zwischenraum" noch eine Breite zufügt, die von der Berechnung nicht berücksichtigt wird.

Danke auch für den Hinweis für col-sep als property.

Ich überarbeite das Ganze mit Deinen Anregungen nochmal und poste hier.

Eine große Vereinfachung ist möglich, wenn Du markup-join verwendest. Da dieser Befehl aber nicht public ist (keine Ahnung warum ...) habe ich ihn aus markup.scm rauskopiert und in den code kopiert.

sep-col kann man als property setzen und mit \override #'(sep-col . <was-auch-immer>) verwenden (ein auskommentiertes Beispiel findest Du im code.
[...]

Gruß,
  Harm

P.S.:
Bist Du wirklich noch auf 2.16.2?
Ich finde die aktuelle stable (2.18.2) schon zu outdated ...

Frei nach "never change a running system" bin ich noch nicht umgestiegen. Die Schmerzpunkte, die ich bislang habe sind in 2.18 nicht behoben, also ist mein "Gewinn" nur gering.

Grüße
   Martin

martinmagtenor

  • Member
Re: Befehl columns (aus LSR 464) erweitert
« Antwort #3 am: Dienstag, 14. April 2015, 22:56 »
Hallo Lilypond-Fans,

hier ist die erste Iteration. Die Berechnung der Breite ist überprüft und etwas berechnungsfreundlicher umformuliert. Beim Spielen damit habe ich zu meiner Überraschung festgestellt, dass das pad-to-box einen festen (sehr kleinen) Abstand macht. Ob links, rechts oder auf beiden Seiten (dann halbiert) habe ich noch nicht herausbekommen.

Vorläufig habe ich das durch eine Korrekturgröße berücksichtigt (col-adjust). Den Wert habe ich in meiner Umgebung experimentell ermittelt. Auf jeden Fall erziele ich damit von 2 bis sieben Spalten ausgewogene Ergebnisse, bei denen links und rechts gleich viel Platz zum Blattrand bleibt.

Eine Vereinfachung ist schon drin: anstelle des pad-to-box für den Zwischenraum tut's auch ein hspace.  :-)

Hier die aktualisierte Definition des Befehls:

#(define-markup-command (columns layout props args) (markup-list?)
   (let* ((col-sep (ly:cm 0.1))    ; <== change here
  (col-adjust (ly:mm 0.6)) ; experimental
   (line-width (/ (- (chain-assoc-get 'line-width props
       (ly:output-def-lookup layout 'line-width))
      (* (+ col-sep (if (zero? col-sep) 0 col-adjust)) (- (length args) 1)))
      (length args)))
     )
     (interpret-markup layout props
       (make-line-markup
((lambda (between list)
    (letrec ((p (lambda (b l)
(if (null? (cdr l)) (cons b l)
    (cons b (cons (car l) (p b (cdr l))))))))
     ; check cases
     (cond
      ((and (list? between) (null? between)) list)
      ((or (null? between) (null? list)) list)
      ; list contains only one element
      ((null? (cdr list)) list)
      ; process list and skip first between
      (else (cdr (p between list)))
     ) ; cond
    ) ; letrec
   ) (if (zero? col-sep) '() (markup #:hspace col-sep))
(map (lambda (line)
(markup #:pad-to-box `(0 . ,line-width) '(0 . 0)
#:override `(line-width . ,line-width)
line))
args))))))

Die zugefügte Prüfung auf 0 für den Wert von col-sep sorgt dafür, dass bei Wert 0 wirklich nichts ergänzt wird (auch die Korrektur wird ausgeschaltet) und damit vollständige Abwärtskompatibilität zum ursprünglichen Befehl (aus LSR 464) bewirkt. Ob das sinnvoll ist, sei erst mal dahin gestellt.

Fortsetzung folgt ...

Grüße
  Martin

martinmagtenor

  • Member
Re: Befehl columns (aus LSR 464) erweitert
« Antwort #4 am: Samstag, 18. April 2015, 14:26 »
Hallo Lilypond-Fans,

und hier kommt die zweite Iteration. Ausdrucken und Nachmessen hatte noch zu einer interessanten Erkenntnis geführt. Auch wenn der Spaltenabstand richtig mit ly:cm angegeben worden war, im Ausdruck wurde deutlich mehr Platz dazwischen gestellt. Es erwies sich, dass der Ausdruck um das 1,6-fache breiter war als gewünscht.

Bezüglich der Ursache tappe ich noch etwas im Nebel. Ich habe Indizien gefunden, dass es mit der Standard-Maß-Eigenschaft stretchability von space zusammen hängt. Dieser Spur werde ich separat nachgehen. Diesem Effekt wird vorläufig durch einen Korrekturfaktor Rechnung getragen, s.u.

Harms Anregung Properties für die Breite des Spaltenzwischenraums habe ich aufgegriffen. Nun kann man diesen Wert individuell einstellen, damit lässt sich columns auch wieder getrennt vom eigentlichen Lilypond-Dokument ablegen.

Hier nun die aktualisierte Fassung:

#(define-markup-command (columns layout props args) (markup-list?)
#:properties ((column-separator (ly:mm 5)) ; default value
      (separator-min-width 0.6)) ; minimum value

   (let* ((n (length args)) ; number of arguments is number of columns
  (eff-cs (/ column-separator 1.58)) ; workaround, compensate stretching
  (w (- eff-cs separator-min-width)) ; w idth of additional horizontal space between columns
  (line-width (/ (- (chain-assoc-get 'line-width props
       (ly:output-def-lookup layout 'line-width))
    (* (if (<= w 0.0) separator-min-width eff-cs) (- n 1)))
n))
     )
     (ly:debug (format #f "markup command columns w=~s" w))
     (interpret-markup layout props
       (make-line-markup
((lambda (between list)
    (letrec ((p (lambda (b l)
(if (null? (cdr l)) (cons b l)
    (cons b (cons (car l) (p b (cdr l))))))))
     ; check cases
     (cond
      ((and (list? between) (null? between)) list)
      ((or (null? between) (null? list)) list)
      ; list contains only one element
      ((null? (cdr list)) list)
      ; process list and skip first between
      (else (cdr (p between list)))
     ) ; cond
    ) ; letrec
   ) (if (<= w 0.0) '() (markup #:hspace w))
(map (lambda (line)
(markup #:pad-to-box `(0 . ,line-width) '(0 . 0)
#:override `(line-width . ,line-width)
line))
args))))))

Und hier ein kompilierbares Beispiel, zwei Seiten, einmal mit sieben Spalten, einmal mit zwei. Die erste Seite mit Standardzwischenraum 0,5cm und im zweiten abweichend 1cm.

\version "2.16.2"

#(set-global-staff-size 18)
#(set-default-paper-size "a4" 'landscape)
#(ly:set-option 'point-and-click #f)

% Inspiriert von http://lsr.di.unimi.it/LSR/Snippet?id=464
% Mit eigenen Ergänzungen: Abstand zwischen den Spalten, um zu verhindern, dass
% der Text "aneinander klebt".

#(define-markup-command (columns layout props args) (markup-list?)
#:properties ((column-separator (ly:mm 5)) ; default value
      (separator-min-width 0.6)) ; minimum value

   (let* ((n (length args)) ; number of arguments is number of columns
  (eff-cs (/ column-separator 1.58)) ; workaround, compensate stretching
  (w (- eff-cs separator-min-width)) ; w idth of additional horizontal space between columns
  (line-width (/ (- (chain-assoc-get 'line-width props
       (ly:output-def-lookup layout 'line-width))
    (* (if (<= w 0.0) separator-min-width eff-cs) (- n 1)))
n))
     )
     (ly:debug (format #f "markup command columns w=~s" w))
     (interpret-markup layout props
       (make-line-markup
((lambda (between list)
    (letrec ((p (lambda (b l)
(if (null? (cdr l)) (cons b l)
    (cons b (cons (car l) (p b (cdr l))))))))
     ; check cases
     (cond
      ((and (list? between) (null? between)) list)
      ((or (null? between) (null? list)) list)
      ; list contains only one element
      ((null? (cdr list)) list)
      ; process list and skip first between
      (else (cdr (p between list)))
     ) ; cond
    ) ; letrec
   ) (if (<= w 0.0) '() (markup #:hspace w))
(map (lambda (line)
(markup #:pad-to-box `(0 . ,line-width) '(0 . 0)
#:override `(line-width . ,line-width)
line))
args))))))

TXTK = \markup { \justify { Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
  sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
  erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
  rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum
  dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
  diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
  sed diam voluptua. }
}

TXTL = \markup { \justify { Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
  sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
  erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
  rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum
  dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
  diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
  sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
  Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
  amet.
  }
        }

\book {
  \bookpart {
    \TXTK
    \markup {
      \columns {
\column {
  \fill-line \huge { \null Lorem \null }
  \vspace #1
  \TXTL
}
      \column {
  \fill-line \huge { \null ipsum \null }
  \vspace #1
  \TXTL
}
      \column {
  \fill-line \huge { \null dolor \null }
  \vspace #1
  \TXTL
}
      \column {
  \fill-line \huge { \null sit \null }
  \vspace #1
  \TXTL
}
      \column {
  \fill-line \huge { \null amet \null }
  \vspace #1
  \TXTL
}
      \column {
  \fill-line \huge { \null consetutur \null }
  \vspace #1
  \TXTL
}
      \column {
  \fill-line \huge { \null sadipscing \null }
  \vspace #1
  \TXTL
}
      }
    }
    \pageBreak
    \TXTK
    \markup {
      \override #(cons 'column-separator (ly:cm 1))
       \columns {
         \column {
           \fill-line \huge { \null ipsum \null }
           \vspace #1
           \TXTL
         }
         \column {
           \fill-line \huge { \null dolor \null }
           \vspace #1
           \TXTL
         }
       }
     }
 
     \paper {
       annotate-spacing = ##f

       oddHeaderMarkup = \markup { \null }
       evenHeaderMarkup = \markup { \null }
       oddFooterMarkup = \markup { \null }
       evenFooterMarkup = \markup { \null }
    }
  }
}


Und hier noch das Ergebnis als PDF.

Grüße
  Martin