B.5.3 Definición de una instrucción de marcado nueva
Esta sección trata sobre la definición de nuevas instrucciones de marcado.
Sintaxis de la definición de instrucciones de marcado | ||
Acerca de las propiedades | ||
Un ejemplo completo | ||
Adaptación de instrucciones incorporadas |
Sintaxis de la definición de instrucciones de marcado
Se pueden definir instrucciones de marcado nuevas usando el macro de
Scheme define-markup-command
, en el nivel sintáctico superior.
(define-markup-command (nombre-de-la-instruccion layout props arg1 arg2 …) (tipo-de-arg1? tipo-de-arg2? …) [ #:properties ((propiedad1 valor-predeterminado1) …) ] …command body…) |
Los argumentos son
-
nombre-de-la-instruccion
nombre de la instrucción de marcado
-
layout
la definición de ‘layout’ (disposición).
-
props
una lista de listas asociativas, que contienen todas las propiedades activas.
-
argi
argumento i-ésimo de la instrucción
-
tipo-de-argi?
predicado de tipo para el argumento i-ésimo
Si la instrucción utiliza propiedades de los argumentos props
,
se puede usar la palabra clave #:properties
para especificar
qué propiedades se usan, así como sus valores predeterminados.
Los argumentos se distinguen según su tipo:
- un marcado, que corresponde al predicado de tipo
markup?
; - una lista de marcados, que corresponde al predicado de tipo
markup-list?
; - cualquier otro objeto de Scheme, que corresponde a predicados de tipo como
list?
,number?
,boolean?
, etc.
No existe ninguna limitación en el orden de los argumentos (después de
los argumentos estándar layout
y props
). Sin embargo, las
funciones de marcado que toman un elemento de marcado como su último
argumento son un poco especiales porque podemos aplicarlas a una lista
de marcados y el resultado es una lista de marcados donde la función
de marcado (con los argumentos antecedentes especificados) se ha
aplicado a todos los elementos de la lista de marcados original.
Dado que la replicación de los argumentos precedentes para aplicar una función de marcado a una lista de marcados es poco costosa principalmente por los argumentos de Scheme, se evitan las caídas de rendimiento simplemente mediante la utilización de argumentos de Scheme para los argumentos antecedentes de las funciones de marcado que toman un marcado como su último argumento.
Las instrucciones de marcado tienen un ciclo de vida más bien
complejo. El cuerpo de la definición de una instrucción de marcado es
responsable de la conversión de los argumentos de la instrucción de
marcado en una expresión de sello que se devuelve. Muy a menudo esto
se lleva a cabo llamando a la función interpret-markup
sobre
una expresión de marcado, pasándole los argumentos layout y
props. Por lo general, estos argumentos se conocen solamente en
una fase muy tardía de la composición tipográfica. Las expresiones de
marcado ya tienen sus componentes ensamblados dentro de expresiones de
marcado cuando se expanden las instrucciones \markup
(dentro de
una expresión de LilyPond) o la macro markup
(dentro de
Scheme). La evaluación y la comprobación de tipos de los argumentos
de la instrucción de marcado tiene lugar en el momento en que se
interpretan \markup
o markup
.
Pero la conversión real de expresiones de marcado en expresiones de
sello mediante la ejecución de los cuerpos de función de marcado solo
tienen lugar cuando se llama a interpret-markup
sobre una
expresión de marcado.
Acerca de las propiedades
Los argumentos layout
y props
de las instrucciones de
marcado traen a escena un contexto para la interpretación del marcado:
tamaño de la tipografía, grueso de línea, etc.
El argumento layout
permite el acceso a las propiedades
definidas en los bloques paper
, usando la función
ly:output-def-lookup
. Por ejemplo, el grueso de línea (el
mismo que el que se usa en las partituras) se lee usando:
(ly:output-def-lookup layout 'line-width)
El argumento props
hace accesibles algunas propiedades a las
instrucciones de marcado. Por ejemplo, cuando se interpreta el
marcado del título de un libro, todas las variables definidas dentro
del bloque \header
se añaden automáticamente a props
, de
manera que el marcado del título del libro puede acceder al título del
libro, el autor, etc. También es una forma de configurar el
comportamiento de una instrucción de marcado: por ejemplo, cuando una
instrucción utiliza tamaños de tipografía durante el procesado, el
tamaño se lee de props
en vez de tener un argumento
font-size
. El que llama a una instrucción de marcado puede
cambiar el valor de la propiedad del tamaño de la tipografía con el
objeto de modificar el comportamiento. Utilice la palabra clave
#:properties
de define-markup-command
para especificar
qué propiedades se deben leer a partir de los argumentos de
props
.
El ejemplo de la sección siguiente ilustra cómo acceder y sobreescribir las propiedades de una instrucción de marcado.
Un ejemplo completo
El ejemplo siguiente define una instrucción de marcado para trazar un rectángulo doble alrededor de un fragmento de texto.
En primer lugar, necesitamos construir un resultado aproximado
utilizando marcados. Una consulta a
de texto
Instrucciones de marcado de texto nos muestra que es útil la instrucción \box
:
\markup \box \box HELLO
Ahora, consideramos que es preferible tener más separación entre el
texto y los rectángulos. Según la documentación de \box
, esta
instrucción usa una propiedad box-padding
, cuyo valor
predeterminado es 0.2. La documentación también menciona cómo
sobreescribir este valor:
\markup \box \override #'(box-padding . 0.6) \box A
Después, el relleno o separación entre los dos rectángulos nos parece muy pequeño, así que lo vamos a sobreescribir también:
\markup \override #'(box-padding . 0.4) \box \override #'(box-padding . 0.6) \box A
Repetir esta extensa instrucción de marcado una y otra vez sería un
quebradero de cabeza. Aquí es donde se necesita una instrucción de
marcado. Así pues, escribimos una instrucción de marcado
double-box
, que toma un argumento (el texto). Dibuja los dos
rectángulos y añade una separación.
#(define-markup-command (double-box layout props text) (markup?) "Trazar un rectángulo doble rodeando el texto." (interpret-markup layout props #{\markup \override #'(box-padding . 0.4) \box \override #'(box-padding . 0.6) \box { #text }#})) |
o, de forma equivalente,
#(define-markup-command (double-box layout props text) (markup?) "Trazar un rectángulo doble rodeando el texto." (interpret-markup layout props (markup #:override '(box-padding . 0.4) #:box #:override '(box-padding . 0.6) #:box text))) |
text
es el nombre del argumento de la instrucción, y
markup?
es el tipo: lo identifica como un elemento de marcado.
La función interpret-markup
se usa en casi todas las
instrucciones de marcado: construye un sello, usando layout
,
props
, y un elemento de marcado. En el segundo caso, la marca
se construye usando el macro de Scheme markup
, véase
Construcción de elementos de marcado en Scheme. La
transformación de una expresión \markup
en una expresión de
marcado de Scheme es directa.
La instrucción nueva se puede usar como sigue:
\markup \double-box A
Sería buen hacer que la instrucción double-box
fuera
personalizable: aquí, los valores de relleno box-padding
son
fijos, y no se pueden cambiar por parte del usuario. Además, sería
mejor distinguir la separación entre los dos rectángulos, del relleno
entre el rectángulo interno y el texto. Así pues, introducimos una
nueva propiedad, inter-box-padding
, para el relleno entre los
rectángulos. El box-padding
se usará para el relleno interno.
Ahora el código nuevo es como se ve a continuación:
#(define-markup-command (double-box layout props text) (markup?) #:properties ((inter-box-padding 0.4) (box-padding 0.6)) "Trazar un rectángulo doble rodeando el texto." (interpret-markup layout props #{\markup \override #`(box-padding . ,inter-box-padding) \box \override #`(box-padding . ,box-padding) \box { #text } #})) |
De nuevo, la versión equivalente que utiliza la macro de marcado sería:
#(define-markup-command (double-box layout props text) (markup?) #:properties ((inter-box-padding 0.4) (box-padding 0.6)) "Trazar un rectángulo doble rodeando el texto." (interpret-markup layout props (markup #:override `(box-padding . ,inter-box-padding) #:box #:override `(box-padding . ,box-padding) #:box text))) |
Aquí, la palabra clave #:properties
se usa de manera que las
propiedades inter-box-padding
y box-padding
se leen a
partir del argumento props
, y se les proporcionan unos valores
predeterminados si las propiedades no están definidas.
Después estos valores se usan para sobreescribir las propiedades
box-padding
usadas por las dos instrucciones \box
.
Observe el apóstrofo invertido y la coma en el argumento de
\override
: nos permiten introducir un valor de variable dentro
de una expresión literal.
Ahora, la instrucción se puede usar dentro de un elemento de marcado, y el relleno de los rectángulos se puede personalizar:
#(define-markup-command (double-box layout props text) (markup?) #:properties ((inter-box-padding 0.4) (box-padding 0.6)) "Draw a double box around text." (interpret-markup layout props #{\markup \override #`(box-padding . ,inter-box-padding) \box \override #`(box-padding . ,box-padding) \box { #text } #})) \markup \double-box A \markup \override #'(inter-box-padding . 0.8) \double-box A \markup \override #'(box-padding . 1.0) \double-box A
Adaptación de instrucciones incorporadas
Una buena manera de comenzar a escribir una instrucción de marcado nueva, es seguir el ejemplo de otra instrucción ya incorporada. Casi todas las instrucciones de marcado que están incorporadas en LilyPond se pueden encontrar en el archivo ‘scm/define-markup-commands.scm’.
Por ejemplo, querríamos adaptar la instrucción \draw-line
, para
que trace una línea doble. La instrucción \draw-line
está
definida como sigue (se han suprimido los comentarios de
documentación):
(define-markup-command (draw-line layout props dest) (number-pair?) #:category graphic #:properties ((thickness 1)) "…documentación…" (let ((th (* (ly:output-def-lookup layout 'line-thickness) thickness)) (x (car dest)) (y (cdr dest))) (make-line-stencil th 0 0 x y))) |
Para definir una instrucción nueva basada en otra existente, copie la
definición y cámbiele el nombre. La palabra clave #:category
se puede eliminar sin miedo, pues sólo se utiliza para generar
documentación de LilyPond, y no tiene ninguna utilidad para las
instrucciones de marcado definidas por el usuario.
(define-markup-command (draw-double-line layout props dest) (number-pair?) #:properties ((thickness 1)) "…documentación…" (let ((th (* (ly:output-def-lookup layout 'line-thickness) thickness)) (x (car dest)) (y (cdr dest))) (make-line-stencil th 0 0 x y))) |
A continuación se añade una propiedad para establecer la separación
entre las dos líneas, llamada line-gap
, con un valor
predeterminado de p.ej. 0.6:
(define-markup-command (draw-double-line layout props dest) (number-pair?) #:properties ((thickness 1) (line-gap 0.6)) "…documentación…" … |
Finalmente, se añade el código para trazar las dos líneas. Se usan
dos llamadas a make-line-stencil
para trazar las líneas, y los
sellos resultantes se combinan usando ly:stencil-add
:
#(define-markup-command (my-draw-line layout props dest) (number-pair?) #:properties ((thickness 1) (line-gap 0.6)) "..documentation.." (let* ((th (* (ly:output-def-lookup layout 'line-thickness) thickness)) (dx (car dest)) (dy (cdr dest)) (w (/ line-gap 2.0)) (x (cond ((= dx 0) w) ((= dy 0) 0) (else (/ w (sqrt (+ 1 (* (/ dx dy) (/ dx dy)))))))) (y (* (if (< (* dx dy) 0) 1 -1) (cond ((= dy 0) w) ((= dx 0) 0) (else (/ w (sqrt (+ 1 (* (/ dy dx) (/ dy dx)))))))))) (ly:stencil-add (make-line-stencil th x y (+ dx x) (+ dy y)) (make-line-stencil th (- x) (- y) (- dx x) (- dy y))))) \markup \my-draw-line #'(4 . 3) \markup \override #'(line-gap . 1.2) \my-draw-line #'(4 . 3)
Otros idiomas: English, deutsch, français.
Acerca de la selección automática del idioma.