|
| 1 | +% Annotate LaTeX Equations |
| 2 | +% |
| 3 | +% (c) 2022 by ST John, https://github.com/st--/ |
| 4 | +% Licensed under MIT License |
| 5 | +% |
| 6 | +\NeedsTeXFormat{LaTeX2e}[1994/06/01] |
| 7 | +\ProvidesPackage{annotate-equations} |
| 8 | + [2022/03/29 v0.1.0 easily annotate equations using TikZ] |
| 9 | + |
| 10 | +%%% lualatex compatibility, from https://tex.stackexchange.com/a/351520/171664 |
| 11 | +\RequirePackage{ifluatex} |
| 12 | +\ifluatex |
| 13 | +\RequirePackage{luatex85} |
| 14 | +\RequirePackage{pdftexcmds} |
| 15 | + \makeatletter |
| 16 | + \let\pdfstrcmp\pdf@strcmp |
| 17 | + \let\pdffilemoddate\pdf@filemoddate |
| 18 | + \makeatother |
| 19 | +\fi |
| 20 | +%%% |
| 21 | + |
| 22 | +\RequirePackage{tikz} |
| 23 | +\RequirePackage{xcolor} |
| 24 | + |
| 25 | +\usetikzlibrary{backgrounds} |
| 26 | +\usetikzlibrary{arrows,shapes} |
| 27 | +\usetikzlibrary{tikzmark} % for \tikzmarknode |
| 28 | +\usetikzlibrary{calc} % for computing the midpoint between two nodes, e.g. at ($(p1.north)!0.5!(p2.north)$) |
| 29 | + |
| 30 | + |
| 31 | +%%%%% SETTINGS %%%%% |
| 32 | + |
| 33 | +\newcommand{\eqnhighlightheight}{} % colorbox will shrink to content |
| 34 | +\renewcommand{\eqnhighlightheight}{\mathstrut} % colorbox will always have full height |
| 35 | + |
| 36 | +\newcommand{\eqnhighlightshade}{17} % light |
| 37 | +%\renewcommand{\eqnhighlightshade}{47} % dark |
| 38 | + |
| 39 | +\newcommand{\eqnannotationtext}[1]{\sffamily\footnotesize#1\strut} %%% TODO remove textsf/footnotesize/strut? |
| 40 | + |
| 41 | + |
| 42 | +\providecommand\EAmarkanchor{north} % default set to "above" |
| 43 | +\providecommand\EAwesteast{east} % default set to "right" |
| 44 | +\providecommand\EAlabelanchor{south} % default set to "label above" |
| 45 | +% for pgfkeys, see https://tex.stackexchange.com/a/34318/171664 |
| 46 | +% for no-value keys, see https://tex.stackexchange.com/a/401848/171664 |
| 47 | +\pgfkeys{ |
| 48 | + /eqnannotate/.is family, /eqnannotate, |
| 49 | + above/.code = {\renewcommand\EAmarkanchor{north}}, |
| 50 | + below/.code = {\renewcommand\EAmarkanchor{south}}, |
| 51 | + left/.code = {\renewcommand\EAwesteast{west}}, |
| 52 | + right/.code = {\renewcommand\EAwesteast{east}}, |
| 53 | + label above/.code = {\renewcommand\EAlabelanchor{south}}, |
| 54 | + label below/.code = {\renewcommand\EAlabelanchor{north}}, |
| 55 | +} |
| 56 | + |
| 57 | +%%%%% %%%%%%%% %%%%% |
| 58 | + |
| 59 | + |
| 60 | +\newcommand*{\eqnhighlightcolorbox}[2]{% |
| 61 | +% \colorbox sets the second argument in text mode, so for use within equations we wrap it in $ $ again |
| 62 | + \mathchoice% to get right font size in each mode: |
| 63 | + {\colorbox{#1}{$\displaystyle #2$}}% |
| 64 | + {\colorbox{#1}{$\textstyle #2$}}% |
| 65 | + {\colorbox{#1}{$\scriptstyle #2$}}% |
| 66 | + {\colorbox{#1}{$\scriptscriptstyle #2$}}% |
| 67 | +} |
| 68 | + |
| 69 | +%%% the fbox with 0pt rule fixes the height of eqnmark vs eqnmarkbox issue |
| 70 | +\newcommand*{\eqnhighlightfbox}[2]{% |
| 71 | +% \fbox sets the second argument in text mode, so for use within equations we wrap it in $ $ again |
| 72 | + \mathchoice% to get right font size in each mode: |
| 73 | + {\begingroup\setlength{\fboxrule}{0pt}\fbox{$\displaystyle\color{#1}#2$}\endgroup}% |
| 74 | + {\begingroup\setlength{\fboxrule}{0pt}\fbox{$\textstyle\color{#1}#2$}\endgroup}% |
| 75 | + {\begingroup\setlength{\fboxrule}{0pt}\fbox{$\scriptstyle\color{#1}#2$}\endgroup}% |
| 76 | + {\begingroup\setlength{\fboxrule}{0pt}\fbox{$\scriptscriptstyle\color{#1}#2$}\endgroup}% |
| 77 | +} |
| 78 | + |
| 79 | +% . is the current color |
| 80 | + |
| 81 | +\newcommand*{\eqnhighlight}[2]{\begingroup\colorlet{currentcolor}{.}\eqnhighlightcolorbox{#1!\eqnhighlightshade}{\eqnhighlightheight #2}\endgroup} |
| 82 | +\newcommand*{\eqncolor}[2]{\begingroup\colorlet{currentcolor}{.}\eqnhighlightfbox{#1}{\eqnhighlightheight #2}\endgroup} |
| 83 | + |
| 84 | +%%% Arguments to \eqnmark[box]: [highlight color]{node name}{term to highlight} |
| 85 | +\newcommand*{\eqnmarkbox}[3][currentcolor]{\addvalue{#2}{#1}\tikzmarknode{#2}{\eqnhighlight{#1}{#3}}} |
| 86 | +\newcommand*{\eqnmark}[3][currentcolor]{\addvalue{#2}{#1}\tikzmarknode{#2}{\eqncolor{#1}{#3}}} |
| 87 | + |
| 88 | +% Store current color in a dictionary (lookup table), |
| 89 | +% from https://tex.stackexchange.com/a/48931/171664 : |
| 90 | +\def\addvalue#1#2{\expandafter\gdef\csname eqnannotate@data@#1\endcsname{#2}} |
| 91 | +\def\usevalue#1{% |
| 92 | + \ifcsname eqnannotate@data@#1\endcsname |
| 93 | + \csname eqnannotate@data@#1\expandafter\endcsname |
| 94 | + \else |
| 95 | + currentcolor% |
| 96 | + \fi |
| 97 | +} |
| 98 | + |
| 99 | + |
| 100 | +%%%%% Helpers for swapping north/south / west/east / -/+ depending on above/below / left/right etc.: |
| 101 | +\newcommand*{\swapNorthSouth}[1]{% |
| 102 | + \ifnum\pdfstrcmp{#1}{south}=0 north\else south\fi |
| 103 | +} |
| 104 | +\newcommand*{\swapWestEast}[1]{% |
| 105 | + \ifnum\pdfstrcmp{#1}{east}=0 west\else east\fi |
| 106 | +} |
| 107 | +\newcommand*{\EAxshift}[1]{% |
| 108 | + \ifnum\pdfstrcmp{#1}{east}=0 -0.3ex\else 0.3ex\fi |
| 109 | +} |
| 110 | +%%%%% |
| 111 | + |
| 112 | + |
| 113 | +\newcounter{eqnannotatenode} |
| 114 | +\newcommand*{\eqnannotateCurrentNode}{eqnannotatenode\theeqnannotatenode} |
| 115 | + |
| 116 | + |
| 117 | +\newcommand{\annotatetwo}[5][]{% |
| 118 | + \begingroup% %%% so we don't leak the \def's below |
| 119 | + \stepcounter{eqnannotatenode}% |
| 120 | + %%% #1: (optional) extra args for \node e.g. yshift=... |
| 121 | + \pgfkeys{/eqnannotate, #2}% %%% all configuration options |
| 122 | + \def\myEAmarkOne{#3}% |
| 123 | + \def\myEAmarkTwo{#4}% |
| 124 | + \colorlet{currentcolor}{.} |
| 125 | + \def\myEAtext{#5}% |
| 126 | + \def\myEAcolor{\usevalue{\myEAmarkOne}}% |
| 127 | + \begin{tikzpicture}[overlay,remember picture,>=stealth,nodes={align=left,inner ysep=1pt},<-] |
| 128 | + % default anchor is at center |
| 129 | + \node[anchor=\swapNorthSouth{\EAmarkanchor},color=\myEAcolor!85,#1] % color blended with white to 85%, any (optional) extra args #1 |
| 130 | + (\eqnannotateCurrentNode) % use counter-based "local node" |
| 131 | + at ($(\myEAmarkOne.\EAmarkanchor)!0.5!(\myEAmarkTwo.\EAmarkanchor)$) % centered between the two nodes |
| 132 | + {\eqnannotationtext{\myEAtext}}; |
| 133 | + % double arrow to two uses within the equation: |
| 134 | + \draw [<->,color=\myEAcolor] (\myEAmarkOne.\EAmarkanchor) |- ([yshift=0.1ex] \eqnannotateCurrentNode.\EAlabelanchor) -| (\myEAmarkTwo.\EAmarkanchor); % from node 1 via annotation to node 2, with anchor #6 each |
| 135 | + \end{tikzpicture}% |
| 136 | + \endgroup% %%% close group again |
| 137 | +} |
| 138 | + |
| 139 | + |
| 140 | +%%% \extractfirst from https://tex.stackexchange.com/a/115733/171664 |
| 141 | +\RequirePackage{expl3} |
| 142 | +\RequirePackage{xparse} |
| 143 | +\ExplSyntaxOn |
| 144 | +\NewDocumentCommand{\extractfirst}{mm} |
| 145 | + { |
| 146 | + \tl_set:Nx #1 {\clist_item:Nn #2 { 1 } } |
| 147 | + } |
| 148 | +\ExplSyntaxOff |
| 149 | +%%% |
| 150 | + |
| 151 | +\newcommand{\annotate}[4][]{% |
| 152 | + \begingroup% %%% so we don't leak the \def's below |
| 153 | + \stepcounter{eqnannotatenode}% |
| 154 | + % |
| 155 | + % |
| 156 | + % |
| 157 | + % #1: (optional) extra args for \node e.g. yshift=... |
| 158 | + \pgfkeys{/eqnannotate, #2} |
| 159 | + \def\myEAmarks{#3}% |
| 160 | + \extractfirst\myEAmark\myEAmarks% %%% get first node for color and annotation |
| 161 | + \def\myEAtext{#4}% |
| 162 | + % |
| 163 | + % |
| 164 | + \colorlet{currentcolor}{.} |
| 165 | + \def\myEAcolor{\usevalue{\myEAmark}}% |
| 166 | + % |
| 167 | + % |
| 168 | + \def\myEAxshift{\EAxshift{\EAwesteast}}% |
| 169 | + \begin{tikzpicture}[overlay,remember picture,>=stealth,nodes={align=left,inner ysep=1pt},<-] |
| 170 | + \node[anchor=\swapNorthSouth{\EAmarkanchor} \swapWestEast{\EAwesteast},color=\myEAcolor!85,#1] % TODO for some reason, passing #1 through command doesn't work... |
| 171 | + % anchor=west: align left edge of text on top of tikzmark in equation |
| 172 | + % should be north west for below and south west for above ... |
| 173 | + (\eqnannotateCurrentNode) at (\myEAmark.\EAmarkanchor) % \EAmarkanchor north: above the equation, south: below |
| 174 | + {\eqnannotationtext{\myEAtext}}; |
| 175 | + \foreach \EAmark in \myEAmarks |
| 176 | + \draw [color=\myEAcolor] (\EAmark.\EAmarkanchor) % arrow from the equation |
| 177 | + % \EAmarkanchor north: above the equation, south: below |
| 178 | + |- ([xshift=\myEAxshift,yshift=0.1ex] \eqnannotateCurrentNode.south \EAwesteast); |
| 179 | + % - south east: we want line to end at bottom right of annotation text; |
| 180 | + % - negative xshift makes it a little bit shorter; |
| 181 | + % - yshift for aesthetics (\strut is ever so slightly too tall). |
| 182 | + \end{tikzpicture}% |
| 183 | + \endgroup% %%% close group again |
| 184 | +} |
| 185 | + |
| 186 | +\endinput |
| 187 | +%% |
| 188 | +%% End of file |
0 commit comments