1

I was wondering if it is possible to define rotatebox in plain old TeX (without importing eplain, pstricks, miniltx or what not or using pdfrotate or other pdf-specific commands).

The word "rotate" does not seem to appear in the TeXbook and it only refers to vbox and hbox.

Use cases:

If would be nice to have a macro for origin center, another one (or the same with a different argument) for origin top-left, etc. but just one will be a valid answer to help me understand how to do such things.

4
  • 5
    If you want reinvent the wheel: rotation is backend dependant. If you create a pdf you have e.g. to use pdfsetmatrix (see pdftex.def) and with dvips postscript instructions like \special{ps: gsave currentpoint currentpoint translate \Grot@angle\GPT@space neg rotate neg exch neg exch translate} (see dvips.def). And naturally you need various commands to calculate the angle etc (see e.g graphics.sty). It is easier to reuse existing code. Nov 16, 2022 at 22:32
  • @UlrikeFischer It is somewhat surprising that TeX does not have this natively, I don't want to reinvent the wheel, just to know how the first wheel was made
    – user285036
    Nov 16, 2022 at 23:01
  • as ulrike says you need to implement sin and cos functions in tex macros (unless you just want to rotate by 90° which would cover 99% of use cases) and the insert different \special for each driver (dvips, dvipdfmx, dvisvg, ...) or back end primitive for pdftex or luatex Nov 16, 2022 at 23:19
  • 2
    @Laravel The point here is basically what you observe: TeX itself can't do rotation, so we need to use backend-specific specials.
    – Joseph Wright
    Nov 17, 2022 at 10:06

2 Answers 2

3

I only filled in the backend code for pdftex the other branches of the conditionals at the end will need suitable \Grot@start definitions (from graphics-def/luatex.def etc)

But this works with pdftex

enter image description here

