Rotation of pgftext does not affect hyperref
Brief outline of the bug
Hello,
firstly, I would like to thank you all for the efforts you have made in maintaining this project.
I have encounted the problem when I used matplotlib pgf backend. The problem is that the hyperref links are not rotated with the \pgftext node.
After a quick research I found out that the problem is pretty common and is known for a long time:
- My first experience with the problem
- Possibly related problem of beamer
- 14 years old report
- 14.5 years old report
- 15 years old report
All of the proposed solutions are based on use of \rotatebox which is rather poor if the exact placement of the node matters. In particular cases of rotation by 90, 180, 270 degrees the \rotatebox is fine with arguments of the text alignment of \pgftext but does not really work as inteded for arbitary degree.
I am aware that this issue is particularly related to the \hyperref package but this is nowdays not really maintained.
Since the problem is present for pretty long time and I believe that this is important, I would like to address this issue to your project. I would like to ask you if you can see any long-term robust solution for the problem or give any advices regarding the problem.
Best wishes
Minimal working example (MWE)
\documentclass{article}
\usepackage{hyperref}
\usepackage{pgfplots}
\begin{document}
\section{Introduction}
\label{text:intro}
\begin{figure}
\centering
\begin{pgfpicture}%
\begin{pgfscope}%
\pgftext[x=0.9in,y=0.5in]{Reference to \ref{text:intro}}
\pgftext[x=0.2in,y=0.2in,rotate=90]{Reference to \ref{text:intro}}
\end{pgfscope}
\end{pgfpicture}
\end{figure}
\end{document}
Reproduces with pdfTeX and LuaTeX, but not XeTeX.
When compiled with pdfTeX,
-
\pgftextfrompgfpackage rotates the box using primitives\pdfliteral; -
\rotateboxfromgraphic(s|x)package rotates the box using primitives\pdfsave,\pdfsetmatrix, and\pdfrestore(see thepdftex.defdriver).
The later 3 primitives are introduced in pdfTeX 1.40.0 (2007) specifically
[...] to save pdfTeX from parsing \pdfliteral contents and to notify pdfTeX about matrix changes to use them in calculating link and anchor positions.
(CTAN announcement for pdfTeX 1.40.0)
\documentclass{article}
\usepackage{hyperref}
\parindent=0pt
\begin{document}
\section{Introduction}\label{text:intro}
\vskip1cm
Emulate \verb|\rotatebox|
\hbox{%
\setbox0=\hbox{Ref to \ref{text:intro}}%
\pdfsave
\pdfsetmatrix{0 1 -1 0}%
\rlap{\copy0}%
% Paired \pdfsave and \pdfrestore must be used at the same place,
% otherwise you got pdftex warning "Misplaced \pdfrestore by (...)".
% See https://tug.org/pipermail/pdftex/2013-April/008858.html
\pdfrestore
}
\vskip1cm
Emulate \verb|\pgftext|
\hbox{%
\pdfliteral{q }%
\pdfliteral{0 1 -1 0 0 0 cm }%
Ref to \ref{text:intro}%
\pdfliteral{Q }%
}
\end{document}
pgf/tikz needs to use \pdfsave and its friends for \pgftext and tikz nodes, in the pdfTeX and LuaTeX drivers.
pgf/tikzneeds to use\pdfsaveand its friends for\pgftextandtikznodes, in the pdfTeX and LuaTeX drivers.
Err, that's not enough if the whole pgfpicture or pgfscope is already rotated.
I don't think we can easily repair this without some major refactoring of the internals and I have burned my fingers a couple of times already touching pgfsys code. (e.g. https://github.com/pgf-tikz/pgf/commit/8e182a4ac2c4cef5e5e0743911e0ffcfcc51b87d, https://github.com/pgf-tikz/pgf/commit/943a0a0c82ca6e9c3a3f730863178d7744e601d6, https://github.com/pgf-tikz/pgf/commit/5492ecdcfc41f27d9fc00a6eaba1eb0791f73ebb)
Reproduces with pdfTeX and LuaTeX, but not XeTeX.
Wow, it does indeed work with XeLaTeX as intended. I have never found this solution to the problem before. Thank you so much for your reserach and efforts! I guess this is a pretty decent workaround for end users.
Maybe this can be closed with 'won't fix' for pdfLatex, and anybody else who want to get this right can just switch to the XeLaTeX.
See also comments in l3backend about which primitives are needed to 'track' annotations.
@josephwright Did you mean the comments for \__kernel_backend_scope_begin:, \__kernel_backend_scope_end:, and \__kernel_backend_matrix:(n|e)?
Try this. It patches \pgfsys@hboxsynced when compiled "with pdfTeX and LuaTeX in direct PDF output mode" (texdoc l3backend-code, sec. 1.2 "LuaTeX and pdfTeX backends").
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{shapes.multipart} % for the \pgfmultipartnode example
\usepackage{hyperref}
\makeatletter
\ifdefined\pdftexversion
% pdftex
\def\pgfsys@save{\pdfsave}
\def\pgfsys@setmatrix{\pdfsetmatrix}
\def\pgfsys@restore{\pdfrestore}
\else\ifdefined\directlua
% luatex
\def\pgfsys@save{\pdfextension save\relax}
\def\pgfsys@setmatrix{\pdfextension setmatrix}
\def\pgfsys@restore{\pdfextension restore\relax}
\fi\fi
\def\pgfsys@hboxsynced@NEW#1{%
{%
\pgfsys@begin@idscope%
\pgfsys@beginscope%
\setbox\pgf@hbox=\hbox{%
\hskip\pgf@pt@x
\raise\pgf@pt@y\hbox{%
\pgf@pt@x=0pt%
\pgf@pt@y=0pt%
%% >>> patch begin
\ifpgf@pt@identity
% original case
\pgflowlevelsynccm
\pgfsys@hbox#1%
\else
\pgfgettransformentries\aa\ab\ba\bb\shiftx\shifty
\pgftransformresetnontranslations
\pgflowlevelsynccm
\pgfsys@save
\pgfsys@setmatrix{\aa\space\ab\space\ba\space\bb}%
\pgfsys@hbox#1%
\pgfsys@restore
\fi
%% <<< patch end
}%
\hss%
}%
\wd\pgf@hbox=0pt%
\ht\pgf@hbox=0pt%
\dp\pgf@hbox=0pt%
\box\pgf@hbox
\pgfsys@endscope%
\pgfsys@end@idscope%
}%
}
%\let\pgfsys@hboxsynced@OLD=\pgfsys@hboxsynced
\ExplSyntaxOn
\sys_ensure_backend: % needed by using \sys_if_output_pdf_p: in preamble
\bool_lazy_and:nnT
{
\bool_lazy_or_p:nn
{ \sys_if_engine_pdftex_p: }
{ \sys_if_engine_luatex_p: }
}
{ \sys_if_output_pdf_p: }
{
\let\pgfsys@hboxsynced=\pgfsys@hboxsynced@NEW
}
\ExplSyntaxOff
\makeatother
\parindent=0pt
\begin{document}
\section{Introduction}\label{text:intro}
\def\drawGrid{%
\begin{pgfscope}
\pgfsetcolor{blue}
\pgfpathgrid{\pgfpointorigin}{\pgfpoint{1cm}{1cm}}
\pgfusepath{draw}
\end{pgfscope}
}
\def\drawBoundingBox{%
\begin{pgfscope}
\pgftransformreset
\pgfsetcolor{gray}
\pgfsetdash{{3pt}{3pt}}{0pt}
\pgfpathrectanglecorners
{\pgfpointanchor{current bounding box}{north east}}
{\pgfpointanchor{current bounding box}{south west}}
\pgfusepath{draw}
\end{pgfscope}
}
\begin{pgfpicture}
\pgfsetbaseline{0cm}
\drawGrid
\pgftext[x=0cm, y=0cm, rotate=90] {Ref to \ref{text:intro}}
\pgftext[x=1cm, y=0cm, rotate=120] {Ref to \ref{text:intro}}
\drawBoundingBox
\end{pgfpicture}
\qquad
\rotatebox[origin=c]{90}{Ref to \ref{text:intro}}
\qquad
\rotatebox[origin=c]{120}{Ref to \ref{text:intro}}
\begin{pgfpicture}
% global transformations
\pgftransformrotate{30}
\pgftransformscale{1.1}
\pgftransformshift{\pgfpoint{10pt}{20pt}}
\pgfsetbaseline{2cm}
\drawGrid
\pgftext[x=1cm, y=1cm, rotate=-90] {Ref to \ref{text:intro}}
\drawBoundingBox
\end{pgfpicture}
\qquad
\rotatebox[origin=c]{-60}{Ref to \ref{text:intro}}
% \pgfnode, \pgfmultipartnode, and tikz nodes
\setbox\pgfnodeparttextbox=\hbox{Ref to}
\setbox\pgfnodepartlowerbox=\hbox{\ref{text:intro}}
\begin{pgfpicture}
% global transformations
\pgftransformrotate{60}
\pgftransformscale{0.8}
\pgftransformyslant{-.3}
\drawGrid
\pgfnode{rectangle}{north}{Ref to \ref{text:intro}}{}{\pgfusepath{stroke}}
\pgftransformshift{\pgfpoint{1cm}{1cm}}
\pgfmultipartnode{circle split}{east}{}{\pgfusepath{stroke}}
\drawBoundingBox
\end{pgfpicture}
\quad
\begin{tikzpicture}[rotate=60, scale=.8, yslant=-.3, nodes={draw, transform shape}]
\draw[blue] (0,0) grid (1,1);
\node[anchor=north] {Ref to \ref{text:intro}};
\node[circle split, anchor=east] at (1,1)
{Ref to\nodepart{lower}\ref{text:intro}};
\draw[reset cm, gray, dashed]
(current bounding box.north east) rectangle
(current bounding box.south west);
\end{tikzpicture}
\qquad
\scalebox{0.8}{\rotatebox{60}{Ref to \ref{text:intro}}}
\end{document}