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{assets/texpackages/annotate-equations}
8 [2023/05/06 v0.2.2 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{\eqnannotationstrut}{\strut} % Package default
40\newcommand{\eqnannotationfont}{\sffamily\footnotesize}
41
42
43\providecommand\EAmarkanchor{north} % default set to "above"
44\providecommand\EAwesteast{east} % default set to "right"
45\providecommand\EAlabelanchor{south} % default set to "label above"
46% for pgfkeys, see https://tex.stackexchange.com/a/34318/171664
47% for no-value keys, see https://tex.stackexchange.com/a/401848/171664
48\pgfkeys{
49 /eqnannotate/.is family, /eqnannotate,
50 above/.code = {\renewcommand\EAmarkanchor{north}},
51 below/.code = {\renewcommand\EAmarkanchor{south}},
52 left/.code = {\renewcommand\EAwesteast{west}},
53 right/.code = {\renewcommand\EAwesteast{east}},
54 label above/.code = {\renewcommand\EAlabelanchor{south}},
55 label below/.code = {\renewcommand\EAlabelanchor{north}},
56}
57
58\tikzset{annotate equations/arrow/.style={}}
59\tikzset{annotate equations/text/.style={font=\eqnannotationfont}}
60
61%%%%% %%%%%%%% %%%%%
62
63
64\newcommand*{\eqnhighlightcolorbox}[2]{%
65% \colorbox sets the second argument in text mode, so for use within equations we wrap it in $ $ again
66 \mathchoice% to get right font size in each mode:
67 {\colorbox{#1}{$\displaystyle #2$}}%
68 {\colorbox{#1}{$\textstyle #2$}}%
69 {\colorbox{#1}{$\scriptstyle #2$}}%
70 {\colorbox{#1}{$\scriptscriptstyle #2$}}%
71}
72
73%%% the fbox with 0pt rule fixes the height of eqnmark vs eqnmarkbox issue
74\newcommand*{\eqnhighlightfbox}[2]{%
75% \fbox sets the second argument in text mode, so for use within equations we wrap it in $ $ again
76 \mathchoice% to get right font size in each mode:
77 {\begingroup\setlength{\fboxrule}{0pt}\fbox{$\displaystyle\color{#1}#2$}\endgroup}%
78 {\begingroup\setlength{\fboxrule}{0pt}\fbox{$\textstyle\color{#1}#2$}\endgroup}%
79 {\begingroup\setlength{\fboxrule}{0pt}\fbox{$\scriptstyle\color{#1}#2$}\endgroup}%
80 {\begingroup\setlength{\fboxrule}{0pt}\fbox{$\scriptscriptstyle\color{#1}#2$}\endgroup}%
81}
82
83% . is the current color
84
85\newcommand*{\eqnhighlight}[2]{\begingroup\colorlet{currentcolor}{.}\eqnhighlightcolorbox{#1!\eqnhighlightshade}{\eqnhighlightheight #2}\endgroup}
86\newcommand*{\eqncolor}[2]{\begingroup\colorlet{currentcolor}{.}\eqnhighlightfbox{#1}{\eqnhighlightheight #2}\endgroup}
87
88%%% Arguments to \eqnmark[box]: [highlight color]{node name}{term to highlight}
89\newcommand*{\eqnmarkbox}[3][currentcolor]{\addvalue{#2}{#1}\tikzmarknode{#2}{\eqnhighlight{#1}{#3}}}
90\newcommand*{\eqnmark}[3][currentcolor]{\addvalue{#2}{#1}\tikzmarknode{#2}{\eqncolor{#1}{#3}}}
91
92% Store current color in a dictionary (lookup table),
93% from https://tex.stackexchange.com/a/48931/171664 :
94\def\addvalue#1#2{\expandafter\gdef\csname eqnannotate@data@#1\endcsname{#2}}
95\def\usevalue#1{%
96 \ifcsname eqnannotate@data@#1\endcsname
97 \csname eqnannotate@data@#1\expandafter\endcsname
98 \else
99 currentcolor%
100 \fi
101}
102
103
104%%%%% Helpers for swapping north/south / west/east / -/+ depending on above/below / left/right etc.:
105\newcommand*{\swapNorthSouth}[1]{%
106 \ifnum\pdfstrcmp{#1}{south}=0 north\else south\fi
107}
108\newcommand*{\swapWestEast}[1]{%
109 \ifnum\pdfstrcmp{#1}{east}=0 west\else east\fi
110}
111\newcommand*{\EAxshift}[1]{%
112 \ifnum\pdfstrcmp{#1}{east}=0 -0.3ex\else 0.3ex\fi
113}
114%%%%%
115
116
117\newcounter{eqnannotatenode}
118\newcommand*{\eqnannotateCurrentNode}{eqnannotatenode\theeqnannotatenode}
119
120
121\newcommand{\annotatetwo}[5][]{%
122 \begingroup% %%% so we don't leak the \def's below
123 \stepcounter{eqnannotatenode}%
124 %%% #1: (optional) extra args for \node e.g. yshift=...
125 \pgfkeys{/eqnannotate, #2}% %%% all configuration options
126 \def\myEAmarkOne{#3}%
127 \def\myEAmarkTwo{#4}%
128 \colorlet{currentcolor}{.}%
129 \def\myEAtext{#5}%
130 \def\myEAcolor{\usevalue{\myEAmarkOne}}%
131 \begin{tikzpicture}[overlay,remember picture,>=stealth,nodes={align=left,inner ysep=1pt},<-]
132 % default anchor is at center
133 \node[anchor=\swapNorthSouth{\EAmarkanchor},color=\myEAcolor!85,
134 annotate equations/text,#1
135 ] % color blended with white to 85%, any (optional) extra args #1
136 (\eqnannotateCurrentNode) % use counter-based "local node"
137 at ($(\myEAmarkOne.\EAmarkanchor)!0.5!(\myEAmarkTwo.\EAmarkanchor)$) % centered between the two nodes
138 {\myEAtext\eqnannotationstrut};
139 % double arrow to two uses within the equation:
140 \draw [<->,color=\myEAcolor, annotate equations/arrow] (\myEAmarkOne.\EAmarkanchor) |- ([yshift=0.1ex] \eqnannotateCurrentNode.\EAlabelanchor) -| (\myEAmarkTwo.\EAmarkanchor); % from node 1 via annotation to node 2, with anchor #6 each
141 \end{tikzpicture}%
142 \endgroup% %%% close group again
143}
144
145
146%%% \extractfirst from https://tex.stackexchange.com/a/115733/171664
147\RequirePackage{expl3}
148\RequirePackage{xparse}
149\ExplSyntaxOn
150\NewDocumentCommand{\extractfirst}{mm}
151 {
152 \tl_set:Nx #1 {\clist_item:Nn #2 { 1 } }
153 }
154\ExplSyntaxOff
155%%%
156
157\newcommand{\annotate}[4][]{%
158 \begingroup% %%% so we don't leak the \def's below
159 \stepcounter{eqnannotatenode}%
160 %
161 %
162 %
163 % #1: (optional) extra args for \node e.g. yshift=...
164 \pgfkeys{/eqnannotate, #2}%
165 \def\myEAmarks{#3}%
166 \extractfirst\myEAmark\myEAmarks% %%% get first node for color and annotation
167 \def\myEAtext{#4}%
168 %
169 %
170 \colorlet{currentcolor}{.}%
171 \def\myEAcolor{\usevalue{\myEAmark}}%
172 %
173 \def\EAspace{ }% %%% workaround: did not find any other way of getting a space into \myEAlabelanchor without upsetting LaTeX/PGF/... somehow
174 \edef\myEAlabelanchor{\EAlabelanchor\EAspace\EAwesteast}%
175 %
176 %
177 \def\myEAxshift{\EAxshift{\EAwesteast}}%
178 \begin{tikzpicture}[overlay,remember picture,>=stealth,nodes={align=left,inner ysep=1pt},<-]
179 \node[anchor=\swapNorthSouth{\EAmarkanchor} \swapWestEast{\EAwesteast},
180 color=\myEAcolor!85,annotate equations/text,#1] % TODO for some reason, passing #1 through command doesn't work...
181 % anchor=west: align left edge of text on top of tikzmark in equation
182 % should be north west for below and south west for above ...
183 (\eqnannotateCurrentNode) at (\myEAmark.\EAmarkanchor) % \EAmarkanchor north: above the equation, south: below
184 {\myEAtext\eqnannotationstrut};
185 \foreach \EAmark in \myEAmarks
186 \draw [color=\myEAcolor, annotate equations/arrow] (\EAmark.\EAmarkanchor) % arrow from the equation
187 % \EAmarkanchor north: above the equation, south: below
188 |- ([xshift=\myEAxshift,yshift=0.1ex] \eqnannotateCurrentNode.\myEAlabelanchor);
189 % - south east: we want line to end at bottom right of annotation text;
190 % - negative xshift makes it a little bit shorter;
191 % - yshift for aesthetics (\strut is ever so slightly too tall).
192 \end{tikzpicture}%
193 \endgroup% %%% close group again
194}
195
196\endinput
197%%
198%% End of file