% \iffalse meta-comment
%
%% File: l3graphics.dtx
%
% Copyright (C) 2017-2024 The LaTeX Project
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
% license or (at your option) any later version.  The latest version
% of this license is in the file
%
%    http://www.latex-project.org/lppl.txt
%
% This file is part of the "l3experimental bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\documentclass[full]{l3doc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{l3graphics} package\\ Graphics inclusion support^^A
% }
%
% \author{^^A
%  The \LaTeX{} Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
%
% \date{Released 2024-03-14}
%
% \maketitle
%
% \begin{documentation}
%
% \section{\pkg{l3graphics} documentation}
%
% \subsection{Graphics keys}
%
% Inclusion of graphic files requires a range of low-level data be passed to
% the backend. This is set up using a small number of key--value settings,
% which are stored in the |graphics| tree.
%
% \begin{variable}{decodearray}
%   Array to decode color in bitmap graphic: when non-empty, this should
%   be in the form of one, two or three pairs of real numbers in the range
%   $[0,1]$, separated by spaces.
% \end{variable}
%
% \begin{variable}{draft}
%   Switch to enable draft mode: graphics are read but not included when this is
%   true.
% \end{variable}
%
% \begin{variable}{interpolate}
%   Switch which indicates whether interpolation should be applied to bitmap
%   graphic files.
% \end{variable}
%
% \begin{variable}{page}
%   The page to extract from a multi-page graphic file: used for |.pdf| files
%   which may contain multiple pages.
% \end{variable}
%
% \begin{variable}{pdf-attr}
%   Additional PDF-focussed attributes: available to allow control of
%   extended |.pdf| structures beyond those needed for graphic inclusion.
%   Due to backend restrictions, this key is only functional with direct
%   PDF mode (pdf\TeX{} and Lua\TeX{}).
% \end{variable}
%
% \begin{variable}{pagebox}
%   The nature of the page box setting used to determine the bounding box of
%   material: used for |.pdf| files which feature multiple page box
%   specifications. A choice from |art|, |bleed|, |crop|, |media|, |trim|.
%   The standard setting is |crop|.
% \end{variable}
%
% \begin{variable}{type}
%   The type of graphic file beign included: if this key is not set, the
%   \emph{type} is determined from the file extension.
% \end{variable}
%
% \subsection{Including graphics}
%
% \begin{function}{\graphics_include:nn, \graphics_include:nV}
%   \begin{syntax}
%     \cs{graphics_include:nn} \Arg{keys} \Arg{file}
%   \end{syntax}
%   Horizontal-mode command which includes the \meta{file} as an graphic
%   at the current location. The file \meta{type} may be given as one of the
%   \meta{keys}, or will otherwise be determined from file extension. The
%   \meta{keys} is used to pass settings as detailed above.
% \end{function}
%
% \begin{variable}{\l_graphics_ext_type_prop}
%   Defines mapping between file extensions and file types; where there is
%   no entry for an extension, the type is assumed to be the extension
%   with the leading |.| removed. Entries should be made in lower case, and
%   the key should be an extension including the leading |.|, for example
%   \begin{verbatim}
%     \prop_put:Nnn \l_graphics_ext_type_prop { .ps } { eps }
%   \end{verbatim}
% \end{variable}
%
% \begin{variable}{\l_graphics_search_path_seq}
%   Each entry is the path to a directory which should be searched when
%   seeking an graphic file. Each path can be relative or absolute, and should
%   not include the trailing slash. The entries are not expanded when
%   used so may contain active characters but should not feature any
%   variable content. Spaces need not be quoted.
% \end{variable}
%
% \subsection{Utility functions}
%
% \begin{function}[noTF]{\graphics_get_full_name:nN}
%   \begin{syntax}
%     \cs{graphics_get_full_name:nN} \Arg{file} \meta{tl~var}
%     \cs{graphics_get_full_name:nNTF} \Arg{file} \meta{tl~var} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Searches for \meta{file} first as given and then using the extensions
%   listed in \cs{l_graphics_search_ext_seq}. The search path used will be
%   the entries of \cs{l_graphics_search_path_seq}. If found, the full file
%   name including any path and extension will be returned in the
%   \meta{tl~var}. In the non-branching version, the \meta{tl var} will be
%   set to \cs{q_no_value} in the case that the graphics is not found.
% \end{function}
%
% \begin{variable}{\l_graphics_search_ext_seq}
%   Extensions to use for graphic searching when the given \meta{file} name is not
%   found by \cs{graphics_get_full_name:nN}.
% \end{variable}
%
% \begin{function}{\graphics_get_pagecount:nN}
%   \begin{syntax}
%     \cs{graphics_get_pagecount:nn} \Arg{file} \meta{tl~var}
%   \end{syntax}
%   Reads the graphics \meta{file} and extracts the number of pages, which
%   are stored in the \meta{tl~var}.
% \end{function}
%
% \subsection{Showing and logging included graphics}
%
% \begin{function}{\graphics_show_list:, \graphics_log_list:}
%   \begin{syntax}
%     \cs{graphics_show_list:}
%     \cs{graphics_log_list:}
%   \end{syntax}
%   These functions list all graphic files loaded by in a similar manner to
%   \cs{file_show_list:} and \cs{file_log_list:}. While
%   \cs{graphics_show_list:} displays the list in the terminal,
%   \cs{graphics_log_list:} outputs it to the log file only. In both cases, only
%   graphics loaded by \pkg{l3graphics} are listed.
% \end{function}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3graphics} implementation}
%
%    \begin{macrocode}
%<*package>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=graphics>
%    \end{macrocode}
%
%    \begin{macrocode}
\ProvidesExplPackage{l3graphics}{2024-03-14}{}
  {L3 Experimental graphics inclusion support}
