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.
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.
center
...