37

I remember long time ago, seeing PDF documents (such as online PDF versions of newspapers) that had hypertext boxes around paragraphs, such that when you clicked inside those boxes the surrounded paragraph will be zoomed in to exactly that paragraph in the Acrobat Reader window. I am not sure what tool was used to create those PDFs, and unfortunately I can't find one example at the moment.

Is it possible to reproduce such zoom-on-click feature with some pdflatex code? To repeat: the requested feature is to create hypertext boxes around paragraphs or figures that when clicked the PDF viewer would zoom in into that region.

This feature can be useful in PDFs with lots of structure in a given page, such as small paragraphs or figures or figure captions scattered around the page.

(Beamer has a "zoom in" feature (\framezoom) but it is not the same thing because that creates a new page with the zoomed region, and I just want to force the PDF reader to enclose the area in the same page by only zooming. I am also aware of marquee zoom in Acrobat but it is not what I am looking for because it is not aware of the content.)

alfC
  • 14,350

3 Answers3

39

The following code (for use with pdfLaTeX) defines the command \zoombox[box line width]{contents}. It creates a click-to-zoom box for fitting the second argument 'contents' into the AdobeReader window. A dotted line is drawn around the box if the optional 'box line width' is >0.

EDIT: The code has been optimized for presentation PDFs (such as beamer generated ones) looked at in full screen mode. The zoom-to box is now centred on the screen when clicked, as requested by @alfC.

\documentclass{beamer}

\usepackage{graphicx}
\usepackage{mwe}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  \zoombox[box line width]{contents}
%
%  optimized version for beamer: in full screen, zoom boxes are centred
%  in the viewer; useable with any documenclass
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\makeatletter
\newsavebox\zb@x
\newcounter{z@@m}
\usepackage{calc}
\newdimen\B@r\newdimen\P@r
\newdimen\@zw\newdimen\@zh\newdimen\@zd