%package
\catcode`@=11

%latex.ltx
\def\zap@space#1 #2{%
  #1%
  \ifx#2\@empty\else\expandafter\zap@space\fi
  #2}
\def\@car#1#2\@nil{#1}
\def\@cdr#1#2\@nil{#2}
\def\@empty{}
\long\def\@gobble#1{}
\long\def\@firstofone#1{#1}

%trig.sty
\chardef\nin@ty=90
\chardef\@clxxx=180
\chardef\@lxxi=71
\mathchardef\@mmmmlxviii=4068
\chardef\@coeffz=72
\chardef\@coefb=42
\mathchardef\@coefc=840
\mathchardef\@coefd=5040
{\catcode`t=12\catcode`p=12\gdef\noPT#1pt{#1}}
\def\TG@rem@pt#1{\expandafter\noPT\the#1\space}
\def\TG@term#1{%
 \dimen@\@tempb\dimen@
 \advance\dimen@ #1\p@}
\def\TG@series{%
 \dimen@\@lxxi\dimen@
 \divide \dimen@ \@mmmmlxviii
 \edef\@tempa{\TG@rem@pt\dimen@}%
 \dimen@\@tempa\dimen@
 \edef\@tempb{\TG@rem@pt\dimen@}%
 \divide\dimen@\@coeffz
 \advance\dimen@\m@ne\p@
 \TG@term\@coefb
 \TG@term{-\@coefc}%
 \TG@term\@coefd
 \dimen@\@tempa\dimen@
 \divide\dimen@ \@coefd}
\def\CalculateSin#1{{%
  \expandafter\ifx\csname sin(\number#1)\endcsname\relax
    \dimen@=#1\p@\TG@@sin
    \expandafter\xdef\csname sin(\number#1)\endcsname
                                    {\TG@rem@pt\dimen@}%
  \fi}}
\def\CalculateCos#1{{%
  \expandafter\ifx\csname cos(\number#1)\endcsname\relax
    \dimen@=\nin@ty\p@
    \advance\dimen@-#1\p@
    \TG@@sin
    \expandafter\xdef\csname cos(\number#1)\endcsname
                                     {\TG@rem@pt\dimen@}%
  \fi}}
\def\TG@reduce#1#2{%
\dimen@#1#2\nin@ty\p@
  \advance\dimen@#2-\@clxxx\p@
  \dimen@-\dimen@
  \TG@@sin}
\def\TG@@sin{%
  \ifdim\TG@reduce>+%
  \else\ifdim\TG@reduce<-%
  \else\TG@series\fi\fi}%
\def\UseSin#1{\csname sin(\number#1)\endcsname}
\def\UseCos#1{\csname cos(\number#1)\endcsname}
\def\z@num{0 }
\def\@tempa{1 }
\def\@tempb{-1 }
\expandafter\let\csname sin(0)\endcsname\z@num
\expandafter\let\csname cos(0)\endcsname\@tempa
\expandafter\let\csname sin(90)\endcsname\@tempa
\expandafter\let\csname cos(90)\endcsname\z@num
\expandafter\let\csname sin(-90)\endcsname\@tempb
\expandafter\let\csname cos(-90)\endcsname\z@num


%graphics.sty
\newdimen\Grot@height
\newdimen\Grot@left
\newdimen\Grot@right
\newdimen\Grot@depth
\newdimen\Grot@l
\newdimen\Grot@r
\newdimen\Grot@h
\newdimen\Grot@d
\newdimen\Grot@x
\newdimen\Grot@y
\long\def\rotatebox#1#2{%
  \leavevmode
  \Grot@setangle{#1}%
  \setbox\z@\hbox{{#2}}%
  \Grot@x\z@
  \Grot@y\z@
  \Grot@box}
\def\Grot@setangle#1{\edef\Grot@angle{#1}}
\def\Grot@Px#1#2#3{%
        #1\Grot@cos#2%
        \advance#1-\Grot@sin#3}
\def\Grot@Py#1#2#3{%
        #1\Grot@sin#2%
        \advance#1\Grot@cos#3}
\def\Grot@box{%
  \begingroup
  \CalculateSin\Grot@angle
  \CalculateCos\Grot@angle
  \edef\Grot@sin{\UseSin\Grot@angle}%
  \edef\Grot@cos{\UseCos\Grot@angle}%
  \Grot@r\wd\z@  \advance\Grot@r-\Grot@x
  \Grot@l\z@     \advance\Grot@l-\Grot@x
  \Grot@h\ht\z@  \advance\Grot@h-\Grot@y
  \Grot@d-\dp\z@ \advance\Grot@d-\Grot@y
  \ifdim\Grot@sin\p@>\z@
    \ifdim\Grot@cos\p@>\z@
      \Grot@Py\Grot@height \Grot@r\Grot@h%B
      \Grot@Px\Grot@right  \Grot@r\Grot@d%E
      \Grot@Px\Grot@left   \Grot@l\Grot@h%C
      \Grot@Py\Grot@depth  \Grot@l\Grot@d%D
    \else
      \Grot@Py\Grot@height \Grot@r\Grot@d%E
      \Grot@Px\Grot@right  \Grot@l\Grot@d%D
      \Grot@Px\Grot@left   \Grot@r\Grot@h%B
      \Grot@Py\Grot@depth  \Grot@l\Grot@h%C
    \fi
  \else
    \ifdim\Grot@cos\p@<\z@
      \Grot@Py\Grot@height \Grot@l\Grot@d%D
      \Grot@Px\Grot@right  \Grot@l\Grot@h%C
      \Grot@Px\Grot@left   \Grot@r\Grot@d%E
      \Grot@Py\Grot@depth  \Grot@r\Grot@h%B
    \else
      \Grot@Py\Grot@height \Grot@l\Grot@h%C
      \Grot@Px\Grot@right  \Grot@r\Grot@h%B
      \Grot@Px\Grot@left   \Grot@l\Grot@d%D
      \Grot@Py\Grot@depth  \Grot@r\Grot@d%E
    \fi
  \fi
  \advance\Grot@height\Grot@y
  \advance\Grot@depth\Grot@y
  \Grot@Px\dimen@  \Grot@x\Grot@y
  \Grot@Py\dimen@ii \Grot@x\Grot@y
  \dimen@-\dimen@     \advance\dimen@-\Grot@left
  \dimen@ii-\dimen@ii \advance\dimen@ii\Grot@y
  \setbox\z@\hbox{%
    \kern\dimen@
    \raise\dimen@ii\hbox{\Grot@start\box\z@\Grot@end}}%
  \ht\z@\Grot@height
  \dp\z@-\Grot@depth
  \advance\Grot@right-\Grot@left\wd\z@\Grot@right
  \leavevmode\box\z@
  \endgroup}




\ifx\pdfoutput\undefined
\else
\ifnum\pdfoutput=1
%pdftex.def
\def\GPT@space{ }
\def\GPT@MatrixIdentity{1 0 0 1}
\def\GPT@Zero{0}
\def\GPT@Minus{-}
\def\GPT@NormalizeNumber#1{%
  \edef#1{#1}%
  \edef#1{\expandafter\zap@space#1 \@empty}%
  \edef#1{\expandafter\GPT@ZapPlus#1+\@nil}%
  \edef#1{\expandafter\GPT@ZapMinusMinus#1--\@nil}%
  \expandafter\GPT@Split#1..\@nil
  \ifx\GPT@frac\@empty
  \else
    \edef\GPT@frac{%
      \expandafter\GPT@Reverse\expandafter{\expandafter}\GPT@frac\@nil
    }%
    \edef\GPT@frac{%
      \expandafter\GPT@ZapLeadingZeros\GPT@frac\@empty
    }%
    \ifx\GPT@frac\@empty
    \else
      \edef\GPT@frac{%
        \expandafter\GPT@Reverse\expandafter{\expandafter}\GPT@frac\@nil
      }%
    \fi
  \fi
  \edef\GPT@sign{\expandafter\@car\GPT@int\@empty\@nil}%
  \ifx\GPT@sign\GPT@Minus
    \edef\GPT@int{\expandafter\@cdr\GPT@int\@nil}%
  \else
    \def\GPT@sign{}%
  \fi
  \edef\GPT@int{%
    \expandafter\GPT@ZapLeadingZeros\GPT@int\@empty
  }%
  \edef\GPT@temp{\GPT@int\GPT@frac}%
  \ifx\GPT@temp\@empty
    \def#1{0}%
  \else
    \edef#1{%
      \GPT@sign
      \GPT@int
      \ifx\GPT@frac\@empty
      \else
        .\GPT@frac
      \fi
    }%
  \fi
}
\def\GPT@ZapPlus#1+#2\@nil{%
  #1%
  \ifx\@empty#2\@empty
    \expandafter\@gobble
  \else
    \expandafter\@firstofone
  \fi
  {%
    \GPT@ZapPlus#2\@nil
  }%
}
\def\GPT@ZapMinusMinus#1--#2\@nil{%
  #1%
  \ifx\@empty#2\@empty
    \expandafter\@gobble
  \else
    \expandafter\@firstofone
  \fi
  {%
    \GPT@ZapMinusMinus#2\@nil
  }%
}
\def\GPT@Split#1.#2.#3\@nil{%
  \def\GPT@int{#1}%
  \ifx\@empty#2\@empty
    \let\GPT@frac\@empty
  \else
    \def\GPT@frac{#2}%
  \fi
}
\def\GPT@Reverse#1#2#3\@nil{%
  \ifx\@empty#3\@empty
    #2#1%
    \expandafter\@gobble
  \else
    \expandafter\@firstofone
  \fi
  {%
    \GPT@Reverse{#2#1}#3\@nil
  }%
}
\def\GPT@ZapLeadingZeros#1{%
  \ifx0#1%
    \expandafter\GPT@ZapLeadingZeros
  \else
    #1%
  \fi
}
\def\Grot@start{%
  \GPT@NormalizeNumber\Grot@sin
  \GPT@NormalizeNumber\Grot@cos
  \edef\GPT@temp{%
    \Grot@cos\GPT@space\Grot@sin\GPT@space
    \if-\Grot@sin
    \else
      \ifx\Grot@sin\GPT@Zero
        \GPT@Zero
      \else
        -\Grot@sin
      \fi
    \fi
    \GPT@space\Grot@cos
  }%
  \ifx\GPT@temp\GPT@MatrixIdentity
    \def\Grot@end{}%
  \else
    \pdfsave
    \pdfsetmatrix{\GPT@temp}%
    \wd\z@\z@
  \fi
}
\def\Grot@end{\pdfrestore}

\else
\fi
\fi


%end package
\catcode`@=12

