How to expand a macro into viewport= arguments(includegraphics)

How do I properly expand a macro \myviewport so that it is parsed as the viewport=0pt 10pt 100pt 200pt argument of \includegraphics[clip, \myviewport]{somepdf.pdf}?

\documentclass{article}
\usepackage{graphicx}
\begin{document}
\newcommand*{\aport}{45.29pt 86.28pt 235pt 187pt}
\includegraphics[clip, viewport={\aport}]{somepdf.pdf}
\end{document}


Error: ! Argument of \Gread@parse@vp has an extra }. My simple idea was based on \includegraphics optional argument not resolved if macro

What does work is giving individual macros for each viewport value:

\documentclass{article}
\usepackage{graphicx}
\usepackage{calc}
\begin{document}
\newlength{\Xoffset}
\setlength{\Xoffset}{35pt}
\newlength{\myl}
\setlength{\myl}{235pt - \Xoffset}
\newcommand*{\lux}{200pt}
\includegraphics[clip, viewport=10pt 50pt \lux{} \the\myl{}, page=3]{somepdf.pdf}
\end{document}


Note that \myl is a length and \lux is a "string".

In the long run I'd like to automate the cropping of some pdf files (which have Media Boxes not starting at 0 0):

\documentclass{article}
\usepackage{graphicx}
\usepackage{calc}

