Cuaderno de GNUtas

Subfiguras en Org Mode con salida a LaTeX (y a HTML)

En mi trabajo de composición tipográfica, que realizo en TEX pero que de un tiempo a esta parte he integrado con el polifacético Org Mode de Emacs como punto de partida de ese flujo, me he visto en la necesidad hace poco de montar una serie de figuras complejas, o sea, figuras que incluyen sub-figuras con su propios pies y numeración. Aparte de la salida a LATEX, la prioritaria, también me interesaba contar con una salida a HTML secundaria, así que estuve probando diversas opciones, trasteos de los cuales vamos a dar cuenta en esta GNUta.

Org Mode cuenta ya de fábrica con un muy completo soporte para bregar con figuras, su numeración y sus referencias cruzadas. Con el tema de las subfiguras la cosa, me temo, está más virgen, entre otras cosas porque es un tipo de construcción que requiere tratamientos diversos dependiendo del contexto (y del número, forma y tamaño de las subfiguras, claro), lo que se traduce en que en más de una vez tendremos que introducir formato directo. Aun así, si podemos lograr algo más liviano y manejable que la verbosidad habitual de LATEX, bienvenido sea (sin obviar que LATEX, por supuesto, es el que trabaja tras bambalinas.

En cuanto al lado de LATEX, precisamente, la construcción y gestión de figuras complejas tiene variadas y muy buenas soluciones a lo largo de su ecosistema macropaquetil. La que yo uso es la que proporciona el paquete subfigure, que incluye muchas opciones, si bien su comando principal es

\subfigure[pie de la subfigura]{archivo de la subfigura}

Las subfiguras podemos incluirlas todas cuantas queramos dentro de un entorno figure, y el paquete ya se encarga más o menos de ir ajustándolas y disponiéndolas, a veces con algo de ayuda de nuestra parte. La cuestión de partida es ver cómo formatear las imágenes solas de un documento de Org Mode para que en la salida a LATEX queden envueltas en ese comando. Y, de paso, cómo poder añadir parámetros. Bien, antes de seguir, un par de consideraciones: los bloques especiales de Org (es decir, aquellos que nombramos a nuestro albedrío, no los que ya vienen de serie en el modo) se convierten en la exportación a LATEX en entornos con el mismo nombre, lo cual es muy útil (y en la exportación a HTML en DIVs, como veremos más adelante). Así pues, si creamos un bloque especial llamado «figure», éste pasará a LATEX como un entorno figure. La segunda cosa provechosa a considerar es que esos bloques también admiten encabezados (CAPTION) y etiquetas según la sintaxis de Org.

Macros de sustitución (primera aproximación)

Para tratar un juego de imágenes como subfiguras, lo primero que se me vino a la cabeza fue incluirlas en un bloque especial «figure» y usar macros de sustitución, que sirven para un roto como para un descosido. Recuérdese que estas macros introducen el contenido textual en bruto y se expanden en un momento tempranero de la exportación, lo que las hace tremendamente útiles. Podemos, asimismo, evaluar Lisp en su contenido, es decir, podemos añadir condicionales (por ejemplo, si es salida a LATEX, haz esto; si es a HTML, haz lo otro, etc.), pero aquí nos basta con una simple sexp. Definimos estas dos macros con sus correspondientes argumentos1:

#+MACRO: startsubfig (eval (concat "@@latex:\\subfigure[@@" "\u200B" $1 "\u200B" "@@latex:]{%@@" "\n#+ATTR_LaTeX: :width\s" $2 "\s:center nil" "\s:options\s" $3))
#+MACRO: stopsubfig @@latex:}$1@@

En la primera macro, startsubfig, tenemos tres argumentos: el pie de la subfigura, el ancho y un tercero destinado a opciones varias que queramos pasarle al comando de LATEX \includegraphics. La segunda macro cierra la construcción. Ni que decir tiene que entre medias, a modo de bocadillo, debe ir el enlace de una típica imagen de Org. De tal forma que si tenemos esto en nuestro documento de Org:

#+CAPTION: Pie de imagen general
#+NAME: fig1
#+begin_figure
@@latex:[h!]@@
@@latex:\centering@@
{{{startsubfig(Hedi Lamarr,0.3\textwidth)}}}
[[file:Hedi.jpg]]
{{{stopsubfig}}}
{{{startsubfig(Sophia Schliemann,0.3\textwidth)}}}
[[file:sophia_schliemann.jpg]]
{{{stopsubfig}}}
{{{startsubfig(Penelope Unraveling Her Work at Night,0.62\textwidth)}}}
[[file:Penelope_Unraveling_Her_Work_at_Night1.jpg]]
{{{stopsubfig}}}
#+end_figure

obtendremos lo siguiente en la salida a LATEX:

\begin{figure}
[h!]
\centering
\subfigure[​Hedi Lamarr​]{%
\includegraphics[width=0.3\textwidth]{Hedi.jpg}
}
\subfigure[​Sophia Schliemann​]{%
\includegraphics[width=0.3\textwidth]{sophia_schliemann.jpg}
}
\subfigure[​Penelope Unraveling Her Work at Night​]{%
\includegraphics[width=0.62\textwidth]{Penelope_Unraveling_Her_Work_at_Night1.jpg}
}
\caption{\label{org916df47}Pie de imagen general}
\end{figure}

Como se ve, el procedimiento funciona razonablemente bien, pero uno es inquieto y curioso y tenía ganas de ir ensayando más posibilidades, toda vez que Elisp y Org nos proporcionan no pocos recursos para jugar a varios niveles.

Código Elisp puro (segunda aproximación)

Esta opción me resultó muy golosa, dado mi humilde amor por Lisp. La cosa consistiría en escribir una función que evaluásemos en nuestro documento durante la exportación, dentro de un típico bloque de código, con la particularidad de que su salida (esto es, el resultado de evaluar ese código) fuera contenido textual en bruto.

Comencé escribiendo esta primera función, que admite tres argumentos obligatorios: las subfiguras (que ha de ser una lista de listas, cuyos elementos tienen siempre dos componentes: el archivo de la subfigura y el pie de la misma), el pie de la figura principal que las contiene y la etiqueta. El argumento opcional son el conjunto de dimensiones y demás opciones que queremos que admitan todas las subfiguras de manera global, introducidas como código LATEX puro:

(defun subfiguras (figuras caption label &optional dim)
  (interactive)
  (when (org-export-derived-backend-p org-export-current-backend 'latex)
    (concat "\\begin{figure}\\centering"
            (mapconcat (lambda (cons)
                         (concat
                          "\n\\subfigure"
                          "["
                          (format "%s" (cdr cons))
                          "]"
                          "{"
                          (if dim
                              (format "\\includegraphics[%s]{" dim)
                            "\\includegraphics{")
                          (format "%s" (car cons))
                          "}}"))
                       figuras
                       "")
            (format "\n\\caption{\\label{%s}%s}" label caption)
            "\n\\end{figure}")))

Si procesamos el material de nuestro ejemplo anterior con esta función dentro de un bloque de código cuya salida sea contenido textual en bruto, conseguiremos un resultado análogo al que obtuvimos mediante las macros de sustitución:

#+begin_src emacs-lisp :exports results :results raw
  (subfiguras '(("sophia_schliemann.jpg" . "Sofía Schliemann")
                ("Hedi.jpg" . "Hedi Lamarr")
                ("Penelope_Unraveling_Her_Work_at_Night1.jpg" . "Penelope Unraveling Her Work at Night"))
              "Pie de imagen principal"
              "fig1"
              "width=.4\\textwidth")
#+end_src

Una segunda versión de la función me proporcionaba más control sobre las dimensiones y opciones de las subfiguras, parámetros esos que pasan a ser el tercer componente de cada elemento de la lista de listas. El resto sigue igual:

(defun subfiguras-bis (figuras caption label)
      (interactive)
      (when (org-export-derived-backend-p org-export-current-backend 'latex)
        (concat "\\begin{figure}\\centering"
                (mapconcat (lambda (lista)
                             (concat
                              "\n\\subfigure"
                              "["
                              (format "%s" (nth 1 lista))
                              "]"
                              "{"
                              (format "\\includegraphics[%s]{" (nth 2 lista))
                              (format "%s" (nth 0 lista))
                              "}}"))
                           figuras
                           "")
                (format "\n\\caption{\\label{%s}%s}" label caption)
                "\n\\end{figure}")))

Con la nueva función nuestro bloque de código tendría esta pinta:

#+begin_src emacs-lisp :exports results :results raw
  (subfiguras-bis '(("sophia_schliemann.jpg"  "Sofía Schliemann" "width=.3\\textwidth")
                    ("Hedi.jpg"  "Hedi Lamarr" "width=.3\\textwidth")
                    ("Penelope_Unraveling_Her_Work_at_Night1.jpg"  "Penelope Unraveling Her Work at Night" "width=0.6\\textwidth"))
                  "Pie de imagen principal"
                  "fig1")
#+end_src

Mediante nuevos enlaces de Org (tercera aproximación)

En esta tercera (y, por el momento, tentativa final) probé también a incluir las opciones para conseguir una salida en HTML lo más parecida posible a la lograda con LATEX, al menos en la disposición de los elementos, ya que HTML es lo que es y no hay que pedirle peras al olmo. El caso es: ¿por qué no echar mano de la utilísima función que incluye org para definir nuevos tipos de enlace, org-link-set-parameters, de la que ya hablamos aquí y aquí?

Ésta fue la función resultante que incluye dos condicionales: uno para la salida a LATEX y otro para HTML. Los pies de cada subfigura son las descripciones de cada enlace (lo cual me resulta tremendamente económico). Y los parámetros para pasar a LATEX los podemos escribir justo después de lo que hayamos escrito para el pie, tras un espacio. Para LATEX van entre «>(…)» y para HTML entre «>{…}». En el primer caso podemos incluir las opciones que queramos y pueda aceptar el comando \includegraphics, es decir, mediante código LATEX puro. En el caso de HTML sólo (de momento) está admitida la anchura de cada subfigura de manera literal: «300px», «30%», etc.:

(org-link-set-parameters
 "subfig"
 :follow (lambda (ruta) (find-file ruta))
 :face '(:foreground "green4" :underline t)
 :display 'full
 :export (lambda (ruta desc salida)
           (cond
            ((eq salida 'latex)
             (if (string-match ">(\\(.+\\))" desc)
                 (concat "\\subfigure[" (replace-regexp-in-string "\s*>.+" "" desc) "]" "{\\includegraphics" "[" (match-string 1 desc) "]" "{"  ruta "}}")
               (format "\\subfigure[%s]{\\includegraphics{%s}}" (replace-regexp-in-string "\s*>.+" "" desc) ruta)))
            ((eq salida 'html)
             (let
                 ((num-fig "xxx"))
               (if (string-match ">{\\(.+\\)}" desc)
                   (concat "<td><img src=\"" ruta "\" alt=\"" ruta "\"" " style=\"width:"
                           (match-string 1 desc)
                           "\""
                           "/><br>"
                           num-fig
                           ". " (replace-regexp-in-string "\s*&gt;.+" "" desc)
                           "</td>")
                 (format "<td><img src=\"%s\" alt=\"%s\"/><br>%s. %s</td>"
                         ruta ruta num-fig
                         (replace-regexp-in-string "\s*&gt;.+" "" desc))))))))

Para la numeración interna (alfabética) de cada grupo de subfiguras, escribí esta otra función que debemos evaluar durante la exportación como filtro para los bloques especiales:

#+BIND: org-export-filter-special-block-functions (filtro-subfig-html)
#+begin_src emacs-lisp :exports results :results none
  (defun filtro-subfig-html (texto backend info)
    (when (org-export-derived-backend-p backend 'html)
      (with-temp-buffer
        (insert texto)
        (let ((fig 0))
          (goto-char (point-min))
          (while (re-search-forward "\\(xxx\\)" nil t)
            (replace-match (make-string 1 (+ ?a fig)) t nil)
            (setf fig (+ 1 fig))))
        (setq texto (buffer-string)))))
#+end_src

En cuanto a la salida a HTML, nos toparemos con un pequeño gran inconveniente, y es que los bloques especiales de Org no admiten CAPTION cuando exportamos a ese formato, a diferencia de lo que sucede con la exportación a LaTeX. Lo que significa que tendremos que apañárnoslas con algunas triquiñuelas y que, a menos que nos metamos en serio a tocar el código de Org y a ensayar un parche (lo que excedería de largo el presupuesto y alcance de estas líneas), tendremos que resignarnos a que nuestras figuras que incluyan subfiguras irán sin numerar. Aun así las ventajas son evidentes, y es que contamos con una única fuente que produce dos resultados aceptablemente confrontables sin tener que cambiar nada en uno u otro caso. Eso sí, no nos libramos de meter algo de formato directo, como se ve en nuestro ejemplo (aquí se puede ver cómo quedan las subfiguras en la salida a LATEX; y aquí, las tres subfiguras generadas en esta página directamente con nuestro código).

#+ATTR_LaTeX: :caption \caption{\label{subfig1}Pie de imagen general}
#+begin_figure
@@latex:[!h]\centering@@
@@html:<div class="org-center"><table style="margin-left:auto;margin-right:auto;"><tr>@@
[[subfig:Hedi.jpg][Hedi Lamarr >(width=.3\textwidth) >{300px}]]
[[subfig:sophia_schliemann.jpg][Sophia Schliemann >(width=.3\textwidth) >{300px}]]
@@html:</tr></table><p> </p><table style="margin-left:auto;margin-right:auto;"><tr>@@
[[subfig:Penelope_Unraveling_Her_Work_at_Night1.jpg][Penelope Unraveling Her Work at Night
>(width=.6\textwidth) >{600px}]]
@@html:</tr></table><br>Pie de imagen general</div>@@
#+end_figure

subfiguras1.png

Figura 1: Nuestras tres subfiguras en LATEX con el paquete subfigure

./images/Hedi.jpg
a. Hedi Lamarr
./images/sophia_schliemann.jpg
b. Sophia Schliemann

./images/Penelope_Unraveling_Her_Work_at_Night1.jpg
c. Penelope Unraveling Her Work at Night

Nuestras tres subfiguras en esta misma página

Publicado: 02/11/20

Última actualización: 21/01/22


Índice general

Acerca de...

Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.

Notas al pie de página:

1

Antes y después de los corchetes de cierre incluyo el carácter Unicode de «espacio cero» para evitar que se malformen las marcas de cursiva de Org en el paso a LATEX.

© Juan Manuel Macías
Creado con esmero en
GNU Emacs