% a plain tex document

aaa \rotatebox{45}{use La\TeX} bbb

\bye
2
  • I think that while this gives working code it doesn't explain what's the mechanism...? (assume you're happy with computing sin and cos "by hand" etc. what would the code be? Probably using pdfrestore pdfsave pdfsetmatrix?)
    – user202729
    Nov 17, 2022 at 14:38
  • 1
    @user202729 all that code is fully documented in the packages specified in comments. texdoc trig for example explains the trig calculation. Nov 17, 2022 at 14:41
3

I thing that most elementary implementation of rotating can be found in OPmac macros. It is based on \pdfsetmatrix, \pdfsave, \pdfrestore from pdftex but other engines can define these commands very simply, see opmac-xetex.tex file, for example.

The main problem is, ta we have to use \pdfsetmatrix{cos\alpha sin\alpha -sin\alpha cos\alpha}, it means that we must to implement a simple calculation of cos, sin, which is not present in the classical TeX. The implementation in the following OPmac macros does an interpolation of value of these functions using table of the values.

\newdimen\tmpdim \newcount\tmpnum

\def\pdfscale#1#2{\pdfsetmatrix{#1 0 0 #2}}

\def\pdfrotate#1{\tmpdim=#1pt
   \ifdim\tmpdim=0pt
   \else \ifdim\tmpdim=90pt \pdfsetmatrix{0 1 -1 0}%
         \else \edef\tmp{#1}\expandafter\pdfrotateA\tmp..\relax
   \fi   \fi
}
\def\pdfrotateA #1.#2.#3\relax{%
   \def\tmp##1.##2\relax {##1}%
   \tmpnum=\expandafter \tmp \the\tmpdim \relax % round
   \ifdim\tmpdim>0pt \def\tmpa{}\else\def\tmpa{-}\fi % save -
   \loop \ifnum\tmpnum<0 \advance\tmpnum by360 \repeat
   \loop \ifnum\tmpnum>360 \advance\tmpnum by-360 \repeat
   \loop \ifnum\tmpnum>90 \pdfrotate{90}\advance\tmpnum by-90 \repeat
   \ifnum\tmpnum=90 \pdfrotate{90}\else
      \ifnum\tmpnum>44 \pdfsetmatrix{.7071 .7071 -.7071 .7071}%
                       \advance\tmpnum by-45 \fi
      \ifnum\tmpnum>22 \pdfsetmatrix{.9272 .3746 -.3746 .9272}%
                       \advance\tmpnum by-22 \fi
      \ifnum\tmpnum>0
         \pdfsetmatrix{\smallcos \smallsin -\smallsin \smallcos}%
   \fi\fi
   \if$#2$\else % fraction part
      \tmpdim=.01745329pt % \pi/180
      \tmpdim=.#2\tmpdim  %
      \edef\tmp{\expandafter\ignorept\the\tmpdim\space}%
      \ifx\tmpa\empty \pdfsetmatrix{1 \tmp -\tmp 1}%
      \else           \pdfsetmatrix{1 -\tmp \tmp 1}%
   \fi\fi
}
\def\smallcos{.\ifcase\tmpnum \or9998\or9994\or9986\or9976\or9962\or9945\or
  9925\or9903\or9877\or9848\or9816\or9781\or9744\or9703\or9659\or9613\or
  9563\or9511\or9455\or9397\or9336\or9272\fi\space}
\def\smallsin{.\ifcase\tmpnum 0\or0175\or0359\or0523\or0698\or0872\or1045\or
  1219\or1391\or1564\or1736\or1908\or2079\or2250\or2419\or2588\or2756\or
  2924\or309\or3256\or342\or3584\or3746\fi\space}

Test: \pdfsave\pdfrotate{35}\rlap{don't use La\TeX}\pdfrestore

\bye

If you find something more straightforward, please, show it.

0

You must log in to answer this question.