\newcommand{\zoombox}[2][0]{%
  \leavevmode%
  \sbox\zb@x{#2}%
  \setlength\B@r{1pt*\ratio{\wd\zb@x}{\ht\zb@x+\dp\zb@x}}%
  \setlength\P@r{1pt*\ratio{\paperwidth}{\paperheight}}%
  \ifdim\B@r>\P@r\relax%
    \setlength\@zw{\wd\zb@x}\setlength\@zh{\@zw*\ratio{\paperheight}{\paperwidth}}%
    \setlength\@zd{(\@zh-\ht\zb@x-\dp\zb@x)*\real{0.5}+\dp\zb@x}%
    \setlength\@zh{\@zh-\@zd}%
  \else%
    \setlength\@zh{\ht\zb@x+\dp\zb@x}%
    \setlength\@zw{\@zh*\ratio{\paperwidth}{\paperheight}}%
    \setlength\@zh{\ht\zb@x}\setlength\@zd{\dp\zb@x}%
  \fi%
  \makebox[0pt][l]{\makebox[\wd\zb@x][c]{\makebox[\@zw][l]{%
    \pdfdest name {zbfs\thez@@m} fitr
      width  \@zw\space
      height \@zh\space
      depth  \@zd\space
  }}}%
  \pdfdest name {zb\thez@@m} fitr
    width  \wd\zb@x\space
    height \ht\zb@x\space
    depth  \dp\zb@x\space
  \immediate\pdfannot 
    width  \wd\zb@x\space
    height \ht\zb@x\space
    depth  \dp\zb@x\space
  {%
    /Subtype/Link/H/N
    /Border [0 0 #1 [1 2]]
    /A <<
      /S/JavaScript
      /JS (
        if(typeof(zoomed)=='undefined'||!zoomed){
          var lastView=this.viewState;
          if(app.fs.isFullScreen) this.gotoNamedDest('zbfs\thez@@m');
          else this.gotoNamedDest('zb\thez@@m');
          zoomed=true;
        }else{
          this.viewState=lastView;
          zoomed=false;
        }
      )
    >>
  }%
  \usebox{\zb@x}%
  \stepcounter{z@@m}%
} 
\makeatother
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{document}
\begin{frame}
\zoombox{\includegraphics[width=1cm]{example-image-a}}
\zoombox{\includegraphics[width=1cm]{example-image-b}}
\zoombox{\includegraphics[width=1cm]{example-image-c}}
\zoombox{\includegraphics[height=1cm]{example-image-golden}}
\zoombox{\includegraphics[width=1cm]{example-image-golden-upright}}
\end{frame}
\end{document}

The second example defines pairs of commands for marking opposing corners (lower left & upper right .OR. upper left & lower right) of a zoom box, and commands for marking text sequences/entire paragraphs.

\documentclass{article}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\makeatletter
\InputIfFileExists{\jobname.zom}{}{}                                                                         
\newwrite\zoomdat
\immediate\openout\zoomdat=\jobname.zom                                                                      
\newcommand{\startzoombox}[2][0]{%                                                                           
  \leavevmode%                                                                                               
  \pdfsavepos%
  \protected@write\zoomdat{}{%
  \string\expandafter\string\def\string\csname\space zb#2.ulx\string\endcsname{%                             
    \noexpand\number\pdflastxpos}%
  \string\expandafter\string\def\string\csname\space zb#2.uly\string\endcsname{%                             
    \noexpand\number\pdflastypos}%                                                                           
  }%
  \ifcsname zb#2.ulx\endcsname\ifcsname zb#2.lrx\endcsname%
    \edef\zoomwd{\dimexpr \csname zb#2.lrx\endcsname sp- \csname zb#2.ulx\endcsname sp\relax}%               
    \edef\zoomdp{\dimexpr \csname zb#2.uly\endcsname sp- \csname zb#2.lry\endcsname sp\relax}%               
    \pdfdest name {zb#2.in} fitr                                                                             
      width \zoomwd                                                                                          
      height 0pt
      depth \zoomdp
    \immediate\pdfannot                                                                                      
      width \zoomwd                                                                                          
      height 0pt
      depth \zoomdp                                                                                          
    {%
      /Subtype/Link/H/N 
      /Border [0 0 1 [1 2]]                                                                                  
      /A <<
        /S/JavaScript                                                                                        
        /JS (
          if(typeof(zoomed)=='undefined'||!zoomed){                                                          
            var lastView=this.viewState;                                                                     
            zoomed=true;
            this.gotoNamedDest('zb#2.in');                                                                   
          }else{
            this.viewState=lastView;                                                                         
            zoomed=false;                                                                                    
          }                                                                                                  
        )                                                                                                    
      >>                                                                                                     
    }%
  \fi\fi%                                                                                                    
}
\def\stopzoombox#1{%\leavevmode%                                                                             
  \leavevmode%                                                                                               
  \pdfsavepos%
  \protected@write\zoomdat{}{%
  \string\expandafter\string\def\string\csname\space zb#1.lrx\string\endcsname{%                             
    \noexpand\number\pdflastxpos}%
  \string\expandafter\string\def\string\csname\space zb#1.lry\string\endcsname{%                             
    \noexpand\number\pdflastypos}%                                                                           
  }%                                                                                                         
}
\def\startzoom{%
  \stepcounter{@zb@id}%
  \xdef\@lblStack{\the@zb@id.\@lblStack}%
  \@ifstar\@startzoomstar\@startzoom%                                                                        
}