\newlength{\Xoffset}
\newlength{\Yoffset}
\newcommand*{\setpdfoffset}[2]{
\setlength{\Xoffset}{#1}
\setlength{\Yoffset}{#2}
}
\newcommand*{\aviewport}{} %set an empty default
\setpdfoffset{0pt}{0pt} %set a default

\newcommand*{\setviewport}[4]{
\newlength{\mylux}\setlength{\mylux}{#1-\Xoffset}
\newlength{\myluy}\setlength{\myluy}{#2-\Yoffset}
\newlength{\myrox}\setlength{\myrox}{#3-\Xoffset}
\newlength{\myroy}\setlength{\myroy}{#4-\Yoffset}
\renewcommand*{\aviewport}{viewport={\the\mylux} {\the\myluy}  {\the\myrox}  {\the\myroy}}
}
\begin{document}

%in Windows command line:
% pdfinfo -box somepdf.pdf |awk "/MediaBox/ {print; print \"Xoffset=\"$2 \" Yoffset=\"$3\"\n\"; print \"\\newlength{\\Xoffset}\\setlength{\\Xoffset}{\"$2\"pt} \\newlength{\\Yoffset}\\setlength{\\Yoffset}{\"$3\"pt}\"}"

\setpdfoffset{35.29pt}{36.28pt}
\setviewport{45.29pt}{86.28pt}{235pt}{187pt}
\emph{\aviewport}
\fbox{\includegraphics[clip, \aviewport{}, page=2]{somepdf}
\end{document}


Error: keyval: viewport={10.0pt} {50.0pt} {199.71pt} {150.72pt} undefined. ...lip, \aviewport]{somepdf}}

XeLaTeX is my first choice, but pdfLaTeX doesn't like this code either.

Bonus: Get the X and Y Offset values for \setpdfoffset by using \input{|"pdfinfo -box somepdf.pdf |awk ..."}, but this is an escaping question and not an expanding.

• Note that there are two subtly different issues here. Hiding the key=value part in a macro breaks in general with keyval methods as the parser for keys doesn't see the right name: that's general and not dependent on the key involved. Here, you have a second issue as viewport is looking for four values separated by spaces, and again hiding stuff in a macro confuses the internals. – Joseph Wright Jan 7 '15 at 7:02
• Regarding the removal of the "crop" tag: Isn't using viewport a way to "crop"? – nks Jan 7 '15 at 21:01
• Fair enough: I'll reverse that – Joseph Wright Jan 7 '15 at 21:06

Here are a couple of ways:

\documentclass{article}
\usepackage{graphicx}
\newcommand{\myviewport}{viewport=0pt 10pt 200pt 50pt}
\begin{document}
\includegraphics[clip, viewport=0pt 10pt 200pt 50pt]{example-image}

\begingroup\edef\x{\endgroup\noexpand\includegraphics[clip, \myviewport]{example-image}}\x

{\let\includegraphics\relax\xdef\x{\includegraphics[clip, \myviewport]{example-image}}}\x
\end{document}


The first shows what the expected output should resemble.

The second defines an expanded version \x of \includegraphics[..]{...} where \includegraphics is left unexpanded. It's also local as \x is discarded after it is used. It is very similar to

\edef\x{\noexpand\includegraphics[clip, \myviewport]{example-image}}\x


without the localization of \x.

The third defines a similar \x which is now global (also expanded, hence \xdef). However, in order to avoid the expansion of \includegraphics it is set to \relax within a group. That prevents it from being expanded with the call to \xdef, and it also survives the group. In particular \show\x reveals

> \x=macro:
->\includegraphics [clip, viewport=0pt 10pt 200pt 50pt]{example-image}.


The viewport, trim and bb (bounding box) keys are special, as they require a specific value input. The input has to be in the expanded form #1 #2 #3 #4 and is recognized using TeX's argument text or \def. So, if the match is not exact, incorrect arguments may be assigned to incorrect elements, causing the problem you're experiencing.

Other macros may be used in a macro-form, to an extent. As a minimum, you have to use an explicit =:

\documentclass{article}

\usepackage{graphicx}

\begin{document}

\includegraphics[width=100pt]{example-image}

\newcommand{\mywidth}{width}%
\newcommand{\widthdimen}{100pt}%
\includegraphics[\mywidth=\widthdimen]{example-image}

%\newcommand{\mywidthkeyval}{width=100pt}%
%\includegraphics[\mywidthkeyval]{example-image}% Does not work

\includegraphics[clip, viewport=10pt 10pt 50pt 50pt]{example-image}

\newcommand{\myviewport}{viewport}%
\newcommand{\viewportvals}{10pt 10pt 50pt 50pt}%
\includegraphics[clip, \myviewport=10pt 10pt 50pt 50pt]{example-image}
%\includegraphics[clip, \myviewport=\viewportvals]{example-image}% Does not work

\end{document}

• Perhaps you might explain a bit why expansion of both the key=value syntax and here the value itself are needed: as I've commented on the question, there are actually two related but distinct issues here as one is about keyval in general and one is tied to the format of the viewport value. – Joseph Wright Jan 7 '15 at 7:04
• @JosephWright: But your comment captures it beautifully already. What is there left to add? – Werner Jan 7 '15 at 7:09
• I think that it would make a good part of an answer, rather than just a comment, but as it's not a full answer and as you've already posted a good one it might be best added in some way here. – Joseph Wright Jan 7 '15 at 8:24
• All two versions of @Werner work. I'll stick with #1 for three reasons: a) it looks more like \includegraphics(compared to @steven-b-segletes); b) the macros for expanding it look a lot more readable and c) less prone to introduce stray spaces. I'm now using three lines to define my own includegraphics 1: newcommand*{\mygraphics}[2][]{% percent sign removes following whitespaces(e.g. generated from new lines), else they are in the document. 2: \begingroup\edef\x{\endgroup\noexpand\includegraphics[clip=true, #1]{#2}}\x 3:} usage \fbox{\mygraphics[\aviewport, page=1]{example-image}} – nks Jan 7 '15 at 14:19
• @JosephWright: I'll add your comments to my answer tonight... sorry for the delay. – Werner Jan 7 '15 at 21:07

The four values for options viewport or trim are identified by the separating spaces. The parser, defined for these options by package graphicx expects explicit spaces and fails, if the spaces are hidden inside a macro.

The following example redefines option viewport (in the same way option trim can be redefined) to expand its argument before passing the expanded value to the original option code:

\documentclass{article}
\usepackage{graphicx}
\makeatletter
% Remember original option viewport as viewport@org
\let\KV@Gin@viewport@org\KV@Gin@viewport
% Redefine option viewport to expand the argument and
% pass it to option viewport@org.
\define@key{Gin}{viewport}{%
\begingroup
\edef\x{\endgroup
\noexpand\setkeys{Gin}{viewport@org={#1}}%
}\x
}
\makeatother

\begin{document}
\newcommand*{\aport}{45.29pt 86.28pt 235pt 187pt}
\includegraphics[clip, viewport=\aport]{tiger}
\end{document}


The equal sign and the comma are syntax characters in LaTeX's key value lists (package keyval and friends). The key value parser will fail, if these characters are hidden inside macros. But it is possible to define a new option for graphicx with an argument that is expanded and executed again as key value list:

\documentclass{article}
\usepackage{graphicx}
\makeatletter
\define@key{Gin}{expandoptions}{%
\begingroup
\edef\x{\endgroup
\noexpand\setkeys{Gin}{#1}%
}\x
}
\makeatother

\begin{document}
\newcommand*{\viewaport}{viewport=45.29pt 86.28pt 235pt 187pt, clip}
\includegraphics[expandoptions=\viewaport]{tiger}
\end{document}


You can define a new key for \includegraphics, I'll call it xviewport.

\documentclass{article}
\usepackage{graphicx}
\usepackage{calc}

\makeatletter
\define@key{Gin}{xviewport}[]{%
\begingroup\edef\x{%
\endgroup\noexpand\setkeys{Gin}{viewport=\x@viewport}%
}\x
}

\newlength{\Xoffset}
\newlength{\Yoffset}
\newcommand*{\setpdfoffset}[2]{%
\setlength{\Xoffset}{#1}%
\setlength{\Yoffset}{#2}%
}
\newcommand*{\setviewport}[4]{%
\def\x@viewport{%
{\the\dimexpr#1-\Xoffset}
{\the\dimexpr#2-\Yoffset}
{\the\dimexpr#3-\Xoffset}
{\the\dimexpr#4-\Yoffset}%
}%
}
\makeatother

\begin{document}

\setpdfoffset{35.29pt}{36.28pt}
\setviewport{45.29pt}{86.28pt}{235pt}{187pt}

\fbox{\includegraphics[clip, xviewport]{example-image.pdf}}

\end{document}


This works, but may not be general enough for your need:

\documentclass{article}
\usepackage[demo]{graphicx}
\begin{document}
\newcommand*{\aport}{45.29pt 86.28pt 235pt 187pt}
\def\X{\includegraphics[clip, viewport=}
\expandafter\X\aport]{somepdf.pdf}
\end{document}

• This works, but you need to be careful about whitespaces inside it: To see this, draw an \fbox{} around it: \fbox{\def\X{\includegraphics[clip, viewport=} \expandafter\X\aport]{example-image}} It adds a little white margin(approximately one character width, the space in viewport=} \expandafter) to the left side of the image. The other solutions provided by @werner don't add this. \fbox{\begingroup\edef\x{\endgroup\noexpand\includegraphics[clip, viewport = \aport] {example-image}}}\x} – nks Jan 7 '15 at 11:41
• @nkrs True, one must be always careful of stray spaces leading up to and following a macro use. But the actual macro use itself, as given by \fbox{\expandafter\X\aport]{somepdf.pdf}} introduces no stray space. – Steven B. Segletes Jan 7 '15 at 11:46