% This package is covered by the LaTeX project public license % (see \url{http://www.latex-project.org/lppl.txt}). % Please report bugs to the author (Michael Palmer, mpalmer@uwaterloo.ca) % modifications since 1.1: % - added second optional argument to \note (tikz code) % - expunged package ifthen, since we require etoolbox anyway % - added package name to default pdfcreator string % - added 'important' option for applying different note formatting % - added 'staggered' option to prevent overlapping placement of notes % - added 'scaletopage' option to pick a page other than the first one % in case that one has an atypical size, as sometimes happens. % - added \pagegrid user macro to draw a grid of helplines over top % the included source page. % In the docs, we should tell the users that the page environment is, in fact, % a tikzpicture - so they can use nodes etc inside it. Maybe we should % specify a relative unit for the x axis also? yes, we do now. we also shift % the scope, so that the origin is always at the bottom left of the source page. % we also need to add to the docs that the leftnotes env can stay - you don't % need to remove it when you set twocolumn to false. \NeedsTeXFormat{LaTeX2e}[1999/12/01] \ProvidesPackage{pdfreview}[2019/02/22 v1.20] \RequirePackage{ adjustbox, calc, environ, etoolbox, fp, graphicx, kvoptions, tikz, twoopt, xstring } \usetikzlibrary{calc,arrows,positioning} % improve file name handling \RequirePackage[extendedchars,space]{grffile} % provide \sout (strikeout) \RequirePackage[normalem]{ulem} \SetupKeyvalOptions{% family=annotation, prefix=prv@, } \DeclareBoolOption[true]{grid} \DeclareBoolOption[false]{twocolumn} \DeclareBoolOption[false]{notenumbers} \DeclareBoolOption[true]{inline} \DeclareBoolOption[false]{withnotesonly} \DeclareBoolOption[false]{staggered} % \DeclareStringOption[1]{scaletopage} \DeclareStringOption[black!30]{gridcolor} \DeclareStringOption[0.75]{bodywidth} \DeclareStringOption[{1cm 1cm 1cm 1cm}]{trim} \DeclareStringOption[0cm]{trimshift} \DeclareStringOption[0.5pt]{bodyframe} \DeclareStringOption[noname]{sourcedoc} \DeclareStringOption[0]{pageoffset} \DeclareStringOption[c]{alignnotes} \DeclareStringOption[yellow]{notesbg} \DeclareStringOption[red]{important} \DeclareStringOption[black]{notesframe} \DeclareStringOption[3pt]{notesep} \DeclareStringOption[footnotesize]{fontsize} \DeclareStringOption[100]{maxscale} \DeclareStringOption[2.5cm]{insertpagemargin} \ProcessKeyvalOptions* % break up trim options - passing raw kvoptions input through to adjustbox fails. % count spaces \StrCount{\prv@trim}{ }[\prv@trimspaces] \typeout{trim option: \prv@trim} \typeout{trim spaces: \prv@trimspaces} % \ifstrequal from etoolbox doesn't work here \IfEq{\prv@trimspaces}{0}% % no spaces - trim all four sides the same {\edef\prv@trimleft{\prv@trim}% \edef\prv@trimright{\prv@trim}% \edef\prv@trimtop{\prv@trim}% \edef\prv@trimbottom{\prv@trim}% }{\IfEq{\prv@trimspaces}{1}% % one space - apply separate trimming to h and v {\StrCut{\prv@trim}{ }{\prv@trimh}{\prv@trimv}% \edef\prv@trimleft{\prv@trimh}% \edef\prv@trimright{\prv@trimh}% \edef\prv@trimtop{\prv@trimv}% \edef\prv@trimbottom{\prv@trimv}} {% assume it's different parameters for all four sides \StrCut{\prv@trim}{ }{\prv@trimleft}{\prv@trimtwo}% \StrCut{\prv@trimtwo}{ }{\prv@trimbottom}{\prv@trimthree}% \StrCut{\prv@trimthree}{ }{\prv@trimright}{\prv@trimtop}}} \newcommand{\resettrim}[1][0pt]{% may be needed for figure pages \edef\prv@trimleft{#1}% \edef\prv@trimright{#1}% \edef\prv@trimtop{#1}% \edef\prv@trimbottom{#1}% } \newcommand{\sourcedoc}{\prv@sourcedoc} % control the split between the included page and the notes pane \newlength{\prv@noteswidth} \newlength{\prv@sourcebodywidth} \newlength{\prv@bodyoffset} \newlength{\prv@notesxoffset} \newlength{\prv@bodypadding} % a length for keeping track of bottom of the last note, % to guard against vertical overlap \newlength{\prv@currentnotebottom} \newlength{\prv@requestedbottom} \newlength{\prv@workingbottom} \newlength{\prv@notesboxheight} \newcommand{\prv@anchor}{west} % empty space between body and notes \setlength{\prv@bodypadding}{\prv@notesep} \newlength{\prv@bodyframewidth} \setlength{\prv@bodyframewidth}{\prv@bodyframe} \newcommand{\prv@notesfont}{\csname \prv@fontsize \endcsname} \newsavebox{\prv@charbox} \sbox{\prv@charbox}{\prv@notesfont\phantom{x}\strut} \newlength{\prv@xsep} \setlength{\prv@xsep}{0.75\wd\prv@charbox} \newlength{\prv@helpnumwidth} \setlength{\prv@helpnumwidth}{\widthof{\prv@notesfont$\prv@maxscale$}+2\prv@xsep} \RequirePackage[hidelinks]{hyperref} \hypersetup{pdfcreator={LaTeX with the pdfreview package}} \RequirePackage{bookmark} \newlength{\prv@notestextwidth} \newlength{\prv@pageheight} \newlength{\prv@unitheight} \newlength{\prv@unitwidth} \newlength{\prv@currentheight} \newsavebox{\prv@pagebox} \newsavebox{\prv@gridboxright} \newsavebox{\prv@gridboxleft} % global setlength - from https://tex.stackexchange.com/questions/406015/ \gdef\gsetlength#1#2{% \begingroup \setlength\skip@{#2} \global#1=\skip@ \endgroup } \tikzset{ notesfont/.style={ % used for both notes and help line numbers font=\prv@notesfont }, % sticky/.style={ % set font and graphics, but not size or anchor notesfont, % operation will shift the whole page up. draw=\prv@notesframe, fill=\prv@notesbg, rounded corners=0.5pt, inner xsep=\prv@xsep, inner ysep=0.4\ht\prv@charbox, outer ysep=0.5pt, outer xsep=0pt, }, % notes/.style={ sticky, anchor=south west,% 't has to be this way, otherwise, the sbox text width=\prv@notestextwidth, text height=\ht\prv@charbox, alias=NN, append after command={ \pgfextra{ % calculate and globally save node height % simplified for height only, from Alain Matthes answer % to https://tex.stackexchange.com/questions/38473/ % the reason for this is that within tikzpicture the % \ht operator doesn't work on saveboxes for some reason. % so we are forced to extract the height from the node. \coordinate (A) at (NN.south); \coordinate (B) at (NN.north); \pgfpointdiff{\pgfpointanchor{A}{center}}% {\pgfpointanchor{B}{center}}% \pgfmathsetlength{\global\prv@notesboxheight}{\pgf@y} } } }, % important/.style={ notes, draw=\prv@important }, % helpnum/.style={ notesfont, draw=none, text=\prv@gridcolor, text width=\prv@helpnumwidth, align=center, inner xsep=0pt, inner ysep=-1em, % hack to squash scale y offset caused by node text }, % grid/.style={ draw=\prv@gridcolor, very thin, }, % boxnode/.style={ inner sep=0pt, outer sep=0pt, anchor=south west, draw=none }, % strutline/.style={ draw=white, line width=0pt }, % hypertarget/.style={ draw=none, inner sep=0pt, outer sep=0pt, alias=targetnode, append after command={ node[draw=none,above of=targetnode,yshift=\prv@notesboxheight] {\hypertarget{#1}{}} } }, % hyperlink/.style={ notes, alias=sourcenode, append after command={ let \p1=(sourcenode.north west), \p2=(sourcenode.south east), \n1={\x2-\x1}, \n2={\y1-\y2} in node [inner sep=0pt, outer sep=0pt,anchor=north west,at=(\p1)] {\hyperlink{#1}{\phantom{\rule{\n1}{\n2}}}} } }, } \newcommand{\prv@drawgridboxright}{% \ifnumless{\ht\prv@gridboxright}{10}{% \global\sbox{\prv@gridboxright}{% \begin{tikzpicture}[y=\prv@unitheight,grid]% \foreach \y in {0, 4, ..., \prv@maxscale}{% \begin{scope}[yshift=\y\prv@unitheight]% \draw (0,0) -- ++(\prv@noteswidth-\prv@helpnumwidth,0) node[helpnum,anchor=west]{$\y$}; \ifnumequal{\y}{\prv@maxscale}{}{% \foreach \z/\w in {1/1,2/2.5,3/1}{% \draw (0,\z) -- ++(\w,0); }}% \end{scope} } \end{tikzpicture}}}{}% \usebox{\prv@gridboxright}% } \newcommand{\prv@drawgridboxleft}{% \ifnumless{\ht\prv@gridboxleft}{10}{% \global\sbox{\prv@gridboxleft}{% \begin{tikzpicture}[y=\prv@unitheight,grid,xscale=-1]% \foreach \y in {0, 4, ..., \prv@maxscale}{% \begin{scope}[yshift=\y\prv@unitheight]% \draw (0,0) -- ++(\prv@noteswidth-\prv@helpnumwidth,0) node[helpnum,anchor=east]{$\y$}; \ifnumequal{\y}{\prv@maxscale}{}{% \foreach \z/\w in {1/1,2/2.5,3/1}{% \draw (0,\z) -- ++(\w,0); }}% \end{scope} } \end{tikzpicture}}}{}% \usebox{\prv@gridboxleft}% } \newcounter{prv@offsetpage} \newcommand*{\prv@pagelabel}{} % boolean switch for suppressing output of empty pages \newbool{prv@isempty} \setbool{prv@isempty}{true} % boolean that is set if code executes inside page environment \newbool{prv@inpage} \setbool{prv@inpage}{false} % flag for noteslist \newbool{prv@noteslistempty} \setbool{prv@noteslistempty}{true} \newsavebox{\prv@wrapper} \newlength{\prv@leftshifted} \newlength{\prv@rightshifted} % flag for initial scaling - we scale dimensions to the first included page \newbool{prv@initialscaling} \setbool{prv@initialscaling}{false} \newcommand{\prv@scaledo}{% \gsetlength{\prv@sourcebodywidth}{\prv@bodywidth\textwidth}% \ifprv@twocolumn% \gsetlength{\prv@noteswidth}{0.5\textwidth-0.5\prv@sourcebodywidth-\prv@bodypadding}% \gsetlength{\prv@bodyoffset}{\prv@noteswidth+\prv@bodypadding-\prv@bodyframe}% \else% \gsetlength{\prv@noteswidth}{\textwidth-\prv@sourcebodywidth-\prv@bodypadding}% \gsetlength{\prv@bodyoffset}{0pt}% \fi% % the x offset for the notes is a bit surprising, because the effective % offset gets altered by the savebox mechanism. This value applies to % the notes in the right margin; for the left margin, we override it locally % in the leftnotes environment. \gsetlength{\prv@notesxoffset}{\textwidth-\prv@noteswidth}% \gsetlength{\prv@notestextwidth}{\prv@noteswidth-\prv@helpnumwidth-2\prv@xsep}% \sbox{\prv@pagebox}{% {\setlength{\fboxsep}{0pt}% \adjustbox{clip,trim=\prv@trimleft{} \prv@trimbottom{} \prv@trimright{} \prv@trimtop{},% width=\prv@sourcebodywidth,fbox=\prv@bodyframe}% {\includegraphics[page=\prv@scaletopage]% {\sourcedoc}}% }}% \gsetlength{\prv@pageheight}{\ht\prv@pagebox}% \gsetlength{\prv@unitheight}{\prv@pageheight/\prv@maxscale}% \gsetlength{\prv@unitwidth}{\prv@sourcebodywidth/\prv@maxscale}% \global\setbool{prv@initialscaling}{true} } \NewEnviron{page}[2][]{% \ifbool{prv@initialscaling}% {}% {\prv@scaledo}% use first page to scale internal length parameters \clearpage% % first, lets adjust the horizontal shifting \ifnumodd{#2}% {\setlength{\prv@leftshifted}{\prv@trimleft+\prv@trimshift}% \setlength{\prv@rightshifted}{\prv@trimright-\prv@trimshift}} {\setlength{\prv@leftshifted}{\prv@trimleft-\prv@trimshift}% \setlength{\prv@rightshifted}{\prv@trimright+\prv@trimshift}} %\setlength{\prv@currentnotebottom}{\prv@pageheight}% \pgfmathsetlength{\global\prv@currentnotebottom}{\prv@pageheight}% \setcounter{prv@offsetpage}{#2+\prv@pageoffset}% \ifnumless{\value{prv@offsetpage}}{1}% {\renewcommand*{\prv@pagelabel}{\roman{page}}}% {\renewcommand*{\prv@pagelabel}{\arabic{prv@offsetpage}}}% \ifbool{prv@withnotesonly}{}% {\phantomsection% \bookmark[level=1,page=\arabic{page}]{p. \prv@pagelabel}}% \begin{lrbox}{\prv@wrapper}% \noindent\begin{minipage}[c][\textheight]{\textwidth}% \noindent\begin{center}% \begin{adjustbox}{width=\textwidth}% \begin{tikzpicture}[x=\prv@unitwidth,y=\prv@unitheight,node distance=3pt]% \ifprv@twocolumn \ifprv@grid \node[boxnode] at (0,0){\prv@drawgridboxleft}; \else \draw[strutline](0,0) -- (\prv@noteswidth,0); \fi \fi \ifprv@grid \node[boxnode,anchor=south east] at (\textwidth,0) {\prv@drawgridboxright}; \else \draw[strutline](\textwidth,0) -- ++(-\prv@noteswidth,0); \fi \begin{scope} \setlength{\fboxsep}{0pt} \node[boxnode] at (\prv@bodyoffset,0) {\adjustbox{clip, trim=\prv@leftshifted{} \prv@trimbottom{} \prv@rightshifted{} \prv@trimtop{},% #1,width=\prv@sourcebodywidth,fbox=\prv@bodyframe}% {\includegraphics[page=#2]{\sourcedoc}% }};% \end{scope} % create a scope that overlays with the source page, in both % one- and two-column mode \begin{scope}[xshift=\prv@bodyoffset] % \global\setbool{prv@inpage}{true} \BODY \global\setbool{prv@inpage}{false} % \end{scope} \end{tikzpicture}% \end{adjustbox}% \end{center}% \end{minipage}% \end{lrbox} % \ifboolexpr{bool {prv@isempty} and bool {prv@withnotesonly}}% {\global\deadcycles=0}% {\noindent\usebox{\prv@wrapper}}% \global\setbool{prv@isempty}{true}\ignorespaces } \newenvironment{leftnotes}% locally override the xoffset for the notes. {\ifprv@twocolumn% \setlength{\prv@notesxoffset}{\prv@helpnumwidth}% \fi% % allow notes to start from the top again in staggered mode % simple \setlength fails here, but \pgfmathsetlength works \pgfmathsetlength{\global\prv@currentnotebottom}{\prv@pageheight}} % {\pgfmathsetlength{\global\prv@currentnotebottom}{\prv@pageheight}} % a matching no-op environment for the bureaucratically inclined \newenvironment{rightnotes}{}{} % use an insertpage environment for putting out the list of notes if requested \newcommand{\prv@noteslist}{\begin{listofnotes}\begin{enumerate}} \newcounter{note} \newsavebox{\prv@notesbox} \newcommand{\prv@calcbottom}[2]{% % calculate bottom y to place next note on % arguments: requested y pos, alignment. All other variables are global % % scale requested bottom position to source page height \FPdiv{\prv@bottomratio}{#1}{\prv@maxscale}% \setlength{\prv@requestedbottom}{\prv@bottomratio\prv@pageheight}% % correct requested bottom position for alignment \ifstrequal{#2}{b}% {}% {\ifstrequal{#2}{c}% {\addtolength{\prv@requestedbottom}{-0.5\prv@notesboxheight}}% {\addtolength{\prv@requestedbottom}{-\prv@notesboxheight}}}% % adjust actual position to avoid overlap where needed. It seems we need two % steps, since pgf is needed to make it stick globally, but it doesn't take % the \minof operator. Oooh the manifold joys of LaTeX. \ifprv@staggered% \setlength{\prv@workingbottom}{\prv@currentnotebottom-\prv@notesboxheight} \gsetlength{\prv@currentnotebottom}% {\minof{\prv@requestedbottom}{\prv@workingbottom}} \else% \gsetlength{\prv@currentnotebottom}{\prv@requestedbottom}% \fi% } % draw a grid of help lines over the source page. We don't % use this as a global option, and the optional argument % to the page environment is already taken; therefore, it % just goes into a user macro. \newcommandtwoopt{\pagegrid}[2][][10]{% \foreach \x in {0,#2,...,100}{ \draw[very thin,\prv@gridcolor,#1](0,\x) -- (100,\x); \draw[very thin,\prv@gridcolor,#1](\x,0) -- (\x,100); }} \newcommand{\prv@importantitem}{\bfseries}% \newcommandtwoopt{\note}[4][\prv@alignnotes][]{% \ifbool{prv@inpage}{% \ifbool{prv@isempty}% {% \ifbool{prv@withnotesonly}% {\phantomsection% \bookmark[level=1,page=\arabic{page}]{p. \prv@pagelabel}}{}% \global\setbool{prv@isempty}{false}% }% {}% \stepcounter{note}% % \begin{scope}[xshift=-\prv@bodyoffset] \ifprv@inline \sbox\prv@notesbox{\node[notes,#2] {% \vspace{-\baselineskip}\newline% hack to force enough height for tabulars \raggedright\setlength{\parindent}{1em}\noindent% \ifprv@notenumbers\arabic{note})\ \fi% #4 };} \prv@calcbottom{#3}{#1} \node[hypertarget=note\arabic{note}] at (\prv@notesxoffset,\prv@currentnotebottom){% \bookmark[level=2,dest=note\arabic{note}]{Note \arabic{note}}\usebox{\prv@notesbox}}; \else \sbox\prv@notesbox{\node[notes,#2]{12345.};} \prv@calcbottom{#3}{#1} \node[hypertarget=note\arabic{note},hyperlink=noteitem\arabic{note}] at (\prv@notesxoffset,\prv@currentnotebottom){% \bookmark[level=2,dest=note\arabic{note}]{Note \arabic{note}}% \raggedright\setlength{\parindent}{1em}\noindent% \global\setbool{prv@noteslistempty}{false} \IfSubStr{#2}{important}% {\g@addto@macro\prv@noteslist{\item% \hypertarget{noteitem\arabic{enumi}}{}{\prv@importantitem{}#4}% \hyperlink{note\arabic{enumi}}{~$\uparrow$}}}% {\g@addto@macro\prv@noteslist{\item% \hypertarget{noteitem\arabic{enumi}}{}{#4}% \hyperlink{note\arabic{enumi}}{~$\uparrow$}}}% % \arabic{note}. }; \fi \end{scope}} {\PackageWarning{pdfreview}{Not typesetting note outside of page environment}}% }% end \note \newcommand{\cnote}[3][]{\note[c][#1]{#2}{#3}} \newcommand{\bnote}[3][]{\note[b][#1]{#2}{#3}} \newcommand{\tnote}[3][]{\note[t][#1]{#2}{#3}} \newcommandtwoopt{\remark}[3][100][c]{\note[#2]{#1}{#3}} % provide for extra pages with text \newenvironment{insertpage}[1][General comments]%{}{}% {\clearpage% \phantomsection\addcontentsline{toc}{section}{#1}% \newgeometry{margin=\prv@insertpagemargin} \section*{#1}} % {\clearpage\aftergroup\restoregeometry} % noteslist is like insertpage, except that we don't restore the geometry % this will keep the modified margins in place for other appendices like % bibliographies \newenvironment{listofnotes}% {\clearpage% \phantomsection\addcontentsline{toc}{section}{List of notes}% \newgeometry{margin=\prv@insertpagemargin} \section*{List of notes}} % {\clearpage} \AtBeginDocument{% divide up the page. Use package geometry if not yet loaded by user. \@ifpackageloaded{geometry}{}% use of both package options and \newgeometry is intentional. {\RequirePackage[hmargin=0.25cm,vmargin=2.5cm]{geometry}% \newgeometry{hmargin=0.25cm,vmargin=2.5cm}}% }% \AtEndDocument{% \ifprv@inline% do nothing \else% close the notes list and print it out \ifbool{prv@noteslistempty}{}{% \g@addto@macro\prv@noteslist{\end{enumerate}\end{listofnotes}} \clearpage \prv@noteslist } \fi} \newcommand{\ssout}[1]{\,\textbf{#1}\,}