\newcommand{\@startzoom}[1][0]{%
  \raisebox{\baselineskip}[0pt][0pt]{\startzoombox[#1]{.\the@zb@id}}%                                                 
}
\newcommand{\@startzoomstar}[1][0]{%
  \makebox[0pt][r]{\raisebox{\baselineskip}[0pt][0pt]{\startzoombox[#1]{.\the@zb@id}}%                                
  \hspace{\parindent}}%                                                                                      
}
\def\stopzoom{%
  \@ifstar\@stopzoomstar\@stopzoom%                                                                          
}
\def\@stopzoom{%
  \@popStack\@lblStack%
  \raisebox{-1ex}[0pt][0pt]{\stopzoombox{.\@lblCur}}%
  \xspace%
}
\def\@stopzoomstar{%
  \@popStack\@lblStack%
  \hfill\raisebox{-1ex}[0pt][0pt]{\stopzoombox{.\@lblCur}}%
  \xspace%
}
\newcounter{@zb@id}
\def\@lblStack{}
\def\@popStack#1{\expandafter\@@popStack#1\nil}
\def\@@popStack#1.#2\nil{\gdef\@lblCur{#1}\gdef\@lblStack{#2}}
\RequirePackage{xspace}
\makeatother
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{document}
\section{Paragraph zooming}
\startzoom[1]First paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first \startzoom[1]\emph{Do not miss this one.}\stopzoom First paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph first paragraph.\stopzoom*

\startzoom*[1]Second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph second paragraph.\stopzoom*
\end{document}

The two commands for marking the corners of a zoom area are

\startzoombox[line width]{label}

and

\stopzoombox{label}

They are meant to be used in picture-making environments, such as pspicture or tikzpicture. Associated command pairs must be identified by unique labels.

For marking text and paragraphs the following commands have been provided:

\startzoom[lwidth]
\startzoom*[lwidth]
\stopzoom
\stopzoom*

The starred versions insert horizontal space (negative \parindent on first line, \hfill at the end of the paragraph).

Zoom areas may be nested. However, smaller ones should be placed on top of bigger ones (i.e. to appear later in the code), in order not to be obscured.


Note that at least two pdflatex runs are necessary.


EDIT:

A dvipdfmx/XeLaTeX version of the \zoombox macro is given in: How do I include a click-to-zoom thumbnail picture in a non-beamer document?

AlexG
  • 54,894
  • 1
    Amazing results!, it works. You should write a package based on this. 1) One defect: For text it doesn't work as I expected because for a long text the text doesn't flow with the page, it only continues beyond the margin. How can I avoid that? (minipage with column width?) 2) One small improvement: How can I change the color of the dotted box line? (e.g. to the more standard blue) (I looked in the PDF Reference manual and couldn't find a way). 3) Note for others: as expected, the feature doesn't work in xpdf or Evince. – alfC Mar 01 '11 at 08:57
  • 1
    (1)results from the fact, that content is stored in a \savebox first in order to be measured its dimensions for later sizing the zoom area. Saveboxes are lr-boxes, that is, they behave like \hbox (TeX) or \makebox (LaTeX). In order to get whole paragraphs, you could either put the text in a \parbox or minipage, as you suggested, or use the \startzoombox[line width]{label} \stopzoombox{label} commands to mark opposing corners of the zoom area. – AlexG Mar 01 '11 at 09:53
  • 1
    (2) To get the link border colour as defined by hyperref, insert /C [\@linkbordercolor] before /Border ... in the above code. The preamble code between the comment lines must be surrounded by \makeatletter and \makeatother and don't forget to \usepackage{hyperref} – AlexG Mar 01 '11 at 10:19
  • (1)(a)the \startzoombox strategy doesn't work well because the coordinates not always enclose the entire paragraph but a subrectangle upto the last full stop. (b)I guess if we convert \start|stopzoombox into an environment we will have something less flexible but with automatic labels (c)in order to make the paragraph look right with minipage I had to add some code to force a consistent indentation (d)and anyway paragraph inside minipage parbox are not broken into pages. The above concerns are just a note for others, they are not important to me because I wntd this for figures and formulas. – alfC Mar 01 '11 at 19:52
  • (2) Looks very professional! – alfC Mar 01 '11 at 19:52
  • 1
    1a) You could put \hfill between the last full stop and \stopzoombox. This pushes the latter to the right text margin. – AlexG Mar 02 '11 at 15:53
  • 1a) I guess then there is another trick also to have the left upper corner in the correct place (it starts with the margin and below the first letter of the paragraph, so the first line and and the left most part of the paragraph are outside the zoom box) – alfC Mar 02 '11 at 21:03
  • new extra question, (3) can PDF directives handle opacity in the colores lines? That is to improve over /C [\@linkbordercolor] to give opacity to the dotted line. – alfC Mar 02 '11 at 21:05
  • 1a) I modified the second code block to implement four macros for zooming short text sequences or complete paragraphs: \startzoom[lwidth]{label}, \startzoom*[lwidth]{label}, \stopzoom{label}, \stopzoom*{label}. The starred versions insert horizontal space (negative \parindent, \hfill) for marking normal paragraphs. – AlexG Mar 03 '11 at 08:57
  • (3) According to the PDF-Spec, transparent link borders are not provided, only solid vs dash lines. The dash/gap lengths can be adjusted as in the example. – AlexG Mar 03 '11 at 11:09
  • @AlexanderGrahn I tried the code but when I click, the entire page is magnified instead of the box. Also, when viewed on TeXWork viewer or Adobe Reader the results are different. This feature could be very useful in accessibility. – Maesumi Mar 21 '12 at 00:53
  • @Maesumi: That's the way it works. The entire page is magnified and the view port (the Reader's document window) is repositioned such that the zoom box tightly fits into it. I have only tested with Adobe Reader. There is some JavaScript involved, which will likely be executed correctly only in Adobe Reader. – AlexG Mar 21 '12 at 12:25
  • @AlexanderGrahn Thanks. Is it supposed to magnify to the maximum size which fits the window, or is it supposed to go in steps of magnification (that is what appears different in different viewers)? By the way there is a similar feature in MathJax: when a formula is clicked/hovered its content gets separately magnified (covering the surrounding areas). I guess the working assumption there is that people with low vision have a harder time with formulas so only a portion of display is magnified. However MathJax is browser/HTML-based. Any controlled magnification in PDF is a nice addition. Thanks. – Maesumi Mar 21 '12 at 14:25
  • @AlexanderGrahn On Adobe, upon second clicking, the magnification goes back to some default (which results in very small size for text), I guess to vertically fit the document in window, is there a way of making the document go back to whatever magnification was being used before the box was clicked? – Maesumi Mar 22 '12 at 13:53
  • @Maesumi:There is already an AR built-in tool called Loupe, which behaves more or less like what you have described in your comment -->link – AlexG Mar 23 '12 at 09:31
  • @AlexanderGrahn Concerning ZoomBox: Whatever the original zoom level of document after a click on zoombox and a second click the document goes to a default of "Zoom to Page Level". For example I set the zoom at 100%, click on box, it goes to say 2500%, click again it goes to something like 50%. The final setting is irrespective of the initial zoom level. Is there a simple way of defining default zoom on AR or in zoombox code? – Maesumi Mar 25 '12 at 15:14
  • @AlexG, hi again, Acrobat has an option called "Use smooth zooming" that allows to animate when changing the zoom level with the usual Acrobat buttons. However this doesn't work for your zoom boxes. Is there something that can be done to the pdfannot to recover this animation feature when changing zoom levels via zoom boxes? – alfC Apr 09 '13 at 05:30
  • @alfC: I had tried to make use of this smooth zooming feature, but it didn't seem to be possible. The GoTo action and zooming via +/- buttons in the GUI are unrelated. – AlexG Apr 09 '13 at 06:44
  • @AlexG, I have been using your code a lot, is it somehow possible to ensure (as far as possible) that the zoomed area will be centered in the window? In general only the upper left corner is aligned to the visible area of the window. This make frames with large aspect ratios to appear in the edges (upper or left) of the visible area instead of the center of the window and it is very distracting since things are zoomed in in a way that is not intuitive to the eye. – alfC Sep 12 '13 at 04:58
  • 1
    @alfC I am afraid it is not possible. I tried the scroll JavaScript method, but instead of centring the given position (the centre coordinates of the zoom-to region) within the viewer window (as advertised by the Acrobat JavaScript specification), it is moved to the upper left corner of the viewer window, only leaving visible the lower right quarter of the zoom-to region, which is clearly not what is expected. – AlexG Sep 12 '13 at 08:36
  • 1
    @alfC: In presentation pdfs, boxes are centred in full screen mode when clicked. See my edit. – AlexG Sep 13 '13 at 14:16
  • @AlexG, work (Acrobat 9.4.1 Linux), Why make it only works in fullscreen mode? Also the boxes are only vertically centered, not horizontally. And horizontally some space is wasted while zooming. – alfC Sep 13 '13 at 14:50
  • 1
    @alfC: The {contents} argument is centred within a tightly fitting zoombox (fitr PDF destination) that has the same aspect ratio as the document page. If the beamer document was set-up with the same aspect ratio as the projector or computer screen (here: 4:3), then in full screen, zoom boxes are enlarged to the maximum, while centred on the screen. Perhaps you have a computer screen with an asp ratio other than 4:3. Try \documenclass[169]{beamer} or whatever. – AlexG Sep 13 '13 at 15:13
  • @AlexG, I see, some constants where chosen assuming a certain aspect ratio of the page (not the screen). I tried it in a portrait paper. BTW, the main usage I give to this is that I can write a normal article and then use it as presentation, such that I can zoomin in figures, equations, and paragraphs. So the whole purpose was to avoid the presentation mode. – alfC Sep 13 '13 at 18:46
  • Good news, this works now in Evince 3.14, although only the position is set (the zoom level is not adjusted). – alfC Dec 17 '14 at 01:41
  • @AlexG, do you think there is a way to use either of the zoom commands inside a section title? In such a way that the zoom box takes over the whole width of the line where the section title lives. I tried this \section{\zoombox[1]{Title}} and \section{\zoombox[1]{Title\hfill}} but in both cases the zoom box doesn't extend to the end of the line (up to the right margin). Trying to use the \start*-\stop* versions gave an error when trying to use it inside a section. – alfC Apr 11 '15 at 10:15
  • @alfC Doesn't seem possible. Perhaps, because sectioning commands are so called fragile commands [Lamport, Sect. C.1.3]? – AlexG Apr 15 '15 at 11:26
  • @AlexG, actually, I found the way, using your \start/stop-zoombox, it works as long as there is an "alternative title" set for the section. \section[Title Alternate]{\startzoom*[0]{tag1}Title\stopzoom*{tag1}}. I had to add some additional \hspace to include the section number, so it is still an incomplete hack. – alfC Apr 16 '15 at 06:57
  • @AlexG, do you think it is possible to avoid naming the boxes, and let latex use the last opened box (like a stack). \startunnamedzoom[lwidth] ... \endlastzoom` ? – alfC Jan 26 '16 at 05:09
  • @alfC This is a good suggestion and should indeed be possible. It is quite a while ago that I put this together and my skills heave improved in the meantime. I will try as soon as p. – AlexG Jan 26 '16 at 07:55
  • @alfC: Ok. I did this for \startzoom/ \stopzoom. For \startzoombox/\stopzoombox, however, labels remain mandatory, since the commands may be used in [tikz|ps]picture environments, where their placement does not necessarily follow a stack-like fashion. – AlexG Jan 27 '16 at 12:55
  • @AlexG If I run the run the file by using the beamer then it works fine, but if I change to book, then I got the errors: ! Arithmetic overflow. \calc@next@digit ... \multiply \calc@numerator 10 \calc@Acount \calc@numerat..., any advise? – MadyYuvi Aug 10 '19 at 04:59
  • @MadyYuvi Did you remove \begin{frame} and \end{frame} from the document body when using book? These are commands from beamer. – AlexG Aug 10 '19 at 14:31
3

AFAIK there is no predefined macro or package which does all of that, but the elements you need exist: You can place hyperlinks with zoom windows using the hyperref package. You can get the coordinates required using the zref package(s), especially zref-abspos. These packages are from the same author and play nicely together.

You would need to place the paragraph in its own box. Place a zref marker before and after the paragraph on the left and right site, respectively to get the coordinates of it. Then put the whole box into a \href macro which uses the coordinates as target. See the manual of hyperref for more details especially \href and the FitR (rectangle zoom).

Martin Scharrer
  • 262,582
  • @Scharrer would this allow to also go back to previous zoom level? That is, after FitR is used to enlarge to fit page would for example a second click make document go back to its origin? – Maesumi Apr 05 '12 at 02:59
  • @Maesumi: There should be the possibility to add a clickable item which triggers to go back to the last viewpoint. In Adobe Reader this can be done manually using ALT+ARROW LEFT, so there should be an hyperlink way as well. – Martin Scharrer Apr 05 '12 at 06:42
3

I suspect you're thinking of the PDF "article thread" feature. This is particularly useful for things like newspapers, where the flow of the text is complicated (multiple columns and the page divided into several articles, and even things like "(continued on page 3)". With Adobe reader, when you mouse over the thread, the hand icon changes to a hand-with-arrow, and upon clicking you zoom to the width of the article, and each additional click follows the flow of the article, no matter how nonlinear and complicated that may be.

PDFtex does have the primitives to support article threads: \pdfthread, \pdfstartthread, \pdfendthread, \pdfthreadmargin. But as far as I know the only latex support is rather limited, via the pdfpages, so only useful if you are using pdfpages for some complex imposition.

Lev Bishop
  • 45,462
  • 1
    I wonder if \pdfthread, etc can simplify Alexander's code and (even get rid of the Javascript code.) – alfC Mar 01 '11 at 20:23