1

I want to define command or environment using the keycommand package and pass a list to one key. In my case this would be a list of images.

To be more explicit: How can I make the following pseudocode work:

\documentclass{article}

\usepackage{keycommand}
\newkeyenvironment{images}[im]{
\begin{center}
\for \image in im
\includegraphics[width=2cm]{im}
}
{
\end{center}
}

\begin{document}
\begin{images}[
im={
image1,
image2,
image3
}
\end{images}
\end{document}
3
  • 1
    Why is it an environment? In your example, you don't supply any content for the environment but only a value for the required argument. That is, it looks more as if this should be a command than an environment.
    – cfr
    May 29, 2016 at 18:21
  • This is only a minimal example, that's why I don't add any content. The pseude-test-content is center...
    – student
    May 29, 2016 at 19:23
  • That's not the content of the environment. That's part of the definition of the environment. You don't have any content in the environment in the document. Moreover, it isn't clear what kind of content you might want there or how that might affect the way the environment should be defined. Centring can be equally applicable to the macro as the environment. (See my answer below, for example, which centres each image within the grid. The grid itself could also, of course, be centred if required.)
    – cfr
    May 29, 2016 at 23:43

2 Answers 2

3

Here's expl3 using a \seq variable and splitting the content of the \commandkey{im} macro into sequence items, then looping over those items using a map function, say \loopfunction

Basically any loop macro that can split the comma separated value list would do of course.

From the documentation of keycommand it's apparent, that this packages uses etoolbox, so \forcsvlist macros can be used as well.

\documentclass{article}

\usepackage{graphicx}
\usepackage{xparse}
\ExplSyntaxOn

\cs_generate_variant:Nn \seq_set_from_clist:Nn {Nx,NV}


\newcommand{\loopovercommandkey}[2][]{%
  \seq_set_from_clist:Nx \l_tmpa_seq {#2}
  \seq_map_inline:Nn \l_tmpa_seq {
    \loopfunction[#1]{##1}% 
  }
}
\ExplSyntaxOff



\newcommand{\loopfunction}[2][]{%
  \includegraphics[#1]{#2}
}

\usepackage{keycommand}
\newkeyenvironment{images}[im]{%
  \begin{center}
    \loopovercommandkey[width=2cm]{\commandkey{im}}
  }
  {%
  \end{center}
}

\begin{document}
\begin{images}[%
im={
ente1,
ente2,
ente3
}]
\end{images}
\end{document}

enter image description here

8
  • I've never used keycommand before and the three duck images are actually the same.
    – user31729
    May 29, 2016 at 17:13
  • Thanks, that's great. Bonus question: Do you have an idea of how to arrange the images in nxm grid automatically?
    – student
    May 29, 2016 at 17:27
  • @student: I don't know the term 'bonus question'
    – user31729
    May 30, 2016 at 19:02
  • This was just a sloppy way to say that I have an additional question about this topic. After trying your solution I noticed that the images should be arranged in a grid and that this should be done automatically since they are read in by a loop. If you have a more simple idea than cfr, please let me know. But after reading cfr's (well written) answer I thought I should learn latex3 anyway :-).
    – student
    May 30, 2016 at 19:06
  • 1
    Well, 'additional questions' are for new questions, not changing the previous one. expl3 is nice for many things, actually
    – user31729
    May 30, 2016 at 19:10
2

The following code defines and illustrates a macro \images{}. \images takes one mandatory argument which should be a key-value list of options. The minimum sensible list sets the key images to a list of one or more image files.

I've defined this as a macro but it could be easily defined using \NewDocumentEnvironment{images}{m} ... instead, or used within the definition of an environment.

Key-value syntax for \images{<key-value options>}:

  • images={<comma separated list of image files>}
  • grid=<columns>x<rows> [default is a 2x2 grid)
  • total width=<dimension>[default is \linewidth]
  • total height=<dimension> [default is \textheight]

The code divides the total width and height by the requested number of rows and columns. It then sets each image provided in the centre of one rectangular cell within the grid, with the first image listed in the top-left and the last in the bottom-right. The top row is filled, then the second, then the third and so on.

The number of images need not match the grid size. TeX will just ignore cells in the grid for which no image is specified. However, you'll get an error if there isn't at least one image in the list and you'll get stuff spilling out of the specified area (and bad box warnings, no doubt) if the number of images exceeds the available cells.

The images are scaled if required to fit within the cells of the grid, keeping the aspect ratio of the originals. However, images are never enlarged - only scaled down to fit if necessary.

For example,

\images{%
  images={cath-gadael-chartref,cathod,cath,coeden-nadolig-2014},
}

\images{%
  images={tspcd,cauldron,duck,tiger,example-image-c},
  grid=3x2,
}

will produce 2 full-page grids. The first is 2x2 by default and each cell gets an image. The second has 6 cells, the sixth being empty.

showframe is used here to show the way the width and height of the page are filled by the grid cells.

image grids

Note that the vertical and horizontal spacing are due to the images not being very well-chosen given the proportions of the grid cells available. In order to make them fit, they get scaled but, to avoid distorting them, their aspect ratios are maintained, creating vertical or horizontal padding within the affected cells.

Complete code:

\documentclass{article}
\usepackage{xparse,graphicx}
\usepackage[showframe]{geometry}
\ExplSyntaxOn
\seq_new:N \l_student_grid_seq
\int_new:N \l_student_cols_int
\int_new:N \l_student_rows_int
\int_new:N \g_student_count_int
\coffin_new:N \l_student_image_coffin
\coffin_new:N \l_student_images_coffin
\tl_new:N \l_student_graphics_tl
\cs_new_protected_nopar:Npn \student_gridset:n #1
{
  \seq_set_split:Nnn \l_student_grid_seq { x } { #1 }
  \seq_get_left:NN \l_student_grid_seq \l_tmpa_tl
  \int_set:Nn \l_student_cols_int { \l_tmpa_tl }
  \seq_get_right:NN \l_student_grid_seq \l_tmpa_tl
  \int_set:Nn \l_student_rows_int { \l_tmpa_tl }
}
\cs_new_protected_nopar:Npn \student_graphics:nn #1 #2
{
  \includegraphics [ #1 ] { #2 }
}
\cs_generate_variant:Nn \student_graphics:nn { Vn }
\cs_new_protected_nopar:Npn \student_boximage:nnn #1 #2 #3
{
  \hbox_set:Nn \l_tmpa_box { \includegraphics { #3 } }
  \dim_compare:nT { \box_ht:N \l_tmpa_box + \box_dp:N \l_tmpa_box > #2 }
  {
    \tl_put_right:Nn \l_student_graphics_tl { height = #2 , }
  }
  \dim_compare:nT { \box_wd:N \l_tmpa_box > #1 }
  {
    \tl_put_right:Nn \l_student_graphics_tl { width = #1 , }
  }
  \tl_put_right:Nn \l_student_graphics_tl { keepaspectratio = true }
  \vcoffin_set:Nnn \l_student_image_coffin { #1 }
  {
    \vbox_to_ht:nn { #2 }
    {
      \skip_vertical:N \fill
      \student_graphics:Vn \l_student_graphics_tl { #3 }
      \skip_vertical:N \fill
    }
  }
}
\cs_generate_variant:Nn \student_boximage:nnn { VVn }
\cs_new_protected_nopar:Npn \student_processimages:nnnnn #1 #2 #3 #4 #5
{
  \int_gzero:N \g_student_count_int
  \dim_set:Nn \l_tmpa_dim { #1 / #3 }
  \dim_set:Nn \l_tmpb_dim { #2 / #4 }
  \clist_map_inline:nn { #5 }
  {
    \student_boximage:VVn \l_tmpa_dim \l_tmpb_dim { ##1 }
    \int_compare:nTF { \g_student_count_int = 0 }
    {
      \coffin_join:NnnNnnnn \l_student_images_coffin { l } { b } \l_student_image_coffin { l } { t } { 0pt } { 0pt }
    }
    {
      \coffin_join:NnnNnnnn \l_student_images_coffin { \l_student_image_coffin-r } { \l_student_image_coffin-t } \l_student_image_coffin { l } { t } { 0pt } { 0pt }
    }
    \int_gincr:N \g_student_count_int
    \int_compare:nT { \g_student_count_int = \l_student_cols_int }
    { \int_gzero:N \g_student_count_int  }
  }
  \coffin_typeset:Nnnnn \l_student_images_coffin { B } { l } { 0pt } { 0pt }
}
\cs_generate_variant:Nn \student_processimages:nnnnn { VVVVV }
\keys_define:nn { student / images }
{
  grid .code:n = {
    \student_gridset:n { #1 }
  },
  grid .initial:n = { 2 x 2 },
  images .clist_set:N = \l_student_images_clist,
  total~width .dim_set:N = \l_student_width_dim,
  total~height .dim_set:N = \l_student_height_dim,
}
\NewDocumentCommand \images { m }
{
  \group_begin:
    \dim_set_eq:NN \parindent \c_zero_dim
    \keys_set:nn { student / images } { total~width = \linewidth , total~height = \textheight }
    \keys_set:nn { student / images } { #1 }
    \student_processimages:VVVVV \l_student_width_dim \l_student_height_dim \l_student_cols_int \l_student_rows_int \l_student_images_clist
  \group_end:
}
\ExplSyntaxOff
\begin{document}
\images{%
  images={cath-gadael-chartref,cathod,cath,coeden-nadolig-2014},
}

\images{%
  images={tspcd,cauldron,duck,tiger,example-image-c},
  grid=3x2,
}

\end{document}

EDIT

If you really need an images environment and to use im as the name of the key, then you can adapt the definition of the keys and change the definition of \images into an images environment.

For example, we can rename the key images as im:

\keys_define:nn { student / images }
{
  grid .code:n = {
    \student_gridset:n { #1 }
  },
  grid .initial:n = { 2 x 2 },
  im .clist_set:N = \l_student_images_clist,
  total~width .dim_set:N = \l_student_width_dim,
  total~height .dim_set:N = \l_student_height_dim,
}

and change \images into an images environment similar to the one in the question:

\NewDocumentEnvironment { images } { m }
{
  \group_begin:
    \begin{center}
      \dim_set_eq:NN \parindent \c_zero_dim
      \keys_set:nn { student / images } { total~width = \linewidth , total~height = \textheight }
      \keys_set:nn { student / images } { #1 }
      \student_processimages:VVVVV \l_student_width_dim \l_student_height_dim \l_student_cols_int \l_student_rows_int \l_student_images_clist
      \skip_vertical:N \medskipamount
}{
    \end{center}
  \group_end:
}

As in the original pseudocode, this uses a center environment around the images and the environment's contents. (I assume the contents of the environment should be wrapped, too, else there is no reason to use an environment at all.)

Then, rather than \images{images={<list>}}, we can say, \begin{images}{im={<list>}} ... \end{images}:

\begin{images}
  {%
    im={cath-gadael-chartref,cathod,cath,coeden-nadolig-2014},
  }
\end{images}

\begin{images}
  {%
    im={tspcd,cauldron,duck,tiger,example-image-c},
    grid=3x2,
    total height=.5\textheight,
    total width=.9\textwidth,
  }
  This is some content for the \verb|images| environment.
\end{images}

The main difference here is that the environment takes a mandatory argument rather than an optional one. This seems to reflect the intended usage: we don't want the argument to be optional given that the environment makes no sense without a list of images.

If you really wanted the argument to be optional, you could say, for example,

\NewDocumentEnvironment { images } { O { im = {example-image-a} } }

and then write

\begin{images}
  [im={<image-1>,<image-2>,<image-3>,...}]
  ....

but this seems non-ideal since the user really should get a warning rather than example-image-a in case no images are specified. Moreover, this will cause weird errors in certain cases e.g. if the user supplies an argument but does not provide a value for im. You can avoid this by specifying a default list elsewhere or by testing to see the key has a value. But that introduces a great many complexities in the service of a less transparent user interface, so seems to be a lose-lose situation all round.

6
  • Thanks. Since for my application the environment, that in my pseudocode is called images already exists and because I don't know latex3 very well, I tried to combine your code with Christian Hupfer's example and replaced \loopovercommandkey[width=2cm]{\commandkey{im}} by \images{images=\commandkey{im}}. However this doesn't work and complains that the file ente1,ente2,ente3 is not found (because it doesn't seem to split the list). Do you have an idea of how to make that work?
    – student
    May 30, 2016 at 13:37
  • @student Please see edit. I've never used keycommand, but it is easy to adapt the code to create an environment and use the im key name, if that's desired.
    – cfr
    May 30, 2016 at 16:26
  • Thanks for the edit. The argument should indeed be optional. The environment in my real case doesn't only insert images (and has many more keys ), it does other things as well and optionally you can insert images (images in one minipage and side by side in the other minipage the other stuff). But you are right, if it was the only purpose to insert images (as it seems from my minimal example), the argument should be mandatory, but that's not the case.
    – student
    May 30, 2016 at 19:28
  • That makes more sense and is really why I included the relevant modification in the answer. In that case, you probably need to test if the list is empty etc.
    – cfr
    May 31, 2016 at 0:32
  • after Christian Hupfer critique that I changed the question with the bonus part (see commend below his answer), I created a new question. If you want, please move your answer to it: tex.stackexchange.com/questions/312436/…
    – student
    May 31, 2016 at 18:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .