# Scale text (\scalebox) in Plain TeX/XeTeX to make it narrower

I've been looking up stuff and searching through the Internet for a few hours now. Basically, I need to do something like this, which I can do in LaTeX with the graphicx package:

\scalebox{.7}[1.0]{NARROW}


But! I need to do it using plain TeX (XeTeX with PDF, to be precise), pereferably with no packages. What is the simplest, most direct way to do it? Does it have something to do with \setbox and \wd? I could only try:

\def\narrow#1{
\setbox0\hbox{#1}
\multiply\wd0 by 0.7
\box0
}


But that obviously doesn't work, it errors out with You can't use \wd' after \multiply.

• Welcome to TeX.SX! To make your approach you'd need \wd0=0.7\wd0 instead. However this will only change the width of the box, but the characters therein will not change, so some will protrude outside the box. You need driver-specific code to do that. You can load graphicx in Plain with \input graphicx.tex. Nov 25, 2019 at 18:40
• Thank you! I've updated the question to include the fact that I'm using XeTeX with PDF output. Nov 25, 2019 at 18:45

dgoodmaniii's answer already shows you why your code didn't work, and how to make it work. However TeX itself doesn't know how to “scale” things. You can only change the dimensions of boxes, but the characters inside them remain untouched. To have the same scaling as \scalebox you need driver-specific code, which will be different from one engine to the other.

Since you use XeTeX, here's the \scalebox code that I stole adapted from graphics.sty and dvipdfmx.def. The code from graphics.sty contains the warpper macro \scalebox, which uses \Gscale@start and \Gscale@end. These two macros put the \specials which will do the scaling (not that due to my lazyness, the second argument of \scalebox is no longer optional):

\catcode\@=11
\long\def\scalebox#1#2#3{%
\leavevmode
\def\Gscale@x{#1}\def\Gscale@y{#2}%
\setbox\z@\hbox{{#3}}%
\setbox\tw@\hbox{\Gscale@start\rlap{\copy\z@}\Gscale@end}%
\ifdim#2\p@<\z@
\ht\tw@-#2\dp\z@
\dp\tw@-#2\ht\z@
\else
\ht\tw@#2\ht\z@
\dp\tw@#2\dp\z@
\fi
\ifdim#1\p@<\z@
\hbox to-#1\wd\z@{\kern-#1\wd\z@\box\tw@\hss}%
\else
\hbox to#1\wd\z@{\box\tw@\kern#1\wd\z@\hss}%
\fi}
\def\GPT@space{ }
\def\Gscale@start{%
\special{pdf:btrans}%
\special{x:scale \Gscale@x\GPT@space\Gscale@y}}
\def\Gscale@end{\special{pdf:etrans}}
\catcode\@=12

\scalebox{0.7}{1.0}{NARROW}\par
\scalebox{1.0}{1.0}{NARROW}\par
\scalebox{1.3}{1.0}{NARROW}\par

\bye


Of course, just loading graphicx.tex is much easier (and engine independent):

\input graphicx.tex

\scalebox{0.7}[1.0]{NARROW}\par
\scalebox{1.0}{NARROW}\par
\scalebox{1.3}[1.0]{NARROW}\par

\bye

• I've simplified your answer and posted my current stuff here, but I am going to accept your answer as correct, because it does what I need. Thanks! Nov 25, 2019 at 23:08
• @Ainar-G Well, yes, the code can certainly be simplified. The version from graphics takes into account the possibility of negative scaling factors, which in your case (apparently fixed 0.7) is not a problem, so the code can be shortened. Personally I'd prefer something that works in more general cases, but that's just my preference :-) (P.S.: I'd add \leavevmode to the beginning of your code. Try \narrow{Hello}\par Hello\par to see the problem) Nov 25, 2019 at 23:18
• I see now. Edited my answer. Thanks again! For anyone reading, see this answer on why you need \leavevmode. Nov 25, 2019 at 23:28

It doesn't work because \wd is basically an operator; you give it the box you want the width of. Also keep in mind that \multiply doesn't work with floating-point numbers, only integers; so if you want to multiply by something like 0.7, you'll have to multiply by 7 and then divide by 10.

Assuming that the full length is \hsize, and you want a box 0.7 times \hsize:

\hbox to\hsize{This box will be the full width}
\newbox\mybox
\setbox\mybox\hbox to\hsize{\hfil}
\newdimen\mywidth\mywidth=\wd\mybox
\multiply\mywidth by7
\divide\mywidth by10
\hbox to\mywidth{This box will be the right width}
\bye


The first line is purely for demonstrative purposes, to show the full length of the line. Then we declare a new box, \mybox, and make sure that its width is also equal to \hsize by assigning to it an empty \hbox of that width. Then we declare a new dimen, \mywidth, and set it equal to the width of \mybox. Then we multiply \mywidth by 7 and divide it by 10, which is the equivalent of multiplying by 0.7. Finally, we put some text in a box of \mywidth, which, as you can see, if 70% the width of the original box.

You can, of course, wrap this in a macro:

\hbox to\hsize{This box will be the full width}

\newbox\mybox
\newdimen\mywidth
\def\width#1#2#3#4{%
\setbox\mybox\hbox to#1{\hfil}
\mywidth=\wd\mybox
\multiply\mywidth by#2
\divide\mywidth by#3
\hbox to\mywidth{#4}
}%

\width{\hsize}{7}{10}{This box will be the short width}
\width{\hsize}{8}{10}{This box will be the short width}
\width{\hsize}{4}{10}{This box will be the short width}
\bye


That will produce the following:

If you need the text to wrap, wrap the #4 in a \hbox and \vbox and change the \hsize inside it:

\newbox\mybox
\newdimen\mywidth
\def\width#1#2#3#4{%
\setbox\mybox\hbox to#1{\hfil}
\mywidth=\wd\mybox
\multiply\mywidth by#2
\divide\mywidth by#3
\hbox to\mywidth{%
\hfil%
\vbox{%
\hsize=\mywidth
#4
}%
\hfil%
}
}%


I've simplified @phelype-oleinik's example as much as I could, and I came up with this:

\def\narrow#1{%
% Enter horizonal mode to make sure we don't bork up the start of the
% paragraph.
\leavevmode%
% Set the scale factors.
\def\ScaleX{0.7}%
\def\ScaleY{1.0}%
% Set box zero to the box with the text as is, without scaling, to get
% the width of this box.  Not sure if that can be done without \setbox.
\setbox 0 \hbox{#1}%
\def\NewWidth{\ScaleX \wd 0}%
% The next box will overfull, but that's okay, as long as it's within
% the new width.
\hfuzz = \NewWidth%
% Create a horizontal box with the new width containing the scaled
% text.
\hbox to \NewWidth {%
\special{pdf:btrans}%
% x:scale requires that the two numbers are separated by a space.
\special{x:scale \ScaleX\space\ScaleY}%
#1%
\special{pdf:etrans}%
}%
}


This works for me, but if you see any potential issues, please let me know.

• If you want to make it simpler you can also use #1\hss instead of setting \hfuzz (seems simpler to me, at least :-). Also you don't need to define \NewWidth, you can simply \hbox to \ScaleX \wd 0{`, which save you from doing one definition. The readability of the code is debatable, though ;-) Nov 25, 2019 at 23:36