%    \end{macrocode}
%
% \begin{variable}{\l_@@_internal_dim, \l_@@_internal_ior, \l_@@_internal_tl}
%   Scratch space.
%    \begin{macrocode}
\dim_new:N \l_@@_internal_dim
\ior_new:N \l_@@_internal_ior
\tl_new:N  \l_@@_internal_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\s_@@_stop}
%   Internal scan marks.
%    \begin{macrocode}
\scan_new:N \s_@@_stop
%    \end{macrocode}
% \end{variable}
%
% \subsection{Graphics keys}
%
% \begin{macro}
%   {
%     \l_@@_decodearray_str  ,
%     \l__@@_draft_bool      ,
%     \l_@@_interpolate_bool ,
%     \l_@@_page_int         ,
%     \l_@@_pagebox_tl       ,
%     \l_@@_pdf_str          ,
%     \l_@@_type_str
%   }
%   Keys which control features of graphics. The standard value of |pagebox|
%   set up here should match the default for the backends themselves: in
%   the absence of any other setting the |crop| should be used. Note that
%   the variable \cs{l_@@_pagebox_str} can be empty internally, as backends
%   which do not support |pagebox| are set up to clear it entirely. The
%   store for |pagebox| is a |tl| as that makes extracting the data
%   easier for some backends.
%    \begin{macrocode}
\tl_new:N \l_@@_pagebox_tl
\keys_define:nn { graphics }
  {
    decodearray .str_set:N =
      \l_@@_decodearray_str ,
    draft .bool_set:N =
      \l_@@_draft_bool ,
    interpolate .bool_set:N =
      \l_@@_interpolate_bool ,
    pagebox .choices:nn =
      { art , bleed , crop , media , trim }
      {
        \tl_set:Ne \l_@@_pagebox_tl
          { \l_keys_choice_tl box }
      } ,
    pagebox .initial:n =
      crop ,
    page .int_set:N =
      \l_@@_page_int ,
    pdf-attr .str_set:N =
      \l_@@_pdf_str ,
    type . str_set:N =
      \l_@@_type_str
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Obtaining bounding box data}
%
% \begin{variable}
%   {
%     \l_@@_llx_dim , \l_@@_lly_dim,
%     \l_@@_urx_dim , \l_@@_ury_dim
%   }
%   Storage for the return of bounding box.
%    \begin{macrocode}
\dim_new:N \l_@@_llx_dim
\dim_new:N \l_@@_lly_dim
\dim_new:N \l_@@_urx_dim
\dim_new:N \l_@@_ury_dim
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_bb_save:n, \@@_bb_save:e}
% \begin{macro}{\@@_bb_restore:nF, \@@_bb_restore:eF}
%   Caching graphic bounding boxes is sensible, and these functions are needed both
%   here and for drive-specific work. So they are made available as documented
%   functions. To save on registers, the \enquote{origin} is only saved if it is
%   not at zero.
%     \begin{macrocode}
\cs_new_protected:Npn \@@_bb_save:n #1
  {
    \dim_if_exist:cTF { c_@@_ #1 _urx_dim }
      { \msg_error:nnn { graphic } { bb-already-cached } {#1} }
      {
        \dim_compare:nNnF \l_@@_llx_dim = { 0pt }
          { \dim_const:cn { c_@@_ #1 _llx_dim } { \l_@@_llx_dim } }
        \dim_compare:nNnF \l_@@_lly_dim = { 0pt }
          { \dim_const:cn { c_@@_ #1 _lly_dim } { \l_@@_lly_dim } }
        \dim_const:cn { c_@@_ #1 _urx_dim } { \l_@@_urx_dim }
        \dim_const:cn { c_@@_ #1 _ury_dim } { \l_@@_ury_dim }
      }
  }
\cs_generate_variant:Nn \@@_bb_save:n { e }
\cs_new_protected:Npn \@@_bb_restore:nF #1#2
  {
    \dim_if_exist:cTF { c_@@_ #1 _urx_dim }
      {
        \dim_set_eq:Nc \l_@@_urx_dim { c_@@_ #1 _urx_dim }
        \dim_set_eq:Nc \l_@@_ury_dim { c_@@_ #1 _ury_dim }
        \dim_if_exist:cTF { c_@@_ #1 _llx_dim }
          { \dim_set_eq:Nc \l_@@_llx_dim { c_@@_ #1 _llx_dim } }
          { \dim_zero:N \l_@@_llx_dim }
        \dim_if_exist:cTF { c_@@_ #1 _lly_dim }
          { \dim_set_eq:Nc \l_@@_lly_dim { c_@@_ #1 _lly_dim } }
          { \dim_zero:N \l_@@_lly_dim }
      }
      {#2}
  }
\cs_generate_variant:Nn \@@_bb_restore:nF { e }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_extract_bb:n, \@@_read_bb:n}
% \begin{macro}{\@@_extract_bb_auix:nn, \@@_extract_bb_auix:Vn}
% \begin{macro}{\@@_extract_bb_auxii:nnn}
% \begin{macro}{\@@_extract_bb_auxiii:nnnn, \@@_extract_bb_auxiii:Vnnn}
% \begin{macro}{\@@_extract_bb_auxiv:nnn}
% \begin{macro}{\@@_read_bb_auxi:nnnn, \@@_read_bb_auxii:Vnnn}
% \begin{macro}
%   {
%     \@@_read_bb_auxii:w ,
%     \@@_read_bb_auxiv:w ,
%     \@@_read_bb_auxv:w
%   }
%  Extracting the bounding box from an |.eps| or |.bb| file is done by
%  reading each line and searching for the marker text |%%BoundingBox:|.
%  The data is read as a string with a mapping over
%  the lines: as there is a colon involved, a little bit of work is needed to
%  get the matching correct. The same approach covers cases where the bounding
%  box has to be calculated by |extractbb|, with just the initial phase
%  different.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_extract_bb:n #1
  {
    \int_compare:nNnTF \l_@@_page_int > 0
      { \@@_extract_bb_auxi:Vn \l_@@_page_int {#1} }
      { \@@_extract_bb_auxii:nnn {#1} { } { } }
  }
\cs_new_protected:Npn \@@_extract_bb_auxi:nn #1#2
  { \@@_extract_bb_auxii:nnn {#2} { :P #1 } { -p~#1~ } }
\cs_generate_variant:Nn \@@_extract_bb_auxi:nn { Vn }
\cs_new_protected:Npn \@@_extract_bb_auxii:nnn #1#2#3
  {
    \tl_if_empty:NTF \l_@@_pagebox_tl
      { \@@_extract_bb_auxiv:nnn }
      { \@@_extract_bb_auxiii:Vnnn \l_@@_pagebox_tl }
      {#1} {#2} {#3}
  }
\cs_new_protected:Npn \@@_extract_bb_auxiii:nnnn #1#2#3#4
  { \@@_extract_bb_auxiv:nnn {#2} { : #1 #3 } { #4 -B~#1~ } }
\cs_generate_variant:Nn \@@_extract_bb_auxiii:nnnn { V }
\cs_new_protected:Npn \@@_extract_bb_auxiv:nnn #1#2#3
  {
    \@@_read_bb_auxi:nnnn {#1} {#2}
      { \ior_shell_open:Nn \l_@@_internal_ior { extractbb~#3-O~#1 } }
      { pipe-failed }
  }
\cs_new_protected:Npn \@@_read_bb:n #1
  {
    \@@_read_bb_auxi:nnnn {#1} { }
      { \ior_open:Nn \l_@@_internal_ior {#1} }
      { graphic-not-found }
  }
%   \end{macrocode}
%  Before any real searching, a check to see if there are cached values
%  available. The name of each graphic will be unique and so it's sensible to
%  store the bounding box data in \TeX{}: this avoids multiple file operations.
%  As bounding boxes here start away from the lower-left origin, we need to
%  store all four values (contrast with for example the \texttt{pdfmode}
%  driver). Here |#2| is a potential page identifier: used to allow caching of
%  individual pages in a multi-page document. Caching is applied to the
%  upper-right position in all cases, but as the lower-left will often be
%  $(0,0)$ it is only cached if required.
%   \begin{macrocode}
\cs_new_protected:Npn \@@_read_bb_auxi:nnnn #1#2#3#4
  {
    \@@_bb_restore:nF {#1#2}
      { \@@_read_bb_auxii:nnnn {#3} {#4} {#1} {#2} }
  }
\cs_new_protected:Npe \@@_read_bb_auxii:nnnn #1#2#3#4
  {
    #1
    \exp_not:N \ior_if_eof:NTF \exp_not:N \l_@@_internal_ior
      { \msg_error:nnn { graphics } {#2} {#3} }
      {
        \ior_str_map_inline:Nn \exp_not:N \l_@@_internal_ior
          {
            \exp_not:N \@@_read_bb_auxiii:w
              ##1 ~ \c_colon_str \s_@@_stop
          }
        \@@_bb_save:n {#3#4}
      }
    \ior_close:N \exp_not:N \l_@@_internal_ior
  }
\use:e
  {
    \cs_new_protected:Npn \exp_not:N \@@_read_bb_auxiii:w
      #1 \c_colon_str #2 \s_@@_stop
      {
        \exp_not:N \str_if_eq:nnT
          { \c_percent_str \c_percent_str BoundingBox }
          {#1}
          { \exp_not:N \@@_read_bb_auxiv:w #2 ( ) \s_@@_stop }
      }
  }
%    \end{macrocode}
%   If the bounding box is |atend|, just ignore the current one and keep going.
%   Otherwise, we need to allow for tabs and multiple spaces (as the line has
%   been read as a string). The easiest way to deal with that is to scan the
%   tokens: at this stage the line fragment should be just numbers and
%   whitespace. \TeX{} will then tidy up for us, with just a leading space to
%   worry about: that is taken out by the |\use:n| here.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_read_bb_auxiv:w #1 ( #2 ) #3 \s_@@_stop
  {
    \str_if_eq:nnF {#2} { atend }
      {
        \tl_set_rescan:Nne \l_@@_internal_tl
          {
            \char_set_catcode_space:n {  9 }
            \char_set_catcode_space:n { 32 }
          }
          { \use:n #1 }
        \exp_after:wN \@@_read_bb_auxv:w \l_@@_internal_tl \s_@@_stop
      }
  }
%    \end{macrocode}
%   A trailing space was deliberately added earlier so we know that the final
%   data point is terminated by a space.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_read_bb_auxv:w #1~#2~#3~#4~#5 \s_@@_stop
  {
    \dim_set:Nn \l_@@_llx_dim { #1 bp }
    \dim_set:Nn \l_@@_lly_dim { #2 bp }
    \dim_set:Nn \l_@@_urx_dim { #3 bp }
    \dim_set:Nn \l_@@_ury_dim { #4 bp }
    \ior_map_break:
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{variable}{\l_@@_final_name_str, \l_@@_full_name_str}
%   The full name is as you'd expect the name including path and extension.
%   The final name here reflects any conversions carried out by the backend,
%   for example if an |.eps| is converted to |.pdf|.
%    \begin{macrocode}
\str_new:N \l_@@_final_name_str
\str_new:N \l_@@_full_name_str
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_internal_box}
%    \begin{macrocode}
\box_new:N \l_@@_internal_box
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_dir_str \l_@@_name_str \l_@@_ext_str}
%    \begin{macrocode}
\str_new:N \l_@@_dir_str
\str_new:N \l_@@_name_str
\str_new:N \l_@@_ext_str
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_graphics_search_path_seq}
%    \begin{macrocode}
\seq_new:N \l_graphics_search_path_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_graphics_search_ext_seq}
%   Used to specify fall-back extensions: actually set on a per-backend basis.
%    \begin{macrocode}
\seq_new:N \l_graphics_search_ext_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_graphics_ext_type_prop}
%   Mapping between extensions and types for non-standard mappings
%    \begin{macrocode}
\prop_new:N \l_graphics_ext_type_prop
\prop_put:Nnn \l_graphics_ext_type_prop { .ps } { eps }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_record_seq}
%   A list of graphic files used.
%    \begin{macrocode}
\seq_new:N \g_@@_record_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\graphics_include:nn, \graphics_include:nV}
% \begin{macro}{\@@_include_search:n}
% \begin{macro}{\@@_include:}
% \begin{macro}
%   {
%     \@@_include_auxi:n, \@@_include_auxi:e, \@@_include_auxii:n,
%     \@@_include_auxiii:n, \@@_include_auxiv:n
%   }
%   Actually including an graphic is relatively straight-forward: most of the
%   work is done by the backend. We only have to deal with making sure the
%   box has no apparent depth. Where the first given name is not found, we
%   search based on extension only if the \meta{type} was not given. The one
%   wrinkle is that we may have found a \texttt{.tex} file matching the file
%   name stem: that's not what we want, so we have to filter out.
%    \begin{macrocode}
\cs_new_protected:Npn \graphics_include:nn #1#2
  {
    \group_begin:
      \keys_set:nn { graphics } {#1}
      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
      \file_get_full_name:nNTF {#2} \l_@@_full_name_str
        {
          \str_if_eq:eeTF { \l_@@_full_name_str } { #2 .tex }
            { \msg_error:nnn { graphics } { graphic-not-found } {#2} }
            { \@@_include: }
        }
        { \msg_error:nnn { graphics } { graphic-not-found } {#2} }
    \group_end:
  }
\cs_generate_variant:Nn \graphics_include:nn { nV }
\cs_new_protected:Npn \@@_include:
  {
    \str_if_empty:NTF \l_@@_type_str
      {
        \file_parse_full_name:VNNN \l_@@_full_name_str
          \l_@@_dir_str \l_@@_name_str \l_@@_ext_str
        \@@_include_auxi:e
          {
            \exp_args:Ne \str_tail:n
              { \str_casefold:V \l_@@_ext_str }
          }
      }
      { \@@_include_auxi:e { \l_@@_type_str } }
  }
\cs_new_protected:Npn \@@_include_auxi:n #1
  {
    \prop_get:NnNF \l_graphics_ext_type_prop { .#1 } \l_@@_internal_tl
      { \tl_set:Nn \l_@@_internal_tl {#1} }
    \exp_args:NV \@@_include_auxii:n \l_@@_internal_tl
  }
\cs_generate_variant:Nn \@@_include_auxi:n { e }
\cs_new_protected:Npn \@@_include_auxii:n #1
  {
    \mode_leave_vertical:
    \cs_if_exist:cTF { @@_backend_include_ #1 :n }
      {
        \tl_set_eq:NN \l_@@_final_name_str \l_@@_full_name_str
        \str_set:Ne \l_@@_full_name_str
          { \exp_args:NV \__kernel_file_name_quote:n \l_@@_full_name_str }
        \exp_args:NnV \use:c { @@_backend_getbb_ #1 :n }
          \l_@@_full_name_str
        \seq_gput_right:NV \g_@@_record_seq \l_@@_final_name_str
        \clist_if_exist:NT \@filelist
          { \exp_args:NV \@addtofilelist \l_@@_final_name_str }
        \bool_if:NTF \l_@@_draft_bool
          { \@@_include_auxiii:n }
          { \@@_include_auxiv:n }
            {#1}
      }
      { \msg_error:nnn { graphics } { unsupported-graphic-type } {#1} }
  }
\cs_new_protected:Npn \@@_include_auxiii:n #1
  {
    \hbox_to_wd:nn { \l_@@_urx_dim - \l_@@_llx_dim }
      {
        \tex_vrule:D
        \tex_hss:D
        \vbox_to_ht:nn
          { \l_@@_ury_dim - \l_@@_lly_dim }
          {
            \tex_hrule:D width
              \dim_eval:n { \l_@@_urx_dim - \l_@@_llx_dim }
            \tex_vss:D
            \hbox_to_wd:nn
              { \l_@@_urx_dim - \l_@@_llx_dim }
              {
                \ttfamily
                \tex_hss:D \l_@@_full_name_str \tex_hss:D
              }
            \tex_vss:D
            \tex_hrule:D
          }
        \tex_hss:D
        \tex_vrule:D
      }
  }
\cs_new_protected:Npn \@@_include_auxiv:n #1
  {
    \hbox_set:Nn \l_@@_internal_box
      {
        \exp_args:NnV \use:c { @@_backend_include_ #1 :n }
          \l_@@_full_name_str
      }
    \box_set_dp:Nn \l_@@_internal_box { 0pt }
    \box_set_ht:Nn \l_@@_internal_box
      { \l_@@_ury_dim - \l_@@_lly_dim }
    \box_set_wd:Nn \l_@@_internal_box
      { \l_@@_urx_dim - \l_@@_llx_dim }
    \box_use_drop:N \l_@@_internal_box
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\graphics_show_list:, \graphics_log_list:, \@@_list:N}
% \begin{macro}[EXP]{\@@_list_aux:n}
%   A function to list all graphic files used.
%    \begin{macrocode}
\cs_new_protected:Npn \graphics_show_list: { \@@_list:N \msg_show:nneeee }
\cs_new_protected:Npn \graphics_log_list: { \@@_list:N \msg_log:nneeee }
\cs_new_protected:Npn \@@_list:N #1
  {
    \seq_remove_duplicates:N \g_@@_record_seq
    #1 { kernel } { file-list }
      { \seq_map_function:NN \g_@@_record_seq \@@_list_aux:n }
        { } { } { }
  }
\cs_new:Npn \@@_list_aux:n #1 { \iow_newline: #1 }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsection{Utility functions}
%
% \begin{macro}{\graphics_get_full_name:nN}
% \begin{macro}[TF]{\graphics_get_full_name:nN}
% \begin{macro}{\@@_get_full_name:n}
%   As well as searching by path, etc., there is a need here to check that
%   we do not trip over |foo.bar| if |.bar| is not a known extension for
%   the current backend.
%    \begin{macrocode}
\cs_new_protected:Npn \graphics_get_full_name:nN #1#2
  {
    \graphics_get_full_name:nNF {#1} #2
      { \tl_set:Nn #2 { \q_no_value } }
  }
\prg_new_protected_conditional:Npnn \graphics_get_full_name:nN #1#2
  { T , F , TF }
  {
    \group_begin:
      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
      \file_get_full_name:nNTF {#1} \l_@@_full_name_str
        {
          \str_if_eq:eeTF { \l_@@_full_name_str } { #1 .tex }
            { \@@_get_full_name:n {#1} }
            {
              \file_parse_full_name:VNNN \l_@@_full_name_str
                \l_@@_dir_str \l_@@_name_str \l_@@_ext_str
              \seq_map_inline:Nn \l_graphics_search_ext_seq
                {
                  \str_if_eq:nVT {##1} \l_@@_ext_str
                    { \seq_map_break:n { \use_none:nn } }
                }
                  \@@_get_full_name:n {#1}
            }
        }
        { \@@_get_full_name:n {#1} }
    \exp_args:NNNV \group_end:
    \tl_set:Nn #2 \l_@@_full_name_str
    \tl_if_empty:NTF #2
      { \prg_return_false: }
      { \prg_return_true: }
  }
\cs_new_protected:Npn \@@_get_full_name:n #1
  {
    \str_clear:N \l_@@_full_name_str
    \seq_map_inline:Nn \l_graphics_search_ext_seq
      {
        \file_get_full_name:nNT { #1 ##1 } \l_@@_full_name_str
          { \seq_map_break:n { \use_none:nn } }
      }
    \use:n
      { \str_clear:N \l_@@_full_name_str }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\graphics_get_pagecount:nN}
% \begin{macro}{\@@_get_pagecount:n}
% \begin{macro}{\@@_get_pagecount:nw}
%   A generic function to read the number of pages in a graphic file. This is
%   used by all of the backend where there is not a dedicated primitive. The
%   plan is essentially the same as reading the bounding box. To avoid multiple
%   calls, the value is cached either here or in the backend.
%    \begin{macrocode}
\cs_new_protected:Npn \graphics_get_pagecount:nN #1#2
  {
    \group_begin:
      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
      \file_get_full_name:nNTF {#1} \l_@@_full_name_str
        {
          \int_if_exist:cF { c_@@_ \l_@@_full_name_str _pages_int }
            {
              \exp_args:NV \@@_backend_get_pagecount:n
                \l_@@_full_name_str
            }
          \tl_set:Nv #2 { c_@@_ \l_@@_full_name_str _pages_int }
        }
        {
          \tl_set:Nn #2 { 0 }
          \msg_error:nnn { graphics } { graphic-not-found } {#1}
        }
    \exp_args:NNNV \group_end:
    \tl_set:Nn #2 #2
  }
\cs_new_protected:Npe \@@_get_pagecount:n #1
  {
    \ior_shell_open:Nn \exp_not:N \l_@@_internal_ior
      { extractbb~-O~#1 }
    \exp_not:N \ior_if_eof:NTF \exp_not:N \l_@@_internal_ior
      { \msg_error:nnn { graphics } { pipe-failed } }
      {
        \ior_str_map_inline:Nn \exp_not:N \l_@@_internal_ior
          {
            \exp_not:N \@@_get_pagecount:nw {#1}
              ##1 ~ \c_colon_str \c_colon_str \s_@@_stop
          }
        \exp_not:N \int_if_exist:cF { c_@@_ #1 _pages_int }
          { \int_const:cn { c_@@_ #1 _pages_int } { 1 } }
      }
    \ior_close:N \exp_not:N \l_@@_internal_ior
  }
\use:e
  {
    \cs_new_protected:Npn \exp_not:N \@@_get_pagecount:nw
      #1#2 \c_colon_str #3 \c_colon_str #4 \s_@@_stop
      {
        \exp_not:N \str_if_eq:nnT
          { \c_percent_str \c_percent_str Pages }
          {#2}
          {
            \int_const:cn { c_@@_ #1 _pages_int } {#3}
            \exp_not:N \ior_map_break:
          }
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro} 
%
% \subsection{Messages}
%
%    \begin{macrocode}
\msg_new:nnnn { graphics } { graphic-not-found }
  { Image~file~'#1'~not~found. }
  {
    LaTeX~tried~to~open~graphic~file~'#1',~
    but~the~file~could~not~be~read.
  }
\msg_new:nnnn { graphics } { pipe-failed }
  { Cannot~run~piped~system~commands. }
  {
    LaTeX~tried~to~call~a~system~process~but~this~was~not~possible.\\
    Try~the~"--shell-escape"~(or~"--enable-pipes")~option.
  }
\msg_new:nnnn { graphics } { unsupported-graphic-type }
  { Image~type~'#1'~not~supported~by~current~driver. }
  {
    LaTeX~was~asked~to~include~an~graphic~of~type~'#1',~
    but~this~is~not~supported~by~the~current~driver~(production~route).
  }
%    \end{macrocode}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex