diff options
author | Karl Berry <karl@freefriends.org> | 2014-08-11 23:01:04 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2014-08-11 23:01:04 +0000 |
commit | 64660cd86edb8ae68a28d7ef7550583d76ae5f5e (patch) | |
tree | 35b526e4b35ea14914e1830ce292a3c442e44a25 /Master | |
parent | 7f08054bf5aebef2dca4f32b2d21e95a50f7e2c5 (diff) |
luatodonotes (8aug14)
git-svn-id: svn://tug.org/texlive/trunk@34908 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master')
-rw-r--r-- | Master/texmf-dist/doc/lualatex/luatodonotes/README | 25 | ||||
-rw-r--r-- | Master/texmf-dist/doc/lualatex/luatodonotes/luatodonotes.pdf | bin | 0 -> 186868 bytes | |||
-rw-r--r-- | Master/texmf-dist/source/lualatex/luatodonotes/luatodonotes.dtx | 1823 | ||||
-rw-r--r-- | Master/texmf-dist/source/lualatex/luatodonotes/luatodonotes.ins | 65 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/luatodonotes/inspect.lua | 297 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/luatodonotes/luatodonotes.lua | 2160 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/luatodonotes/luatodonotes.sty | 634 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/luatodonotes/path_line.lua | 120 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/luatodonotes/path_point.lua | 73 | ||||
-rwxr-xr-x | Master/tlpkg/bin/tlpkg-ctan-check | 2 | ||||
-rwxr-xr-x | Master/tlpkg/libexec/ctan2tds | 1 | ||||
-rw-r--r-- | Master/tlpkg/tlpsrc/collection-luatex.tlpsrc | 1 | ||||
-rw-r--r-- | Master/tlpkg/tlpsrc/luatodonotes.tlpsrc | 0 |
13 files changed, 5200 insertions, 1 deletions
diff --git a/Master/texmf-dist/doc/lualatex/luatodonotes/README b/Master/texmf-dist/doc/lualatex/luatodonotes/README new file mode 100644 index 00000000000..cd40a3995b4 --- /dev/null +++ b/Master/texmf-dist/doc/lualatex/luatodonotes/README @@ -0,0 +1,25 @@ +This package allows you to insert comments for further editing in your document. +The comments are shown in the margins besides the text. +Different styles for the comments can be used, which can be configured using +package options. +This package depends heavily on Lua, so it can only be used with LuaLaTeX. + +luatodonotes is based on the todonotes package by Henrik Skov Midtiby +(http://www.ctan.org/pkg/todonotes). + +Installation +------------ +Run 'latex luatodonotes.ins' to generate the package files and copy the listed +files into your TEXMF tree. + +License +------- +The luatodonotes package is subject to the LATEX Project Public License. +The following external lua libraries are used: + +- path_line.lua and path_point.lua + taken from luapower.com (Public domain) + +- inspect.lua + by Enrique GarcĂa Cota + MIT License diff --git a/Master/texmf-dist/doc/lualatex/luatodonotes/luatodonotes.pdf b/Master/texmf-dist/doc/lualatex/luatodonotes/luatodonotes.pdf Binary files differnew file mode 100644 index 00000000000..d6ebf3dae39 --- /dev/null +++ b/Master/texmf-dist/doc/lualatex/luatodonotes/luatodonotes.pdf diff --git a/Master/texmf-dist/source/lualatex/luatodonotes/luatodonotes.dtx b/Master/texmf-dist/source/lualatex/luatodonotes/luatodonotes.dtx new file mode 100644 index 00000000000..3188307f4c5 --- /dev/null +++ b/Master/texmf-dist/source/lualatex/luatodonotes/luatodonotes.dtx @@ -0,0 +1,1823 @@ +% \iffalse meta-comment +% +% Copyright (C) 2014 by Fabian Lipp <fabian.lipp@gmx.de> +% based on the todonotes package +% by Henrik Skov Midtiby <henrikmidtiby@gmail.com> +% ------------------------------------------------------- +% +% This file may be distributed and/or modified under the +% conditions of the LaTeX Project Public License, either version 1.2 +% of this license or (at your option) any later version. +% The latest version of this license is in: +% +% http://www.latex-project.org/lppl.txt +% +% and version 1.2 or later is part of all distributions of LaTeX +% version 1999/12/01 or later. +% +% \fi +% +% \iffalse +%<*driver> +\ProvidesFile{luatodonotes.dtx} +%</driver> +%<package>\NeedsTeXFormat{LaTeX2e}[1999/12/01] +%<package>\ProvidesPackage{luatodonotes} +%<*package> + [2014/08/07 v0.1 luatodonotes source and documentation.] +%</package> +% +%<*driver> +\documentclass{ltxdoc} +\usepackage{wrapfig} +\usepackage[colorlinks, linkcolor=black, urlcolor=blue]{hyperref} +\usepackage[colorinlistoftodos, shadow]{luatodonotes}[2014/06/08] +\usepackage{fontspec} +\usepackage{amsmath} +\usepackage{setspace} +\usepackage{soul} +\setcounter{tocdepth}{2} +\EnableCrossrefs +\CodelineIndex +\RecordChanges +\begin{document} + \DocInput{luatodonotes.dtx} +%\iffalse +% \PrintChanges +% \PrintIndex +%\fi +\end{document} +%</driver> +% \fi +% +% \CheckSum{841} +% +% \CharacterTable +% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +% Digits \0\1\2\3\4\5\6\7\8\9 +% Exclamation \! Double quote \" Hash (number) \# +% Dollar \$ Percent \% Ampersand \& +% Acute accent \' Left paren \( Right paren \) +% Asterisk \* Plus \+ Comma \, +% Minus \- Point \. Solidus \/ +% Colon \: Semicolon \; Less than \< +% Equals \= Greater than \> Question mark \? +% Commercial at \@ Left bracket \[ Backslash \\ +% Right bracket \] Circumflex \^ Underscore \_ +% Grave accent \` Left brace \{ Vertical bar \| +% Right brace \} Tilde \~} +% +% +% \changes{0.1}{2014/08/07}{The first version of the package} +% \GetFileInfo{luatodonotes.dtx} +% +% \DoNotIndex{\newcommand,\newenvironment} +% +% \iffalse +% A macro for marking things todo before the next relase +% (typically update of documentation). +% \fi +% \newcommand{\donow}[1]{\todo[color=blue]{#1}} +% +% +% \title{The \textsf{luatodonotes} package\thanks{This document +% corresponds to \textsf{luatodonotes}~\fileversion, dated \filedate.}} +% \author{Fabian Lipp\thanks{This documentation and the whole package is based +% on version 1.0.2 of the todonotes package by Henrik Skov Midtiby.} \\ +% \texttt{fabian.lipp@gmx.de}} +% +% \maketitle +% +% \begin{abstract} +% The |luatodonotes| package allows you to insert to--do items in your +% document. At any point in the document a list of all the inserted +% to--do items can be listed with the |\listoftodos| command. +% +% It is an extended version of the |todonotes| package and uses more advanced +% algorithms to place the to--do notes notes on the page. +% For this algorithms it depends on Lua\TeX. +% \end{abstract} +% +% \tableofcontents +% +% \newpage +% \section{Introduction} +% +% The |luatodonotes| package makes three commands available to the +% user: |\todo[]{}|, |\missingfigure{}| and |\listoftodos|. +% |\todo[]{}| and |\missingfigure{}| makes it possible to insert +% notes in your document about things that has to be done later +% (todonotes \ldots). +% This package is based on version 1.0.2 of +% |todonotes|\footnote{\url{http://www.ctan.org/pkg/todonotes}} by Henrik Skov +% Midtiby. +% +% The positions of the notes on the page is determined using algorithms +% implemented in Lua, so you have to process your documents using Lua\LaTeX. +% The package can be used as a drop-in replacement for the original +% |todonotes| package, you +% only need to modify |\usepackage{todonotes}| to |\usepackage{luatodonotes}|. +% Note that |todonotes| and |luatodonotes| must not be loaded inside the same +% document. +% +% Some alternatives for the luatodonotes package are: +% \begin{itemize} +% \item \href{http://www.ctan.org/tex-archive/macros/latex/contrib/easy-todo}{easy-todo}\\ +% Depends on |color|, |tocloft| and |ifthen|, small feature set. +% \item \href{http://www.ctan.org/pkg/fixmetodonotes}{fixmetodonotes}\\ +% Depends on |graphicx|, |color|, |transparent|, |watermark|, |fix-cm|, |ulem| and |tocloft|, small feature set. +% \item \href{http://www.ctan.org/tex-archive/macros/latex/contrib/todo}{todo}\\ +% Depends on |amssymb|, medium feature set. +% \item \href{http://www.ctan.org/tex-archive/macros/latex/contrib/fixme}{fixme}\\ +% Large package with a lot of features. +% \item \href{http://www.ctan.org/pkg/todonotes}{todonotes} +% \end{itemize} +% +% Compared to the classical todonotes this package has more advanced algorithms +% and more configuration options to control the position of the notes on the +% page. +% Additionally, we are able to place notes at almost every position on the +% page, e.\,g., in floating environments or in footnotes. +% As a disadvantage luatodonotes requires Lua\LaTeX for document processing, so +% a standard |pdflatex| won't work. +% Depending on the chosen layout for the to--do notes the runtime can be much +% higher than with todonotes. +% Labels placed by luatodonotes can conflict with text placed with +% |\marginpar|. +% +% The main reason for considering other packages is that the todonotes +% package is quire large and relies heavily on tikz. +% This can slow down compilation of large documents significantly. +% The mentioned alternatives have a different feature set and does not +% rely on tikz, which makes them require less ressoureces. +% +% +% \subsection{Usage} +% The package is loaded with |\usepackage|\oarg{options}|{luatodonotes}|. +% Valid options are described in Section~\ref{subsecPackageOptions}. +% Note that |todonotes| must \emph{not} be loaded. +% You have to use |lualatex| to process your document, |pdflatex| will not +% work. +% The package depends on positions written to the aux-file, so you have to run +% |lualatex| twice or even three times to get the labels and leaders for the +% notes right. +% +% \DescribeMacro{\todo} +% My \index{\todo}most common usage of the todonotes package, is to +% insert an todonotes somewhere in a latex document. +% An example of this usage is the command +% +% |\todo{Make a cake \ldots}|, +% +% \noindent +% which renders like\todo{Make a cake \ldots}. +% The |\todo| command has this structure: +% |\todo|\oarg{options}\marg{todo text}. +% The |todo text| is the text that will be shown in the todonote and +% in the list of todos. The optional argument |options|, allows the +% user to customize the appearance of the inserted todonotes. +% For a description of all the options see section +% \ref{subsecTodoOptions}. +% +% \DescribeMacro{\todoarea} +% The |\todoarea| is similiar to |\todo|, but is able to highlight a specified +% area in the text, to which the note is connected. +% The command has this structure: +% |\todoarea|\oarg{options}\marg{note text}\marg{highlighted text}. +% This command was not tested extensively until now, so it should be used with +% caution. +% +% \DescribeMacro{\missingfigure} +% The |\missingfigure| command inserts an image containing an +% attention sign and the given text. +% The command takes only one argument +% |\missingfigure|\marg{text}, a text string that could +% describe what the figure should consist of. +% An example of its usage could be +% +% |\missingfigure{Make a sketch of the structure of a trebuchet.}| +% +% \noindent +% which renders like. +% +% \missingfigure{Make a sketch of the structure of a trebuchet.} +% +% +% \DescribeMacro{\listoftodos} +% The |\listoftodos| command inserts a list of all the todos in the +% current document. |\listoftodos| takes no arguments. +% For this document the list of to--do's looks like. +% \listoftodos +% \vspace{0.5cm} +% +% \DescribeMacro{\todototoc} +% The |\todototoc| command adds an entry to the table of contents for +% list of todos. The command should be placed right before the +% |\listoftodos| command. +% +% \subsection{Package options} +% \label{subsecPackageOptions} +% \DescribeMacro{disable} +% If the option |disable| is passed to the package, the macros +% usually defined by the package (|\todo|, |\todoarea|, |\listoftodos| and +% |\missingfigure|) are defined as macros with no effect, and thus +% all inserted notes are removed. +% +% \DescribeMacro{obeyDraft, obeyFinal} +% When the option |obeyDraft| is given, the package checks +% if the one of the options |draft|, |draftcls| or |draftclsnofoot| +% is given (this option is usually given to +% the documentclass). If the |draft| option is given, the +% functionality of the package is enabled and otherwise the effect +% of the package is disabled. +% The option |obeyFinal| does something similar, except that the +% todonotes package is only disabled if the |final| option given. +% +% \DescribeMacro{danish, german, ngerman, french, swedish} +% \DescribeMacro{spanish, catalan, italian} +% \DescribeMacro{portuguese, dutch} +% Use translations of the text strings +% ''List of todos'' and ''Missing figure''. +% The default is to use none of these options, which results in +% english text strings. +% Currently the following languages are supported: +% catalan, +% danish, +% dutch, +% french, +% german, +% ngerman, +% italian, +% portuguese, +% spanish and +% swedish. +% +% \DescribeMacro{colorinlistoftodos} +% Adds a small colored square in front of all items in the Todo +% list. The color of the square is the same as the fill color of the +% inserted todonote. +% This can be useful if there are different types of todos +% (insert reference, explain in detail, \ldots) where the color of +% the inserted todonote marks the type of todo. +% +% \DescribeMacro{color} +% \DescribeMacro{backgroundcolor} +% \DescribeMacro{linecolor} +% \DescribeMacro{bordercolor} +% These options sets the default colors for the todo command. +% There is three colors that can be specified. The border color +% (default |bordercolor=black|) around the inserted text, the color +% behind the inserted text (default |backgroundcolor=orange|) and +% the color of the line connecting the inserted textbox with the +% current location in the text (default |linecolor=black!30|). +% Setting the |color| option to |val| passes this value on to the +% background and line color options. +% The specified colors must be valid according to the +% |xcolor| package. +% +% \DescribeMacro{textsize} +% |textsize=value| sets the default text size of the inserted +% todonotes to the given value. +% Value is the ''name'' of the used font size, eg. if the desired +% fontsize is |\tiny| use |textsize=tiny|. The default value is +% |textsize=normalsize|. +% +% \DescribeMacro{prependcaption} +% The |prependcaption| option triggers a special behaviour of the +% |caption=val| option for the todo command, where the given value +% |val| is inserted in the inserted todonote. +% +% \DescribeMacro{shadow} +% If the |shadow| option is given, the inserted todonotes will be +% displayed with a gray shadow. +% I expect that the option will trigger problems with tikz versions +% prior to 2.0. +% +% \DescribeMacro{figwidth} +% \DescribeMacro{figheight} +% The |figwidth=length| option and |figheight=length| option set the default +% width and height of the figure +% inserted by the |\missingfigure| command. +% The default value is |\columnwidth| for the width and |4cm| for the height. +% +% \DescribeMacro{leaderwidth} +% The |leaderwidth=length| option specifies the width of the leader lines. +% The argument is passed to the |line width| option in TikZ. +% The default value is |1.6pt|. +% +% \DescribeMacro{leadertype} +% The |leadertype=type| option specifies the shape of the leaders, which are +% drawn between the labels in the margin and the corresponding sites in text. +% We use the characterization of the leader types known from boundary labeling: +% $p$ denotes a segment parallel to the left/right side of the text area, while +% $o$ denotes a orthogonal segment. +% $s$ is a straight-line segment. +% The following types are available (|opo| is the default value): +% \begin{itemize} +% \item |s|: +% Straight-line connection between site and label. +% \item |sBezier|: +% Uses the straight-line leaders but transforms them into B\'ezier curves, +% which are easier to follow for the reader. +% The generated curves don't cross each other when the straight-line leaders +% are crossing-free. +% \item |opo|: +% This is the style used in the original todonotes package. +% The leaders start with a horizontal segment at the site in the text, +% followed by a vertical segment in the margin beneath the text. +% The last segment is a vertical segment, which connects to the label. +% \item |os|: +% This is the style used in common word processing applications like +% LibreOffice. +% The leader also starts with a horizontal segment that leads to the margin +% and is connected to the label by a straight line. +% \item |po|: +% The leader starts with a vertical segment at the site in text and is then +% connected to the label by a horizontal segment. +% \end{itemize} +% +% \DescribeMacro{positioning} +% The |positioning=algorithm| option specifies, which algorithm is used to +% determine the positions of the notes on the page. +% You should choose the algorithm depending on the leader type you want to use. +% The default value for this option is |inputOrderStacks|. +% The following algorithms are available: +% \begin{itemize} +% \item |inputOrder|: +% Place the labels in the order given by the +% y-coordinates of the corresponding sites in text. +% Intended for use with $opo$- or $os$-leaders. +% \item |inputOrderStacks|: +% Like the algorithm before, but the labels are +% clustered before they are placed. +% Thus the labels are placed nearer to their sites. +% Intended for use with $opo$- or $os$-leaders. +% \item |sLeaderNorthEast|: +% Places labels in a way that they can be connected to their sites by +% straight-line leaders without crossings. +% The leaders are attached to the upper right or upper left corner of the +% label (depending on which site of the text the label is placed). +% Intended for use with $s$-leaders or B\'ezier leaders. +% \item |sLeaderNorthEastBelow|: +% Like the algorithm before, but the leader is attached to a point that is a +% constant offset below the corner of the label. +% Intended for use with $s$-leaders or B\'ezier leaders. +% \item |sLeaderNorthEastBelowStacks|: +% Like the algorithm before, but the labels are cluster before they are +% placed. +% Thus the labels are placed nearer to their sites. +% Intended for use with $s$-leaders or B\'ezier leaders. +% \item |sLeaderEast|: +% Like the algorithms before, but the leader is attached to the center of the +% right or left boundary of the label. +% Intended for use with $s$-leaders or B\'ezier leaders. +% \item |poLeaders|: +% Calculates label positions that lead to $po$-leaders with minimum total +% length. +% This algorithm depends heavily on the number of notes, so the runtime and +% memory consumption can get very high. +% \item |poLeadersAvoidLines|: +% Like the algorithm before, but tries to avoid overlapping of horizontal +% leader segments with text. +% This algorithm depends heavily on advanced Lua\TeX\ features to manipulate +% the data structures of the page, so it possibly could give conflicts with +% other packages. +% \end{itemize} +% +% \DescribeMacro{splitting} +% The |splitting=algorithm| option can be used to place the labels on both sides +% of the text. +% The notes are only separated when there is enough space on both sides (see +% |minNoteWidth|. +% The default value for this option is |none|. +% Available algorithms for this option are: +% \begin{itemize} +% \item |none|: +% Labels are placed in the wider margin only. +% \item |middle|: +% The text area is split in the middle in a left and a right half. +% Labels, whose sites are in the left half of the text, are placed in the +% left margin, the others in the right margin. +% \item |median|: +% The notes are seperated at the median of the sites (sorted by +% x-coordinate). +% That is, the number of notes in the left and the right margin is equal +% (except for one note). +% \item |weightedMedian|: +% Considers the height of the labels for the median. +% So the total height of the labels in the left margin is approximately equal +% to that in the right margin. +% \end{itemize} +% +% \DescribeMacro{interNoteSpace} +% The |interNoteSpace=length| option specifies the minimum vertical distance +% between two notes. +% The default value is |5pt|. +% +% \DescribeMacro{noteInnerSep} +% The |noteInnerSep=length| option specifies the |inner sep| used for the TikZ +% nodes, i.\,e., the distance between the border of the note and the text inside +% it. +% The default value is |5pt|. +% +% \DescribeMacro{routingAreaWidth} +% The |routingAreaWidth=length| option specifies the width of the so called +% routing area. +% This is the area, in which the vertical segment of $opo$-leaders are placed. +% The area is also used for $os$-leaders. +% The default value is |0.4cm|. +% +% \DescribeMacro{minNoteWidth} +% The |minNoteWidth=length| option specifies the minimum width of the labels. +% When there is fewer space in one of the margins, this margin is not considered +% for label placement. +% If both margins are narrower, no labels are placed and an error message is +% printed to the console output. +% The default value of this option is |2.0cm|. +% +% \DescribeMacro{distanceNotesPageBorder} +% The |distanceNotesPageBorder=length| option specifies the horizontal distance +% from the labels to the borders of the paper. +% You can adjust this setting to your printer margins. +% The default value of this option is |0.5cm|. +% +% \DescribeMacro{distanceNotesText} +% The |distanceNotesPageBorder=length| option specifies the horizontal distance +% between the labels and the text area. +% With $opo$- or $os$-leaders the routing area is inserted additionally so the +% distance between labels and text area increases. +% The default value of this option is |0.2cm|. +% +% \DescribeMacro{rasterHeight} +% The |rasterHeight=length| option is used only for the $po$-leader algorithm. +% For this algorithm the page is rasterized and the labels are placed only on +% the positions given by this raster. +% Decreasing this value can yield better results (i.\,e., smaller total leader +% length), but strongly increases the runtime and memory consumption. +% The default value of this option is |1cm|. +% +% \DescribeMacro{debug} +% When the |debug| option is activated the package is more verbose on +% the commandline. +% Additionally, some markers, which can be used to understand the algorithms, +% are drawn on the page (depending on the chosen algorithm). +% +% +% +% +% \subsection{Options for the todo command} +% \label{subsecTodoOptions} +% There are several options that can be given to the |\todo| +% command. All the options are described here and often I have +% included examples of the change in visual appearance. +% Default values for these options can be set using the presetkeys +% command. +% \begin{verbatim} +% \presetkeys{todonotes}{fancyline, color=blue!30}{} +% \end{verbatim} +% +% \DescribeMacro{disable} +% The |disable| option can be given directly to the todo command. +% If given the command has no effect. +% +% \DescribeMacro{color} +% \DescribeMacro{backgroundcolor} +% \DescribeMacro{linecolor} +% \DescribeMacro{bordercolor} +% These options set the color that is used in the current todo +% command. +% The color classes is the same as used in the color package +% options, see section \ref{subsecPackageOptions}. +% Default values can be set by the color +% options when the todonotes package is loaded. +% \todo[color=green!40]{And a green note} +% The todo notes inserted in this paragraph is created with the +% command +% |\todo[color=green!40]{And a green note}|. +% The color of the inserted note could be used to mark different +% types of tasks (insert references, explain something in detail, +% \ldots), this could be streamlined by defining new commands like +% below. +% \begin{verbatim} +% \newcommand{\insertref}[1]{\todo[color=green!40]{#1}} +% \newcommand{\explainindetail}[1]{\todo[color=red!40]{#1}} +% \end{verbatim} +% An example that uses all of the color options is given below +% \todo[linecolor=green!70!white, backgroundcolor=blue!20!white, +% bordercolor=red]{Anything but default colors}. +% \begin{verbatim} +% \todo[linecolor=green!70!white, backgroundcolor=blue!20!white, +% bordercolor=red]{Anything but default colors}. +% \end{verbatim} +% +% +% \DescribeMacro{line / noline} +% If you want to get rid of the line connecting the inserted note +% with the place in the text where the note occurs in the latex +% code, the option |noline| can be used. +% \todo[noline]{A note with no line connecting it to the placement +% in the original text.} +% |\todo[noline]{A note with no line ...}| +% \vspace{1.0cm} +% +% +% \DescribeMacro{inline / noinline} +% It is possible to place a todonote inside the text instead of +% placing it in the margin, this could be desirable if the text in +% the note has a considerable length. +% |\todo[inline]{A todonote placed in the text}| +% \todo[inline]{A todonote placed in the text} + +% \begin{wrapfigure}[1]{r}[20mm]{40mm} +% \begin{tikzpicture} +% \draw[red] (0, 0) circle(0.45); +% \draw[green] (1, 0) circle(0.45); +% \draw[blue] (2, 0) circle(0.45); +% \end{tikzpicture} +% \caption{A text explaining the image. +% \todo[inline]{Fill those circles \ldots}} +% \end{wrapfigure} +% Another usage for the inline option is when you want to add a +% todonote to a figure caption. +% +% \begin{verbatim} +% \begin{wrapfigure}{r}[20mm]{40mm} +% \begin{tikzpicture} +% \draw[red] (0, 0) circle(0.45); +% \draw[green] (1, 0) circle(0.45); +% \draw[blue] (2, 0) circle(0.45); +% \end{tikzpicture} +% \caption{A text explaining the image. +% \todo[inline]{Fill those circles \ldots}} +% \end{wrapfigure} +% \end{verbatim} +% +% \DescribeMacro{size} +% |size=val| changes the size of the text inside the todonote. +% The commands used to create the notes below are \\ \noindent +% |\todo[size=\Large]{A note with a large font size.}| +% and \\ \noindent +% |\todo[inline, size=\tiny]{Note with very small font size.}|. +% \todo[size=\Large]{A note with a large font size.} +% \todo[inline, size=\footnotesize]{Note with very small font size.} +% +% \DescribeMacro{list / nolist} +% When the option |nolist| is given, the todo item will not appear in +% the list of todos. +% +% \DescribeMacro{caption} +% The |caption| option enables the user to specify a short +% description of the todonote that are inserted in the list of +% todos instead of the full todonote text. +% \todo[caption={Short note}]{A very long and tedious note that +% cannot be on one line in the list of todos.} +% \begin{verbatim} +% \todo[caption={Short note}]{A very long and tedious note that +% cannot be on one line in the list of todos.}. +% \end{verbatim} +% The effect of this option is altered with the package option +% |prependcaption| or the |prepend| / |noprepend| option for the +% todo command. +% +% \DescribeMacro{prepend / noprepend} +% The options |prepend| and |noprepend| can be used for setting +% whether a given caption should be prepended to the todonote or +% not. +% Globally this can be set using the |prependcaption| option for the +% package.~\todo[prepend, caption={Short note with prepend}]{A very +% long and tedious note that cannot be on one line in the list of +% todos.} Below is the effect of the option shown using the code: +% \todo[noprepend, caption={Short note with noprepend}]{A very long +% and tedious note that cannot be on one line in the list of +% todos.} +% \begin{verbatim} +% \todo[prepend, caption={Short note with prepend}]{A very long and tedious +% note that cannot be on one line in the list of todos.}. +% \todo[noprepend, caption={Short note with noprepend}]{A very long and +% tedious note that cannot be on one line in the list of todos.}. +% \end{verbatim} +% +% \DescribeMacro{author} +% The |author| option takes a parameter, the name of the author. +% The given name is inserted in the todonote. +% \todo[author=Xavier]{Testing author option.} +% \todo[author=Xavier, inline]{Testing author option.} +% \begin{verbatim} +% \todo[author=Xavier]{Testing author option.} +% \todo[author=Xavier, inline]{Testing author option.} +% \end{verbatim} +% +% \subsection{Options for the missingfigure command} +% +% \DescribeMacro{figwidth} +% The |figwidth=length| option sets the width of the figure inserted by the +% |\missingfigure| command. +% Length values below $6cm$ might trigger some problems with the +% visual appearance. +% Try to compare the default of the missing figure command, when the +% option is given or not. +% \begin{verbatim} +% \missingfigure[figwidth=6cm]{Testing a long text string} +% \end{verbatim} +% \missingfigure[figwidth=6cm]{Testing a long text string} +% \begin{verbatim} +% \missingfigure{Testing a long text string} +% \end{verbatim} +% \missingfigure{Testing a long text string} +% \begin{wrapfigure}{r}[2cm]{6cm} +% \missingfigure[figwidth=6cm]{Add a test image \ldots} +% \end{wrapfigure} +% Another usage of the option is when |\missingfigure| is used in +% the wrapfigure environment. +% \begin{verbatim} +% \begin{wrapfigure}{r}[2cm]{6cm} +% \missingfigure[figwidth=6cm]{Add a test image \ldots} +% \end{wrapfigure} +% \end{verbatim} +% +% \DescribeMacro{figheight} +% The |figheight=length| option changes the height of the inserted +% missing figure. +% The default height is 4cm and using values lower than this might +% cause the warning sign to pop out of the gray area. +% \begin{verbatim} +% \missingfigure[figheight=6cm]{Testing a long text string} +% \end{verbatim} +% \missingfigure[figheight=6cm]{Testing} +% +% +% \subsection{Options for the listoftodos command} +% The |\listoftodos| command takes one optional argument, that +% defines the name of the inserted list of todos. +% \begin{verbatim} +% \listoftodos[I can be called anything] +% \end{verbatim} +% +% +% +% \subsection{Known issues} +% \subsubsection{Package loading order} +% The luatodonotes package requires the following packages: +% \begin{multicols}{2} +% \begin{itemize} +% \item ifthen +% \item xkeyval +% \item xcolor +% \item tikz +% \item graphicx (is loaded via the tikz package) +% \item luacode +% \item luatex +% \item atbegshi +% \item xstring +% \item zref-abspage +% \item ifoddpage +% \item soul +% \item soulpos +% \end{itemize} +% \end{multicols} +% \noindent +% When luatodonotes are loaded in the preamble, the package checks +% if these packages all are loaded. If that is not the case it loads +% the missing packages with no options given. +% If you want to give some specific options to some of these +% packages, you have to load them \emph{before} the luatodonotes +% package, otherwise you will get an ''Option clash'' error when +% latex works on the document. +% +% If both the menukeys and the xcolor (with the option \verb!table!) +% package should be loaded, the following order must be used. +% \begin{verbatim} +%\usepackage[table]{xcolor} +%\usepackage{todonotes} +%\usepackage{menukeys} +% \end{verbatim} +% +% \subsubsection{Spacing around inserted notes} +% Inserted todo commands will eat the white space after the command. +% \begin{verbatim} +%Testing\todo{Does this eat the space?} testing +% \end{verbatim} +% +% Testing\todo{Does this eat the space?}testing +% +% +% \subsubsection{Conflicts with the amsart documentclass} +% The |amsart| document class redefines some internal commands that +% is used by the todonotes package, this will cause an malfunctioning +% |\listoftodos| command. +% The following code to circumvent the problem was given by Dan +% Luecking on comp.text.tex +% \begin{verbatim} +% \makeatletter +% \providecommand\@dotsep{5} +% \makeatother +% \listoftodos\relax +% \end{verbatim} +% +% NOT TESTED +% NOT TESTED +% NOT TESTED +% +% Dominique suggests the following workaround. +% \begin{verbatim} +% \makeatletter +% \providecommand\@dotsep{5} +% \def\listtodoname{List of Todos} +% \def\listoftodos{\@starttoc{tdo}\listtodoname} +% \makeatother +% \end{verbatim} +% +% +% +% \subsubsection{Unknown option ''remember picture''} +% If latex throws the error +% \begin{verbatim} +% Package tikz Error: I do not know what to do with the option ``remember picture''. +% \end{verbatim} +% It probably means that your latex installation is outdated, as +% only newer versions of latex driver for tikz supports the +% |remember picture| option. +% For additional info consult +% ''Section 9.2.2 Producing PDF Output'' in the tikz manual. +% \url{http://www.ctan.org/tex-archive/graphics/pgf/base/doc/generic/pgf/pgfmanual.pdf} +% +% +% +% \subsubsection{List of todo heading is not correctly formatted} +% If using natbib, the todonotes list title gets screwed up unless +% you do something like this: +% \begin{verbatim} +% \makeatletter\let\chapter\@undefined\makeatother +% \end{verbatim} +% Suggestion by Richard Stanton. +% +% +% +% \subsubsection{Some commands not working inside notes} +% Some commands will not work like expected, when used inside of a note. +% They will cause errors when processing the document or have simply no effect. +% This is caused by the mechanism used to layout the notes: +% The content is written into a hbox when a |\todo| is encountered. +% The contents of this box are then stored until the note is typeset. +% By that time the contents are taken out of the hbox (by |\unhbox|) and put +% into a |\parbox| with the width required for the note. +% I don't have a solution for this problem yet. +% +% +% +% +% +% \iffalse +% \StopEventually{\PrintChanges\PrintIndex} +% \fi +% \StopEventually{\PrintChanges} +% +% \newpage +% \section{Implementation} +% +% Identifies the package and loads the packages dependences. +% \begin{macrocode} +\ProvidesPackage{luatodonotes}[2014/06/08] +\RequirePackage{ifthen} +\RequirePackage{xkeyval} +\RequirePackage{xcolor} +\RequirePackage{tikz} +\usetikzlibrary{positioning} +\usetikzlibrary{intersections} +\usetikzlibrary{decorations.pathmorphing} +\RequirePackage{luacode} +\RequirePackage{luatex} +\RequirePackage{atbegshi} +\RequirePackage{xstring} +\RequirePackage{zref-abspage} +\RequirePackage{ifoddpage} +\RequirePackage{soul} +\RequirePackage{soulpos} +% \end{macrocode} +% Some default values are set +% \begin{macrocode} +\newcommand{\@todonotes@text}{}% +\newcommand{\@todonotes@backgroundcolor}{orange} +\newcommand{\@todonotes@linecolor}{black!30} +\newcommand{\@todonotes@bordercolor}{black} +\newcommand{\@todonotes@leaderwidth}{1.6pt} +\newcommand{\@todonotes@textsize}{\normalsize} +\newcommand{\@todonotes@figwidth}{\columnwidth} +\newcommand{\@todonotes@figheight}{4cm} +% \end{macrocode} +% Default values for variables added by luatodonotes +% \begin{macrocode} +\newcommand{\@todonotes@positioning}{inputOrderStacks} +\newcommand{\@todonotes@splitting}{none} +\newcommand{\@todonotes@leadertype}{opo} +\newcommand{\@todonotes@interNoteSpace}{5pt} +\newcommand{\@todonotes@noteInnerSep}{5pt} +\newcommand{\@todonotes@routingAreaWidth}{0.4cm} +\newcommand{\@todonotes@minNoteWidth}{2.0cm} +\newcommand{\@todonotes@distanceNotesPageBorder}{0.5cm} +\newcommand{\@todonotes@distanceNotesText}{0.2cm} +\newcommand{\@todonotes@rasterHeight}{1cm} +% \end{macrocode} +% \begin{macrocode} +\AtBeginDocument{ +\ifx\undefined\phantomsection +\newcommand{\phantomsection}{} +\fi +} +% \end{macrocode} +% +% \subsection{Declaration of options for the package} +% In this part the various options for +% the package are defined. +% +% Define the default text strings and set localization options for +% the danish and german languages. +% \begin{macrocode} +\newcommand{\@todonotes@todolistname}{Todo list} +\newcommand{\@todonotes@MissingFigureText}{Figure} +\newcommand{\@todonotes@MissingFigureUp}{Missing} +\newcommand{\@todonotes@MissingFigureDown}{figure} +\newcommand{\@todonotes@SetTodoListName}[1] + {\renewcommand{\@todonotes@todolistname}{#1}} +\newcommand{\@todonotes@SetMissingFigureText}[1] + {\renewcommand{\@todonotes@MissingFigureText}{#1}} +\newcommand{\@todonotes@SetMissingFigureUp}[1] + {\renewcommand{\@todonotes@MissingFigureUp}{#1}} +\newcommand{\@todonotes@SetMissingFigureDown}[1] + {\renewcommand{\@todonotes@MissingFigureDown}{#1}} +\newif{\if@todonotes@reverseMissingFigureTriangle} +\DeclareOptionX{catalan}{ + \@todonotes@SetTodoListName{Llista de feines pendents}% + \@todonotes@SetMissingFigureText{Figura}% + \@todonotes@SetMissingFigureUp{Figura}% + \@todonotes@SetMissingFigureDown{pendent}% +} +\DeclareOptionX{danish}{% + \@todonotes@SetTodoListName{G\o{}rem\aa{}lsliste}% + \@todonotes@SetMissingFigureText{Figur}% + \@todonotes@SetMissingFigureUp{Manglende}% + \@todonotes@SetMissingFigureDown{figur}% +} +\DeclareOptionX{dutch}{% + \@todonotes@SetTodoListName{Lijst van onafgewerkte taken}% + \@todonotes@SetMissingFigureText{Figuur}% + \@todonotes@SetMissingFigureUp{Ontbrekende}% + \@todonotes@SetMissingFigureDown{figuur}% +} +\DeclareOptionX{english}{% + \@todonotes@SetTodoListName{Todo list}% + \@todonotes@SetMissingFigureText{Figure}% + \@todonotes@SetMissingFigureUp{Missing}% + \@todonotes@SetMissingFigureDown{figure}% +} +\DeclareOptionX{french}{% + \@todonotes@SetTodoListName{Liste des points \`a traiter}% + \@todonotes@SetMissingFigureText{Figure}% + \@todonotes@SetMissingFigureUp{Figure}% + \@todonotes@SetMissingFigureDown{manquante}% + \@todonotes@reverseMissingFigureTrianglefalse +} +\DeclareOptionX{german}{% + \@todonotes@SetTodoListName{Liste der noch zu erledigenden Punkte}% + \@todonotes@SetMissingFigureText{Abbildung}% + \@todonotes@SetMissingFigureUp{Fehlende}% + \@todonotes@SetMissingFigureDown{Abbildung}% +} +\DeclareOptionX{italian}{ + \@todonotes@SetTodoListName{Elenco delle cose da fare}% + \@todonotes@SetMissingFigureText{Figura}% + \@todonotes@SetMissingFigureUp{Figura}% + \@todonotes@SetMissingFigureDown{mancante}% +} +\DeclareOptionX{ngerman}{% + \@todonotes@SetTodoListName{Liste der noch zu erledigenden Punkte}% + \@todonotes@SetMissingFigureText{Abbildung}% + \@todonotes@SetMissingFigureUp{Fehlende}% + \@todonotes@SetMissingFigureDown{Abbildung}% +} +\DeclareOptionX{portuguese}{ + \@todonotes@SetTodoListName{Lista de tarefas pendentes}% + \@todonotes@SetMissingFigureText{Figura}% + \@todonotes@SetMissingFigureUp{Figura}% + \@todonotes@SetMissingFigureDown{pendente}% +} +\DeclareOptionX{spanish}{ + \@todonotes@SetTodoListName{Lista de tareas pendientes}% + \@todonotes@SetMissingFigureText{Figura}% + \@todonotes@SetMissingFigureUp{Figura}% + \@todonotes@SetMissingFigureDown{pendiente}% +} +\DeclareOptionX{swedish}{% + \@todonotes@SetTodoListName{Att g\"{o}ra-lista}% + \@todonotes@SetMissingFigureText{Figur}% + \@todonotes@SetMissingFigureUp{Figur}% + \@todonotes@SetMissingFigureDown{saknas}% +} +% \end{macrocode} +% Create a counter, for storing the number of inserted todos. +% \begin{macrocode} +\newcounter{@todonotes@numberoftodonotes} +% \end{macrocode} +% Create a counter, for storing the number of lines in the current todoarea. +% \begin{macrocode} +\newcounter{@todonotes@numberofLinesInArea} +% \end{macrocode} +% Toggle whether the package should obey the global draft option. +% \begin{macrocode} +\newif{\if@todonotes@obeyDraft} +\DeclareOptionX{obeyDraft}{\@todonotes@obeyDrafttrue} +\newif{\if@todonotes@isDraft} +\DeclareOptionX{draft}{\@todonotes@isDrafttrue} +\DeclareOptionX{draftcls}{\@todonotes@isDrafttrue} +\DeclareOptionX{draftclsnofoot}{\@todonotes@isDrafttrue} +\newif{\if@todonotes@obeyFinal} +\DeclareOptionX{obeyFinal}{\@todonotes@obeyFinaltrue} +\newif{\if@todonotes@isFinal} +\DeclareOptionX{final}{\@todonotes@isFinaltrue} +% \end{macrocode} +% Make it possible to disable the functionality +% of the package. If this option is given, the +% commands |\todo{}| and |\listoftodos| are defined +% as commands with no effect. (But you can still +% compile you document with these commands). +% \begin{macrocode} +\newif{\if@todonotes@disabled} +\DeclareOptionX{disable}{\@todonotes@disabledtrue} +% \end{macrocode} +% Show small boxes in the list of todos with the color of the +% inserted todonotes. +% \begin{macrocode} +\newif{\if@todonotes@colorinlistoftodos} +\DeclareOptionX{colorinlistoftodos}{\@todonotes@colorinlistoftodostrue} +% \end{macrocode} +% We only define dvistyle for compatibility with todonotes. +% The option was intented for use with \texttt{tex}, there should be no problems +% using \texttt{luatex}. +% So we ignore this option and issue a warning. +% \begin{macrocode} +\DeclareOptionX{dvistyle}{\PackageWarningNoLine{luatodonotes} + {Parameter dvistyle is not supported by luatodonotes. + Ignoring this option}} +% \end{macrocode} +% Create a color option. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {color}{ + \renewcommand{\@todonotes@backgroundcolor}{#1} + \renewcommand{\@todonotes@linecolor}{#1}} +% \end{macrocode} +% Make the background color of the notes as +% an option. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {backgroundcolor}{\renewcommand{\@todonotes@backgroundcolor}{#1}} +% \end{macrocode} +% Make the line color of the notes as +% an option. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {linecolor}{\renewcommand{\@todonotes@linecolor}{#1}} +% \end{macrocode} +% Make the color of the notes box color as +% an option. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {bordercolor}{\renewcommand{\@todonotes@bordercolor}{#1}} +% \end{macrocode} +% Make the width of the leader line as +% an option. +% It is later set as \texttt{line width} in TikZ. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {leaderwidth}{\renewcommand{\@todonotes@leaderwidth}{#1}} +% \end{macrocode} +% Set whether short captions given as arguments to the todo command +% should be included in the inserted todonote. +% \begin{macrocode} +\newif{\if@todonotes@prependcaptionglobal} +\@todonotes@prependcaptionglobalfalse +\DeclareOptionX{prependcaption}{\@todonotes@prependcaptionglobaltrue} +% \end{macrocode} +% This option is only there for compatibility with todonotes. +% We ignore it and issue a warning because the width of our labels is +% determined dynamically based on the page layout. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {textwidth}{\PackageWarningNoLine{luatodonotes} + {Parameter textwidth is not supported by luatodonotes}} +% \end{macrocode} +% Make the text size as an option. It requires some magic with the +% |\csname| and |\endcsname| macros, as commands cannot be taken as +% options for a package. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {textsize}{\renewcommand{\@todonotes@textsize}{\csname #1\endcsname}} +% \end{macrocode} +% Add option for shadows behind the inserted notes +% \begin{macrocode} +\newif{\if@todonotes@shadowenabled} +\@todonotes@shadowenabledfalse +\DeclareOptionX{shadow}{\@todonotes@shadowenabledtrue +\usetikzlibrary{shadows}} +% \end{macrocode} +% Add option for the default width of the figure inserted with +% |\missingfigure|. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {figwidth}{\renewcommand{\@todonotes@figwidth}{#1}} +\define@key{luatodonotes.sty}% + {figheight}{\renewcommand{\@todonotes@figheight}{#1}} +% \end{macrocode} +% Specify the name of the algorithm used to specify the position of the labels. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {positioning}{\renewcommand{\@todonotes@positioning}{#1}} +% \end{macrocode} +% Specify the name of the algorithm used to split the notes for left and right side. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {splitting}{\renewcommand{\@todonotes@splitting}{#1}} +% \end{macrocode} +% Specify the type of leaders that are drawn. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {leadertype}{\renewcommand{\@todonotes@leadertype}{#1}} +% \end{macrocode} +% Specify the vertical distance between the notes. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {interNoteSpace}{\renewcommand{\@todonotes@interNoteSpace}{#1}} +% \end{macrocode} +% Specify the distance from the text inside the notes to the border. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {noteInnerSep}{\renewcommand{\@todonotes@noteInnerSep}{#1}} +% \end{macrocode} +% Specify the width of the routing area used for $opo$- and $os$-leaders. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {routingAreaWidth}{\renewcommand{\@todonotes@routingAreaWidth}{#1}} +% \end{macrocode} +% Minimum width of notes in one margin beside the text to be considered for +% label placement. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {minNoteWidth}{\renewcommand{\@todonotes@minNoteWidth}{#1}} +% \end{macrocode} +% Specify horizontal distance from the notes to the borders of the page. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {distanceNotesPageBorder}% + {\renewcommand{\@todonotes@distanceNotesPageBorder}{#1}} +% \end{macrocode} +% Specify the horizontal distance between the notes and the text area. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {distanceNotesText}{\renewcommand{\@todonotes@distanceNotesText}{#1}} +% \end{macrocode} +% Specify the height of the raster used for the $po$-leader algorithm. +% \begin{macrocode} +\define@key{luatodonotes.sty}% + {rasterHeight}{\renewcommand{\@todonotes@rasterHeight}{#1}} +% \end{macrocode} +% This option is used to activate debug mode. +% Luatex prints more verbose output to the commandline in this mode. +% Furthermore, some of the algorithms also print debugging hints onto the +% output page. +% \begin{macrocode} +\newif{\if@todonotes@debugenabled} +\@todonotes@debugenabledfalse +\DeclareOptionX{debug}{\@todonotes@debugenabledtrue} +% \end{macrocode} +% Finally process the given options. +% \begin{macrocode} +\ProcessOptionsX* +% \end{macrocode} +% If the |obeyDraft| is given, check whether one of the |draft|, +% |draftcls| or |draftclsnofoot| +% options are given and enable or disable the functionality of this +% package. +% If the |obeyFinal| option is given together with the |final| option +% the todonotes are disabled. +% The |disable| option will overrule the effect of |obeyDraft|. +% \begin{macrocode} +\if@todonotes@disabled +\else + \if@todonotes@obeyDraft + \@todonotes@disabledtrue + \if@todonotes@isDraft + \@todonotes@disabledfalse + \fi + \fi + \if@todonotes@obeyFinal + \@todonotes@disabledfalse + \if@todonotes@isFinal + \@todonotes@disabledtrue + \fi + \fi +\fi +% \end{macrocode} +% \subsection{Initialisation of our Lua code} +% In this part we define some of the variables used by Lua depending on the +% package options and do some other initialisation tasks. +% +% We first need some temporary dimensions, which are written by \TeX\ and read +% from Lua. +% We use dimensions here because it is easier to access \TeX\ dimensions from +% Lua than \LaTeX\ lengths. +% We use |tex.dimen| in Lua to access dimensions. +% The first dimensions are used when extracting the absolute coordinates of a +% position on the page. +% \begin{macrocode} +\newdimen\@todonotes@extractx +\newdimen\@todonotes@extracty +% \end{macrocode} +% The following savebox and dimensions are used to calculate the height of a +% certain label. +% The box and dimensions are filled by \TeX\ and then read from Lua. +% \begin{macrocode} +\newsavebox\@todonotes@heightcalcbox +\newdimen\@todonotes@heightcalcboxdepth +\newdimen\@todonotes@heightcalcboxheight +% \end{macrocode} +% The following savebox is used to store the contents of a note and is then +% read from Lua. +% \begin{macrocode} +\newsavebox\@todonotes@notetextbox +% \end{macrocode} +% The following dimensions are used to read |\baselineskip| and +% |\f@size| from Lua. +% Dimension |\@todonotes@currentsidemargin| is set to the left margin, +% i.\,e., to the value of length |\oddsidemargin| or +% |\evensidemargin| depending on the type page. +% \begin{macrocode} +\newdimen\@todonotes@baselineskip +\newdimen\@todonotes@fontsize +\newdimen\@todonotes@currentsidemargin +% \end{macrocode} +% Loading our main Lua file. +% \begin{macrocode} +\directlua{require("luatodonotes")} +% \end{macrocode} +% Setting variables to values given by package options. +% \begin{macrocode} +\directlua{noteInnerSep = + string.todimen("\luatexluaescapestring{\@todonotes@noteInnerSep}")} +\directlua{noteInterSpace = + string.todimen("\luatexluaescapestring{\@todonotes@interNoteSpace}")} +\directlua{routingAreaWidth = + string.todimen("\luatexluaescapestring{\@todonotes@routingAreaWidth}")} +\directlua{minNoteWidth = + string.todimen("\luatexluaescapestring{\@todonotes@minNoteWidth}")} +\directlua{distanceNotesPageBorder = + string.todimen("\luatexluaescapestring{\@todonotes@distanceNotesPageBorder}")} +\directlua{distanceNotesText = + string.todimen("\luatexluaescapestring{\@todonotes@distanceNotesText}")} +\directlua{rasterHeight = + string.todimen("\luatexluaescapestring{\@todonotes@rasterHeight}")} +% \end{macrocode} +% Set the variable for the positioning algorithm depending on the corresponding +% package option. +% \begin{macrocode} +\IfStrEqCase{\@todonotes@positioning}{% + {inText}{\directlua{positioning = positioningAlgos["inText"]}}% + {inputOrder}{\directlua{positioning = positioningAlgos["inputOrder"]}}% + {inputOrderStacks}{\directlua{positioning = + positioningAlgos["inputOrderStacks"]}}% + {sLeaderNorthEast}{\directlua{positioning = + positioningAlgos["sLeaderNorthEast"]}}% + {sLeaderNorthEastBelow}{\directlua{positioning = + positioningAlgos["sLeaderNorthEastBelow"]}}% + {sLeaderEast}{\directlua{positioning = + positioningAlgos["sLeaderEast"]}}% + {poLeaders}{\directlua{positioning = positioningAlgos["poLeaders"]}}% + {poLeadersAvoidLines}{\directlua{positioning = + positioningAlgos["poLeadersAvoidLines"]}}% + {sLeaderNorthEastBelowStacks}{\directlua{positioning = + positioningAlgos["sLeaderNorthEastBelowStacks"]}}}% + [\directlua{positioning = positioningAlgos["inputOrderStacks"]} + \PackageWarningNoLine{luatodonotes} + {Invalid value for parameter positioning: \@todonotes@positioning}] +% \end{macrocode} +% Set the variable for the splitting algorithm depending on the corresponding +% package option. +% \begin{macrocode} +\IfStrEqCase{\@todonotes@splitting}{% + {none}{\directlua{splitting = splittingAlgos["none"]}}% + {middle}{\directlua{splitting = splittingAlgos["middle"]}}% + {median}{\directlua{splitting = splittingAlgos["median"]}}% + {weightedMedian}{\directlua{splitting = splittingAlgos["weightedMedian"]}}}% + [\directlua{splitting = splittingAlgos["none"]} + \PackageWarningNoLine{luatodonotes} + {Invalid value for parameter split: \@todonotes@splitting}] +% \end{macrocode} +% Specify the requested leader type. +% \begin{macrocode} +\IfStrEqCase{\@todonotes@leadertype}{% + {s}{\directlua{leaderType = leaderTypes["s"]}}% + {opo}{\directlua{leaderType = leaderTypes["opo"]}}% + {po}{\directlua{leaderType = leaderTypes["po"]}}% + {sBezier}{\directlua{leaderType = leaderTypes["sBezier"]}}% + {os}{\directlua{leaderType = leaderTypes["os"]}}}% + [\directlua{leaderType = leaderTypes["opo"]} + \PackageWarningNoLine{luatodonotes} + {Invalid value for parameter leadertype: \@todonotes@leadertype}] +% \end{macrocode} +% The following commands are used to detect the absolute positions of lines on +% the page. +% +% We first need to define a command to be able to insert the position from +% |\pdflastypos| into a write-whatsit in Lua. +% We need this workaround because we cannot insert |\pdflastypos| directly into +% the tokenlist in the Lua callback \texttt{callbackOutputLinePositions()}. +% \begin{macrocode} +\def\@todonotes@pdflastypos{\the\pdflastypos} +% \end{macrocode} +% The following commands are written to the temporary \texttt{lpo}-file. +% When reading this file we call a Lua function for each line in the file and +% thus can collect the line positions in a Lua table. +% \begin{macrocode} +\newcommand{\@todonotes@lineposition}[3]{% + \directlua{linePositionsAddLine(#1,#2,#3)}% +} +\newcommand{\@todonotes@nextpage}{% + \directlua{linePositionsNextPage()}% +}% +% \end{macrocode} +% The following macro is used in \texttt{AtBeginShipout} to signal in the +% \texttt{lpo}-file that a new page is started. +% \begin{macrocode} +\newcommand{\@todonotes@writeNextpageToLpo}{% + \ifdefined\tf@lpo% + \immediate\write\tf@lpo{\@backslashchar @todonotes@nextpage}% + \fi +} +% \end{macrocode} +% Depending on the debug-option of the package we set the corresponding Lua +% variable here. +% Additionally, we prepare to print our notes and leaders in foreground when in +% debug mode. +% \begin{macrocode} +\if@todonotes@debugenabled + \directlua{todonotesDebug = true} + \newcommand{\@todonotes@AtBeginShipoutUpperLeft} + {\AtBeginShipoutUpperLeftForeground} +\else + \directlua{todonotesDebug = false} + \newcommand{\@todonotes@AtBeginShipoutUpperLeft} + {\AtBeginShipoutUpperLeft} +\fi +% \end{macrocode} +% Initialise the script when all Lua variables are set according to the package +% options. +% \begin{macrocode} +\directlua{initTodonotes()} +% \end{macrocode} +% Some definitions to highlight areas in text. +% The first command is needed to accept control spaces (|\ |) in arguments for +% soul commands. +% After that we define the highlighting command used for todoareas. +% \begin{macrocode} +\soulregister{\ }{0} +\newlength{\todonotes@textmark@width} +\newlength{\todonotes@textmark@fontsize} +\newlength{\todonotes@textmark@linebelow} +\newlength{\todonotes@textmark@lineabove} +\ulposdef{\todonotes@textmark@highlight}{% + \setlength\todonotes@textmark@width\ulwidth% + \setlength\todonotes@textmark@fontsize{\f@size pt}% + \stepcounter{@todonotes@numberofLinesInArea}% + \ifulstarttype{0}% + {% begin of area + \def\todonotes@textmark@decoLeft{}% + \def\todonotes@textmark@shift{-2pt}% + \addtolength\todonotes@textmark@width{2pt}% + \setcounter{@todonotes@numberofLinesInArea}{1}}% + {\def\todonotes@textmark@decoLeft{@todonotes@todoarea}% + \def\todonotes@textmark@shift{-4pt}% + \addtolength\todonotes@textmark@width{4pt}}% + \ifulendtype{0}% + {% last line of area + \def\todonotes@textmark@decoRight{}% + \addtolength\todonotes@textmark@width{2pt}% + \directlua{processLastLineInTodoArea()}}% + {\def\todonotes@textmark@decoRight{@todonotes@todoarea}% + \addtolength\todonotes@textmark@width{4pt}}% + \newcommand{\@todonotes@nodeNamePrefix}% + {@todonotes@\arabic{@todonotes@numberoftodonotes}% + @\arabic{@todonotes@numberofLinesInArea} }% + \hspace*{\todonotes@textmark@shift}{\smash{% + \begin{tikzpicture}[overlay,remember picture, + deco/.style={}]% + \setlength\todonotes@textmark@linebelow% + {-0.95\dimexpr\baselineskip-\f@size pt\relax}% + \setlength\todonotes@textmark@lineabove% + {\dimexpr\f@size pt+\todonotes@textmark@linebelow\relax}% + \coordinate + (\@todonotes@nodeNamePrefix areaSW) + at (0,\todonotes@textmark@linebelow); + \coordinate + (\@todonotes@nodeNamePrefix areaSE) + at (\todonotes@textmark@width, \todonotes@textmark@linebelow); + \coordinate + (\@todonotes@nodeNamePrefix areaNE) + at (\todonotes@textmark@width,\todonotes@textmark@lineabove); + \coordinate + (\@todonotes@nodeNamePrefix areaNW) + at (0,\todonotes@textmark@lineabove); + \draw[draw=green!70,fill=green,fill opacity=.2] + (\@todonotes@nodeNamePrefix areaSW) + decorate[\todonotes@textmark@decoLeft] { + -- (\@todonotes@nodeNamePrefix areaNW) + } + -- (\@todonotes@nodeNamePrefix areaNE) + decorate[\todonotes@textmark@decoRight] { + -- (\@todonotes@nodeNamePrefix areaSE) + } + -- cycle; + \end{tikzpicture}% + }}% +}% +% \end{macrocode} +% \subsection{Options for the todo command} +% +% In this part the various options for +% commands in the package are defined. +% Set an arbitrarily fill color +% \begin{macrocode} +\newcommand{\@todonotes@currentlinecolor}{}% +\newcommand{\@todonotes@currentbackgroundcolor}{}% +\newcommand{\@todonotes@currentbordercolor}{}% +\define@key{todonotes}{color}{% + \renewcommand{\@todonotes@currentlinecolor}{#1}% + \renewcommand{\@todonotes@currentbackgroundcolor}{#1}}% +\define@key{todonotes}{linecolor}{% + \renewcommand{\@todonotes@currentlinecolor}{#1}}% +\define@key{todonotes}{backgroundcolor}{% + \renewcommand{\@todonotes@currentbackgroundcolor}{#1}}% +\define@key{todonotes}{bordercolor}{% + \renewcommand{\@todonotes@currentbordercolor}{#1}}% +\newcommand{\@todonotes@currentleaderwidth}{}% +\define@key{todonotes}{leaderwidth}{% + \renewcommand{\@todonotes@currentleaderwidth}{#1}}% +% \end{macrocode} +% Set a relative font size +% \begin{macrocode} +\newcommand{\@todonotes@sizecommand}{}% +\define@key{todonotes}{size}{\renewcommand{\@todonotes@sizecommand}{#1}}% +% \end{macrocode} +% Should the todo item be disabled? +% \begin{macrocode} +\newif\if@todonotes@localdisable% +\define@key{todonotes}{disable}[]{\@todonotes@localdisabletrue}% +\define@key{todonotes}{nodisable}[]{\@todonotes@localdisablefalse}% +% \end{macrocode} +% Should the todo item be included in the list of todos? +% \begin{macrocode} +\newif\if@todonotes@appendtolistoftodos% +\define@key{todonotes}{list}[]{\@todonotes@appendtolistoftodostrue}% +\define@key{todonotes}{nolist}[]{\@todonotes@appendtolistoftodosfalse}% +% \end{macrocode} +% Should the todo item be displayed inline? +% \begin{macrocode} +\newif\if@todonotes@inlinenote% +\define@key{todonotes}{inline}[]{\@todonotes@inlinenotetrue}% +\define@key{todonotes}{noinline}[]{\@todonotes@inlinenotefalse}% +% \end{macrocode} +% \begin{macrocode} +\newif\if@todonotes@prependcaption% +\define@key{todonotes}{prepend}[]{\@todonotes@prependcaptiontrue}% +\define@key{todonotes}{noprepend}[]{\@todonotes@prependcaptionfalse}% +% \end{macrocode} +% Should the note in the margin be connected to the insertion point +% in the text? +% \begin{macrocode} +\newif\if@todonotes@line% +\define@key{todonotes}{line}[]{\@todonotes@linetrue}% +\define@key{todonotes}{noline}[]{\@todonotes@linefalse}% +% \end{macrocode} +% Only here for compatibility with todonotes. +% We don't need the fancy lines because we have more advanced drawing styles. +% So we ignore this option and issue a warning. +% \begin{macrocode} +\define@key{todonotes}{fancyline}[]{\PackageWarningNoLine{luatodonotes} + {Parameter fancyline is not supported by luatodonotes}}% +\define@key{todonotes}{nofancyline}[]{}% +% \end{macrocode} +% Author option. +% \begin{macrocode} +\newcommand{\@todonotes@author}{}% +\newif\if@todonotes@authorgiven% +\define@key{todonotes}{author}{% + \renewcommand{\@todonotes@author}{#1}% + \@todonotes@authorgiventrue}% +\define@key{todonotes}{noauthor}[]{\@todonotes@authorgivenfalse}% +% \end{macrocode} +% Should the text in the list of todos be different from the text +% in the todonote? +% \begin{macrocode} +\newcommand{\@todonotes@caption}{}% +\newif\if@todonotes@captiongiven% +\define@key{todonotes}{caption}% + {\renewcommand{\@todonotes@caption}{#1}% + \@todonotes@captiongiventrue}% +\define@key{todonotes}{nocaption}[]{\@todonotes@captiongivenfalse}% +% \end{macrocode} +% Change the current figure width and height. +% \begin{macrocode} +\newcommand{\@todonotes@currentfigwidth}{\@todonotes@figwidth} +\define@key{todonotes}% + {figwidth}{\renewcommand{\@todonotes@currentfigwidth}{#1}} +\newcommand{\@todonotes@currentfigheight}{\@todonotes@figheight} +\define@key{todonotes}% + {figheight}{\renewcommand{\@todonotes@currentfigheight}{#1}} +% \end{macrocode} +% Preset values of the options +% \begin{macrocode} +\presetkeys% + {todonotes}% + {linecolor=\@todonotes@linecolor,% + backgroundcolor=\@todonotes@backgroundcolor,% + bordercolor=\@todonotes@bordercolor,% + leaderwidth=\@todonotes@leaderwidth,% + nodisable,% + noinline,% + nocaption,% + noauthor,% + figwidth=\@todonotes@figwidth,% + figheight=\@todonotes@figheight,% + line, list, size=\@todonotes@textsize}{}% +% \end{macrocode} +% \subsection{The main code part} +% Here are the actual macros defined. +% The following boolean is used to remember if |\todo| or |\todoarea| +% was called. +% \begin{macrocode} +\newif\if@todonotes@areaselected% +% \end{macrocode} +% The following token registers are used to access the data for a note (which +% is stored in macros) from Lua. +% \begin{macrocode} +\newtoks\@todonotes@toks@currentlinecolor% +\newtoks\@todonotes@toks@currentbackgroundcolor% +\newtoks\@todonotes@toks@currentbordercolor% +\newtoks\@todonotes@toks@currentleaderwidth% +\newtoks\@todonotes@toks@sizecommand% +% \end{macrocode} +% If the option "disable" was passed to the package +% define empty commands. +% \begin{macrocode} +\if@todonotes@disabled% + \newcommand{\listoftodos}[1][]{} + \newcommand{\@todo}[2][]{} + \newcommand{\@todoarea}[3][]{} + \newcommand{\missingfigure}[2][]{} +\else % \if@todonotes@disabled +% \end{macrocode} +% Define the |\listoftodos| command and define the +% appearance of the list of todos. +% \begin{macrocode} +\newcommand{\listoftodos}[1][\@todonotes@todolistname] + {\@ifundefined{chapter}{\section*{#1}}{\chapter*{#1}} \@starttoc{tdo}} +\newcommand{\l@todo} + {\@dottedtocline{1}{0em}{2.3em}} +% \end{macrocode} +% Define styles used by the todo command. +% Colors are set directly when placing the notes. +% \begin{macrocode} +\tikzset{@todonotes@todoarea/.style={ + decoration={snake,amplitude=3.5pt,segment length=5pt}}} +\tikzset{@todonotes@notestyleraw/.style={ + line width=0.5pt, + inner sep = \@todonotes@noteInnerSep, + rounded corners=4pt}} +% \end{macrocode} +% Add shadows and rounded corners to the inserted todonotes. +% \begin{macrocode} +\if@todonotes@shadowenabled + \tikzset{@todonotes@notestyle/.style={@todonotes@notestyleraw, + general shadow={shadow xshift=.5ex, shadow yshift=-.5ex, + opacity=1,fill=black!50}}} +\else + \tikzset{@todonotes@notestyle/.style={@todonotes@notestyleraw}} +\fi +\tikzset{@todonotes@leader/.style={}} +\tikzset{@todonotes@textmark/.style={rounded corners}} +\tikzset{@todonotes@inlinenote/.style={ + @todonotes@notestyle, + draw=\@todonotes@currentbordercolor, + fill=\@todonotes@currentbackgroundcolor, + text width=\linewidth - 1.6 ex - 1 pt}} +% \end{macrocode} +% \begin{macro}{\@todocommon} +% Common macro used from |\@todo| and |\@todoarea|. +% Used to actually draw/save the note. +% \begin{macrocode} +\newcommand{\@todocommon}[2]{% +% \end{macrocode} +% Use the global value for determining the default prepend behavior. +% \begin{macrocode} +\if@todonotes@prependcaptionglobal% +\@todonotes@prependcaptiontrue% +\else% +\@todonotes@prependcaptionfalse% +\fi% +% \end{macrocode} +% Store the original text for later usage and parse the given options. +% \begin{macrocode} +\renewcommand{\@todonotes@text}{#2}% +\renewcommand{\@todonotes@caption}{#2}% +\setkeys{todonotes}{#1}% +% \end{macrocode} +% If the option |disable| is given to the command, no output is generated. +% \begin{macrocode} +\if@todonotes@localdisable% +\else% +% \end{macrocode} +% Add the item to the list of todos. When the option +% |colorinlistoftodos| is given to the package a small colored +% square is added in front of the text. +% \begin{macrocode} +\addtocounter{@todonotes@numberoftodonotes}{1}% +\if@todonotes@appendtolistoftodos% + \phantomsection% + \if@todonotes@captiongiven% + \else% + \renewcommand{\@todonotes@caption}{#2}% + \fi% + \@todonotes@addElementToListOfTodos% +\fi% +% \end{macrocode} +% Prepend the short caption given if it is requested +% \begin{macrocode} +\if@todonotes@captiongiven% + \if@todonotes@prependcaption% + \renewcommand{\@todonotes@text}{\@todonotes@caption: #2}% + \fi% +\fi% +% \end{macrocode} +% Place the todonote as indicated by the options (inline or in a +% marginpar), below is the code for the inline placement. +% \begin{macrocode} +\if@todonotes@inlinenote% + \@todonotes@drawInlineNote% +\else% + \@todonotes@drawMarginNoteWithLine% +\fi%\if@todonotes@inlinenote +\fi%\if@todonotes@localdisable +}% +% \end{macrocode} +% \end{macro} +% \begin{macro}{\@todo} +% Command that draws normal notes. +% \begin{macrocode} +\newcommand{\@todo}[2][]{% + \@todonotes@areaselectedfalse% + \@todocommon{#1}{#2}% +}% +% \end{macrocode} +% \end{macro} +% \begin{macro}{\@todoarea} +% Command that draws notes that highlight a certain area in text. +% \begin{macrocode} +\newcommand{\@todoarea}[3][]{% + \@todonotes@areaselectedtrue% + \@todocommon{#1}{#2}% + \todonotes@textmark@highlight{#3}% +% \end{macrocode} +% Mark the end of the highlighted area with a Tikz coordinate. +% The begin is marked by |\@todocommon|. +% \begin{macrocode} + \begin{tikzpicture}[remember picture, overlay]% + \node [coordinate] (@todonotes@\arabic{@todonotes@numberoftodonotes} % + inTextEnd) {};% + \end{tikzpicture}% + \zref@label{@todonotes@\arabic{@todonotes@numberoftodonotes}@end}% +}% +% \end{macrocode} +% \end{macro} +% \begin{macro}{drawMarginNoteWithLine} +% Define helper function |drawMarginNoteWithLine|. +% \begin{macrocode} +\newcommand{\@todonotes@drawMarginNoteWithLine}{% +% \end{macrocode} +% When the todonote should be placed inside a marginpar, the code +% below is applied. +% First is the current location in the document stored, this enables +% us later to connect this point with the inserted todonote. +% \begin{macrocode} + \begin{tikzpicture}[remember picture, overlay]% + \node [coordinate] (@todonotes@\arabic{@todonotes@numberoftodonotes} % + inText) {};% + \end{tikzpicture}% +% \end{macrocode} +% Update the dimensions to be accessed by Lua. +% \begin{macrocode} + \@todonotes@baselineskip=\baselineskip% + \@todonotes@fontsize=\f@size pt% +% \end{macrocode} +% Place a label at the site. +% We use this to query the page number, on which the note was placed. +% \begin{macrocode} + \zref@label{@todonotes@\arabic{@todonotes@numberoftodonotes}}% +% \end{macrocode} +% Append author before the note text if one is given. +% \begin{macrocode} + \if@todonotes@authorgiven% + \let\@todonotes@text@old=\@todonotes@text + \renewcommand{\@todonotes@text}{\@todonotes@author: \@todonotes@text@old}% + \fi% +% \end{macrocode} +% We use edef here to get these macros fully expanded. +% After that we write them to a toks register and read them from Lua. +% \begin{macrocode} + \edef\@todonotes@tmp{\@todonotes@currentlinecolor}% + \@todonotes@toks@currentlinecolor=\expandafter{\@todonotes@tmp}% + \edef\@todonotes@tmp{\@todonotes@currentbackgroundcolor}% + \@todonotes@toks@currentbackgroundcolor=\expandafter{\@todonotes@tmp}% + \edef\@todonotes@tmp{\@todonotes@currentbordercolor}% + \@todonotes@toks@currentbordercolor=\expandafter{\@todonotes@tmp}% + \edef\@todonotes@tmp{\@todonotes@currentleaderwidth}% + \@todonotes@toks@currentleaderwidth=\expandafter{\@todonotes@tmp}% +% \end{macrocode} +% We cannot fully expand the size command (using |\edef| causes errors when +% compiling). +% \begin{macrocode} + \@todonotes@toks@sizecommand=\expandafter{\@todonotes@sizecommand}% +% \end{macrocode} +% We store the text that should be shown in this note into a box and copy this +% box to a variable in Lua. +% \begin{macrocode} + \savebox\@todonotes@notetextbox{\@todonotes@sizecommand\@todonotes@text}% +% \end{macrocode} +% Prepare parameters and add the note to the list in Lua. +% \begin{macrocode} + \if@todonotes@line% + \def\@todonotes@param@drawLeader{true}% + \else% + \def\@todonotes@param@drawLeader{false}% + \fi% + \if@todonotes@areaselected% + \def\@todonotes@param@noteType{area}% + \else% + \def\@todonotes@param@noteType{}% + \fi% + \directlua{addNoteToList(\arabic{@todonotes@numberoftodonotes},% + \@todonotes@param@drawLeader,\luastringO{\@todonotes@param@noteType})}% +}% +% \end{macrocode} +% \end{macro} +% \begin{macro}{addElementToListOfTodos} +% Define helper function |addElementToListOfTodos|. +% \begin{macrocode} +\newcommand{\@todonotes@addElementToListOfTodos}{% + \if@todonotes@colorinlistoftodos% + \addcontentsline{tdo}{todo}{% + \fcolorbox{\@todonotes@currentbordercolor}% + {\@todonotes@currentbackgroundcolor}% + {\textcolor{\@todonotes@currentbackgroundcolor}{o}}% + \ \@todonotes@caption}% + \else% + \addcontentsline{tdo}{todo}{\@todonotes@caption}% + \fi}% +% \end{macrocode} +% \end{macro} +% \begin{macro}{drawInlineNote} +% Define helper function |drawInlineNote|. +% \begin{macrocode} +\newcommand{\@todonotes@drawInlineNote}{% + {\par\noindent\begin{tikzpicture}[remember picture]% + \draw node[@todonotes@inlinenote,font=\@todonotes@sizecommand]{% + \if@todonotes@authorgiven% + {\noindent \@todonotes@sizecommand % + \@todonotes@author:\,\@todonotes@text}% + \else% + {\noindent \@todonotes@sizecommand \@todonotes@text}% + \fi};% + \end{tikzpicture}\par}% + }% +% \end{macrocode} +% \end{macro} +% \begin{macro}{\missingfigure} +% Defines the |\missingfigure| macro. +% \begin{macrocode} +\newcommand{\missingfigure}[2][]{% +\setkeys{todonotes}{#1}% +\addcontentsline{tdo}{todo}{\@todonotes@MissingFigureText: #2}% +\par +\noindent +\begin{tikzpicture} +\draw[fill=black!40, draw = white, line width=0pt] + (-2, -2.5) rectangle +(\@todonotes@currentfigwidth, \@todonotes@currentfigheight); +\draw (2, -0.3) node[right, text + width=\@todonotes@currentfigwidth-4.5cm] {#2}; +\draw[red, fill=white, rounded corners = 5pt, line width=10pt] + (30:2cm) -- (150:2cm) -- (270:2cm) -- cycle; +\draw (0, 0.3) node {\@todonotes@MissingFigureUp}; +\draw (0, -0.3) node {\@todonotes@MissingFigureDown}; +\end{tikzpicture} +}% Ending \missingfigure command +\fi % Ending \@todonotes@ifdisabled +% \end{macrocode} +% \end{macro} +% \begin{macro}{\todototoc} +% Inserts a reference to the list of todos in the table of contents. If |chapter| is defined, +% |chapter| is used as level otherwise will |section| be used. +% The |\todototoc| command respects the disable option. +% \begin{macrocode} +\newcommand{\todototoc} +{ + \if@todonotes@disabled + \else + \addcontentsline{toc}{\@ifundefined{chapter}{section}{chapter}}{\@todonotes@todolistname} + \fi +} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\todo} +% Define the |\todo| command as a redirection to |\@todo|. +% \begin{macrocode} +\newcommand{\todo}[2][]{\@bsphack\@todo[#1]{#2}\@esphack\ignorespaces}% +% \end{macrocode} +% \end{macro} +% \begin{macro}{\todoarea} +% Define the |\todoarea| command as a redirection to |\@todoarea|. +% We don't want to ignore spaces after this command. +% \begin{macrocode} +\newcommand{\todoarea}[3][]{\@bsphack\@todoarea[#1]{#2}{#3}\@esphack}% +% \end{macrocode} +% \end{macro} +% The following commands are executed when a page is complete and is written to +% the output PDF (shipout in \TeX\ terms). +% The |\AtBeginShipout| command is provided by package atbegshi. +% \begin{macrocode} +\if@todonotes@disabled +\else +\AtBeginShipout{% +% \end{macrocode} +% We draw to the foreground or background of the page (depending if debug option +% is set for the package). +% \begin{macrocode} + \@todonotes@AtBeginShipoutUpperLeft{ + \@todonotes@writeNextpageToLpo +% \end{macrocode} +% Determine if we are on a left or on a right side (important for margins) and +% set variables accordingly. +% |\relax| seems to be needed at end to really write new value for +% currentsidemargin. +% \begin{macrocode} + \checkoddpage% + \ifoddpageoroneside% + \@todonotes@currentsidemargin=\the\oddsidemargin% + \directlua{currentPageOdd = true}% + \else% + \@todonotes@currentsidemargin=\the\evensidemargin% + \directlua{currentPageOdd = false}% + \fi\relax% +% \end{macrocode} +% We switch to the default catcodes of \LaTeX\ here. +% This is important if catcodes are changed in the main text, e.\,g., by a +% verbatim environment at the end of the page. +% \begin{macrocode} + \BeginCatcodeRegime\CatcodeTableLaTeX +% \end{macrocode} +% Calculates the areas, in which the labels can be placed. +% This calculation depends on currentsidemargin. +% So this has to be done inside |\AtBeginShipoutUpperLeft| (otherwise odd/even +% page detection won't work). +% \begin{macrocode} + \directlua{calcLabelAreaDimensions()}% +% \end{macrocode} +% Calculates the needed height for every note. +% This has to be outside of the tikzpicture because it uses a savebox to compute +% the height. +% This box does not work in the tikzpicture. +% \begin{macrocode} + \directlua{calcHeightsForNotes()}% has to be outside of tikzpicture + \begin{tikzpicture}[remember picture,overlay] +% \end{macrocode} +% Reads the absolute coordinates of every note on the page and writes them to +% the Lua objects. +% \begin{macrocode} + \directlua{getInputCoordinatesForNotes()} +% \end{macrocode} +% Runs the positioning algorithm and actually draws the notes and leaders. +% \begin{macrocode} + \directlua{printNotes()} + \end{tikzpicture}% +% \end{macrocode} +% Delete the drawn notes from the Lua lists and prepare for the next page. +% \begin{macrocode} + \directlua{clearNotes()}% + \EndCatcodeRegime + }% +} +\fi % Ending \@todonotes@ifdisabled +% \end{macrocode} +% +% \newpage +% \Finale +\endinput + diff --git a/Master/texmf-dist/source/lualatex/luatodonotes/luatodonotes.ins b/Master/texmf-dist/source/lualatex/luatodonotes/luatodonotes.ins new file mode 100644 index 00000000000..779664abf56 --- /dev/null +++ b/Master/texmf-dist/source/lualatex/luatodonotes/luatodonotes.ins @@ -0,0 +1,65 @@ +%% +%% Copyright (C) 2014 by Fabian Lipp <fabian.lipp@gmx.de> +%% based on the todonotes package by +%% Henrik Skov Midtiby <henrikmidtiby@gmail.com> +%% +%% This file may be distributed and/or modified under the conditions of +%% the LaTeX Project Public License, either version 1.2 of this license +%% or (at your option) any later version. The latest version of this +%% license is in: +%% +%% http://www.latex-project.org/lppl.txt +%% +%% and version 1.2 or later is part of all distributions of LaTeX version +%% 1999/12/01 or later. +%% + +\input docstrip.tex +\keepsilent + +\usedir{tex/latex/luatodonotes} + +\preamble + +This is a generated file. + +Copyright (C) 2014 by Fabian Lipp <fabian.lipp@gmx.de> +based on the todonotes package by + Henrik Skov Midtiby <henrikmidtiby@gmail.com> + +This file may be distributed and/or modified under the conditions of +the LaTeX Project Public License, either version 1.2 of this license +or (at your option) any later version. The latest version of this +license is in: + + http://www.latex-project.org/lppl.txt + +and version 1.2 or later is part of all distributions of LaTeX version +1999/12/01 or later. + +\endpreamble + +\generate{\file{luatodonotes.sty}{\from{luatodonotes.dtx}{package}}} + +\obeyspaces +\Msg{*************************************************************} +\Msg{* *} +\Msg{* To finish the installation you have to move the following *} +\Msg{* files into a directory searched by TeX: *} +\Msg{* *} +\Msg{* luatodonotes.sty *} +\Msg{* luatodonotes.lua *} +\Msg{* path_line.lua *} +\Msg{* path_point.lua *} +\Msg{* inspect.lua *} +\Msg{* *} +\Msg{* To produce the documentation run the file todonotes.dtx *} +\Msg{* through LaTeX. *} +\Msg{* *} +\Msg{* Happy TeXing! *} +\Msg{* *} +\Msg{*************************************************************} + + +\endbatchfile + diff --git a/Master/texmf-dist/tex/lualatex/luatodonotes/inspect.lua b/Master/texmf-dist/tex/lualatex/luatodonotes/inspect.lua new file mode 100644 index 00000000000..638a673a708 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luatodonotes/inspect.lua @@ -0,0 +1,297 @@ +local inspect ={ + _VERSION = 'inspect.lua 2.0.0', + _URL = 'http://github.com/kikito/inspect.lua', + _DESCRIPTION = 'human-readable representations of tables', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2013 Enrique GarcĂa Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +-- Apostrophizes the string if it has quotes, but not aphostrophes +-- Otherwise, it returns a regular quoted string +local function smartQuote(str) + if str:match('"') and not str:match("'") then + return "'" .. str .. "'" + end + return '"' .. str:gsub('"', '\\"') .. '"' +end + +local controlCharsTranslation = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v" +} + +local function escapeChar(c) return controlCharsTranslation[c] end + +local function escape(str) + local result = str:gsub("\\", "\\\\"):gsub("(%c)", escapeChar) + return result +end + +local function isIdentifier(str) + return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) +end + +local function isArrayKey(k, length) + return type(k) == 'number' and 1 <= k and k <= length +end + +local function isDictionaryKey(k, length) + return not isArrayKey(k, length) +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + -- strings and numbers are sorted numerically/alphabetically + if ta == tb and (ta == 'string' or ta == 'number') then return a < b end + + local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] + -- Two default types are compared according to the defaultTypeOrders table + if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] + elseif dta then return true -- default types before custom ones + elseif dtb then return false -- custom types after default ones + end + + -- custom types are sorted out alphabetically + return ta < tb +end + +local function getDictionaryKeys(t) + local keys, length = {}, #t + for k,_ in pairs(t) do + if isDictionaryKey(k, length) then table.insert(keys, k) end + end + table.sort(keys, sortKeys) + return keys +end + +local function getToStringResultSafely(t, mt) + local __tostring = type(mt) == 'table' and rawget(mt, '__tostring') + local str, ok + if type(__tostring) == 'function' then + ok, str = pcall(__tostring, t) + str = ok and str or 'error: ' .. tostring(str) + end + if type(str) == 'string' and #str > 0 then return str end +end + +local maxIdsMetaTable = { + __index = function(self, typeName) + rawset(self, typeName, 0) + return 0 + end +} + +local idsMetaTable = { + __index = function (self, typeName) + local col = setmetatable({}, {__mode = "kv"}) + rawset(self, typeName, col) + return col + end +} + +local function countTableAppearances(t, tableAppearances) + tableAppearances = tableAppearances or setmetatable({}, {__mode = "k"}) + + if type(t) == 'table' then + if not tableAppearances[t] then + tableAppearances[t] = 1 + for k,v in pairs(t) do + countTableAppearances(k, tableAppearances) + countTableAppearances(v, tableAppearances) + end + countTableAppearances(getmetatable(t), tableAppearances) + else + tableAppearances[t] = tableAppearances[t] + 1 + end + end + + return tableAppearances +end + +local function parse_filter(filter) + if type(filter) == 'function' then return filter end + -- not a function, so it must be a table or table-like + filter = type(filter) == 'table' and filter or {filter} + local dictionary = {} + for _,v in pairs(filter) do dictionary[v] = true end + return function(x) return dictionary[x] end +end + +local function makePath(path, key) + local newPath, len = {}, #path + for i=1, len do newPath[i] = path[i] end + newPath[len+1] = key + return newPath +end + +------------------------------------------------------------------- +function inspect.inspect(rootObject, options) + options = options or {} + local depth = options.depth or math.huge + local filter = parse_filter(options.filter or {}) + + local tableAppearances = countTableAppearances(rootObject) + + local buffer = {} + local maxIds = setmetatable({}, maxIdsMetaTable) + local ids = setmetatable({}, idsMetaTable) + local level = 0 + local blen = 0 -- buffer length + + local function puts(...) + local args = {...} + for i=1, #args do + blen = blen + 1 + buffer[blen] = tostring(args[i]) + end + end + + local function down(f) + level = level + 1 + f() + level = level - 1 + end + + local function tabify() + puts("\n", string.rep(" ", level)) + end + + local function commaControl(needsComma) + if needsComma then puts(',') end + return true + end + + local function alreadyVisited(v) + return ids[type(v)][v] ~= nil + end + + local function getId(v) + local tv = type(v) + local id = ids[tv][v] + if not id then + id = maxIds[tv] + 1 + maxIds[tv] = id + ids[tv][v] = id + end + return id + end + + local putValue -- forward declaration that needs to go before putTable & putKey + + local function putKey(k) + if isIdentifier(k) then return puts(k) end + puts( "[" ) + putValue(k, {}) + puts("]") + end + + local function putTable(t, path) + if alreadyVisited(t) then + puts('<table ', getId(t), '>') + elseif level >= depth then + puts('{...}') + else + if tableAppearances[t] > 1 then puts('<', getId(t), '>') end + + local dictKeys = getDictionaryKeys(t) + local length = #t + local mt = getmetatable(t) + local to_string_result = getToStringResultSafely(t, mt) + + puts('{') + down(function() + if to_string_result then + puts(' -- ', escape(to_string_result)) + if length >= 1 then tabify() end -- tabify the array values + end + + local needsComma = false + for i=1, length do + needsComma = commaControl(needsComma) + puts(' ') + putValue(t[i], makePath(path, i)) + end + + for _,k in ipairs(dictKeys) do + needsComma = commaControl(needsComma) + tabify() + putKey(k) + puts(' = ') + putValue(t[k], makePath(path, k)) + end + + if mt then + needsComma = commaControl(needsComma) + tabify() + puts('<metatable> = ') + putValue(mt, makePath(path, '<metatable>')) + end + end) + + if #dictKeys > 0 or mt then -- dictionary table. Justify closing } + tabify() + elseif length > 0 then -- array tables have one extra space before closing } + puts(' ') + end + + puts('}') + end + + end + + -- putvalue is forward-declared before putTable & putKey + putValue = function(v, path) + if filter(v, path) then + puts('<filtered>') + else + local tv = type(v) + + if tv == 'string' then + puts(smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then + puts(tostring(v)) + elseif tv == 'table' then + putTable(v, path) + else + puts('<',tv,' ',getId(v),'>') + end + end + end + + putValue(rootObject, {}) + + return table.concat(buffer) +end + +setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end }) + +return inspect + diff --git a/Master/texmf-dist/tex/lualatex/luatodonotes/luatodonotes.lua b/Master/texmf-dist/tex/lualatex/luatodonotes/luatodonotes.lua new file mode 100644 index 00000000000..61175bbbf68 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luatodonotes/luatodonotes.lua @@ -0,0 +1,2160 @@ +-- +-- Copyright (C) 2014 by Fabian Lipp <fabian.lipp@gmx.de> +-- ------------------------------------------------------- +-- +-- This file may be distributed and/or modified under the +-- conditions of the LaTeX Project Public License, either version 1.2 +-- of this license or (at your option) any later version. +-- The latest version of this license is in: +-- +-- http://www.latex-project.org/lppl.txt +-- +-- and version 1.2 or later is part of all distributions of LaTeX +-- version 1999/12/01 or later. +-- + +require("lualibs") +--require("debugger")() +local inspect = require('inspect') + +local point = require'path_point' +local pathLine = require'path_line' +--local bezier3 = require'path_bezier3' + +-- TODO Funktionen/Variablen in Namespace o. Ă„. packen + +-- strings used to switch to standard catcodes for LaTeX packages +local catcodeStart = "\\makeatletter" +local catcodeEnd = "\\makeatother" + +local currentPage = 1 + +local const1In = string.todimen("1in") -- needed for calculations of page borders + -- (used as a constant in TeX) +-- constants set in sty-file +-- + noteInnerSep (inner sep used for tikz nodes) +-- + noteInterSpace (vertical space between notes) +-- + routingAreaWidth (width of the track routing area for opo-leaders) +-- + minNoteWidth (width that must be available for labels to consider the left or +-- right border of the page for placing labels) +-- + distanceNotesPageBorder (distance from the page borders to the outmost point +-- of the labels) +-- + distanceNotesText (horizontal distance between the labels and the text area) +-- + todonotesDebug (activate debug outputs when true) + + +-- stores information about available algorithms +positioningAlgos = {} +splittingAlgos = {} +leaderTypes = {} + +-- stores the notes for the current page +notesForPage = {} +-- Fields for each note: +-- index: numbers notes in whole document +-- indexOnPage: index of the note in the notesForPage array +-- textbox: links to a hbox that contains the text, which is displayed inside +-- the note +-- origInputX, origInputY: position in which the todo-command was issued +-- inputX, inputY: position to which the leader should be attached (can have a +-- certain offset to origInputX/Y) +-- heightLeft, heightRight: height of the contained text when placed on +-- left/right side +-- pageNr: absolute number of page on which site for note is placed +-- rightSide: true means the note should be placed on the right side; +-- otherwise left side is meant +-- fontsize: fontsize used for paragraph in that the note was defined +-- baselineskip: \baselineskip in the paragraph in that the note was defined +-- outputX, outputY: position on which the north west anchor of the note should +-- be placed +-- lineColor: color of line connecting note to text +-- backgroundColor: color of background of note +-- borderColor: color of border of note +-- leaderWidth: width of leader (used as argument for tikz line width) +-- +-- Additional fields for text area: +-- noteType: constant string "area" +-- origInputEndX, origInputEndY: position at which the todo area ends +-- pageNrEnd: absolute number of page on which todo area ends +-- lineCountInArea: highest line index in area +-- linesInArea: positions of lines in area (to detect page/column break) + +-- stores the areas for the labels on the current page +-- (calculated in function calcLabelAreaDimensions()) +local labelArea = {} + +-- stores the positions of the text lines on every page +local linePositions = {} +-- Fields for every position: +-- 1: position of baseline +-- 2: upper bound of line (baseline + height) +-- 3: lower bound of line (baseline - depth) + +-- variables used for construction of linePositions list when reading the file +local linePositionsCurPage = {} +local linePositionsPageNr = 0 + + +-- *** metatable for note objects *** +local noteMt = {} +-- getHeight(): yield heightLeft or heightRight depending on rightSide (implemented by metatable) +function noteMt:getHeight() + if self.rightSide then + return self.heightRight + else + return self.heightLeft + end +end +function noteMt:getLabelAnchorY() + local leaderAnchor = positioning.leaderAnchor + local y + if leaderAnchor == "north east" then + y = self.outputY + elseif leaderAnchor == "east" then + y = self.outputY - noteInnerSep - self:getHeight() / 2 + else + error("Invalid anchor for algorithm") + end + + if positioning.leaderShift then + y = y + self.leaderShiftY + end + + return y +end +function noteMt:getInTextAnchorTikz() + return "(" .. self.inputX .. "sp," .. self.inputY .. "sp)" +end +function noteMt:getLabelAnchorTikz() + local leaderAnchor = positioning.leaderAnchor + if leaderAnchor == "north east" and self.rightSide then + leaderAnchor = "north west" + elseif leaderAnchor == "east" and self.rightSide then + leaderAnchor = "west" + end + + local shiftStr = "" + if positioning.leaderShift then + shiftStr = "[shift={(" .. self.leaderShiftX .. "sp," .. self.leaderShiftY .. "sp)}]" + end + + return "(" .. shiftStr .. " @todonotes@" .. self.index .. " note." .. leaderAnchor .. ")" +end +function noteMt:boxForNoteText(rightSide) + local area = labelArea:getArea(rightSide) + local noteWidth + if area == nil then + noteWidth = minNoteWidth + else + noteWidth = area.noteWidth - 2*noteInnerSep + end + local retval = "\\directlua{tex.box[\"@todonotes@notetextbox\"] = " .. + "node.copy_list(notesForPage[" .. self.indexOnPage .. "].textbox)}" + retval = retval .. "\\parbox{" .. noteWidth .. "sp}" .. + "{\\raggedright\\unhbox\\@todonotes@notetextbox}" + return retval +end + +function noteMt:getClipPathForTodoArea() + -- detext which lines are in same column/page as start of area + local lineCount = self.lineCountInArea + local maxLine = 1 + local lines = self.linesInArea + local lastY = lines[1] + while maxLine < lineCount do + if lines[maxLine + 1] < lastY then + maxLine = maxLine + 1 + lastY = lines[maxLine] + end + end + + local function nodename(i, corner) + return "(@todonotes@" .. self.index .. "@" .. i .. " area" .. corner .. ")" + end + local path = nodename(1, "NW") + local pathLeft = "" + if maxLine == 1 then + -- only one line + path = path .. " -- " .. nodename(1, "NE") .. + " -- " .. nodename(1, "SE") + else + path = path .. " -- " .. nodename(1, "NE") .. + " decorate[@todonotes@todoarea] { -- " .. nodename(1, "SE") .. "}" + end + for i = 2, maxLine do + if i == lineCount then + -- area does not use the whole line + path = path .. " -| " .. nodename(i, "NE") .. + " -- " .. nodename(i, "SE") + else + -- area uses whole line + path = path .. " -- " .. nodename(i, "NE") .. + " decorate[@todonotes@todoarea] { -- " .. nodename(i, "SE") .. "}" + end + pathLeft = " -- " .. nodename(i, "SW") .. + " decorate[@todonotes@todoarea] { -- " .. nodename(i, "NW") .. "}" .. + pathLeft + end + path = path .. pathLeft .. " -| " .. nodename(1, "SW") .. "-- cycle" + return path +end + + + +-- *** label areas *** +-- stores areas for placing labels on current page +function labelArea:getArea(rightSide) + if rightSide then + return self.right + else + return self.left + end +end +-- yields the x-coordinate of the boundary of the label that is pointing +-- towards the text +function labelArea:getXTextSide(rightSide) + if rightSide then + return self.right.left + else + return self.left.right + end +end +function labelArea:isOneSided() + if self.right == nil or self.left == nil then + return true + else + return false + end +end + + + +-- is called by the sty-file when all settings (algorithms etc.) are made +function initTodonotes() + if positioning.needLinePositions then + luatexbase.add_to_callback("post_linebreak_filter", callbackOutputLinePositions, "outputLinePositions") + tex.print("\\@starttoc{lpo}") + tex.print("\\directlua{lpoFileStream = \\the\\tf@lpo}") + end +end + + +-- valid values for noteType: nil/"" (for point in text), "area" +function addNoteToList(index, drawLeader, noteType) + local newNote = {} + newNote.index = index + newNote.textbox = node.copy_list(tex.box["@todonotes@notetextbox"]) + newNote.baselineskip = tex.dimen["@todonotes@baselineskip"] + newNote.fontsize = tex.dimen["@todonotes@fontsize"] + newNote.lineColor = tex.toks["@todonotes@toks@currentlinecolor"] + newNote.backgroundColor = tex.toks["@todonotes@toks@currentbackgroundcolor"] + newNote.borderColor = tex.toks["@todonotes@toks@currentbordercolor"] + newNote.leaderWidth = tex.toks["@todonotes@toks@currentleaderwidth"] + newNote.drawLeader = drawLeader + if noteType == "area" then + newNote.noteType = "area" + newNote.lineCountInArea = 0 + -- else: newNote.noteType = nil (default value) + end + setmetatable(newNote, {__index = noteMt}) + newNote.indexOnPage = #notesForPage + 1 + notesForPage[newNote.indexOnPage] = newNote +end + +function clearNotes() + -- delete the texts for the notes on this page from memory + -- (garbage collection does not work for nodes) + for _, v in pairs(notesForPage) do + node.free(v.textbox) + end + notesForPage = notesForNextPage + -- update indexOnPage for the new notes + for k, v in pairs(notesForPage) do + v.indexOnPage = k + end + + currentPage = currentPage + 1 +end + +function processLastLineInTodoArea() + -- LaTeX counter is accessed as TeX count by prefixing c@ + ind = tex.count["c@@todonotes@numberoftodonotes"] + val = tex.count["c@@todonotes@numberofLinesInArea"] + notesForPage[#notesForPage].lineCountInArea = val +end + + +-- *** constructing the linePositions list *** +function linePositionsNextPage() + linePositionsPageNr = linePositionsPageNr + 1 + linePositionsCurPage = {} + linePositions[linePositionsPageNr] = linePositionsCurPage +end + +function linePositionsAddLine(ycoord, lineheight, linedepth) + local baseline = ycoord - tex.pageheight + linePositionsCurPage[#linePositionsCurPage + 1] = {baseline, baseline + lineheight, baseline - linedepth} +end + +-- DEBUG +function markLines() + print("marking lines for page " .. currentPage) + if linePositions[currentPage] ~= nil then + for _, v in pairs(linePositions[currentPage]) do + --local yPos = - (tex.pageheight - v[1]) + local yPos = v[3] + local yTop = v[2] + tex.print("\\draw[red] (10cm," .. yPos .."sp) circle(3pt);") + tex.print("\\fill[red] (10cm," .. yPos .."sp) circle(1pt);") + tex.print("\\draw[green] (2cm," .. yTop .."sp) -- ++(17cm,0);") + tex.print("\\draw[blue] (2cm," .. yPos .."sp) -- ++(17cm,0);") + end + end +end + + +function getInputCoordinatesForNotes() + tex.sprint(catcodeStart) + for k, v in ipairs(notesForPage) do + local nodename = "@todonotes@" .. v.index .. " inText" + + tex.sprint("\\pgfextractx{\\@todonotes@extractx}{\\pgfpointanchor{" .. + nodename .. "}{center}}") + tex.sprint("\\pgfextracty{\\@todonotes@extracty}{\\pgfpointanchor{" .. + nodename .. "}{center}}") + tex.print("\\directlua{notesForPage[" .. k .. "].origInputX = " .. + "tex.dimen[\"@todonotes@extractx\"]}") + tex.print("\\directlua{notesForPage[" .. k .. "].origInputY = " .. + "tex.dimen[\"@todonotes@extracty\"]}") + + if v.noteType == "area" then + nodename = nodename .. "End" + tex.sprint("\\pgfextractx{\\@todonotes@extractx}{\\pgfpointanchor{" .. + nodename .. "}{center}}") + tex.sprint("\\pgfextracty{\\@todonotes@extracty}{\\pgfpointanchor{" .. + nodename .. "}{center}}") + tex.print("\\directlua{notesForPage[" .. k .. "].origInputEndX = " .. + "tex.dimen[\"@todonotes@extractx\"]}") + tex.print("\\directlua{notesForPage[" .. k .. "].origInputEndY = " .. + "tex.dimen[\"@todonotes@extracty\"]}") + + notesForPage[k].linesInArea = {} + for i = 1, v.lineCountInArea do + nodename = "@todonotes@" .. v.index .. "@" .. i .. " areaSW" + tex.sprint("\\pgfextracty{\\@todonotes@extracty}{\\pgfpointanchor{" .. + nodename .. "}{center}}") + tex.print("\\directlua{notesForPage[" .. k .. "].linesInArea[" .. + i .. "] = " .. "tex.dimen[\"@todonotes@extracty\"]}") + end + end + end + tex.sprint(catcodeEnd) +end + +function calcLabelAreaDimensions() + local routingAreaSpace = 0 + if leaderType.needRoutingArea then + routingAreaSpace = routingAreaWidth + end + + local top = tex.voffset + tex.dimen.topmargin + const1In + local bottom = top + tex.dimen.headheight + tex.dimen.headsep + tex.dimen.textheight + tex.dimen.footskip + local currentsidemargin = tex.hoffset + tex.dimen["@todonotes@currentsidemargin"] + const1In + + local left = {} + left.top = -top + left.bottom = -bottom + left.left = distanceNotesPageBorder + left.right = currentsidemargin - distanceNotesText - routingAreaSpace + if left.right - left.left < minNoteWidth then + -- not enough space left of text + left = nil + else + left.noteWidth = left.right - left.left + end + + local right = {} + right.top = -top + right.bottom = -bottom + right.left = currentsidemargin + tex.dimen.textwidth + distanceNotesText + routingAreaSpace + right.right = tex.pagewidth - distanceNotesPageBorder + if right.right - right.left < minNoteWidth then + -- not enough space right of text + right = nil + else + right.noteWidth = right.right - right.left + end + + local text = {} + text.left = currentsidemargin + text.right = currentsidemargin + tex.dimen.textwidth + + labelArea.left = left + labelArea.right = right + labelArea.text = text +end + +function calcHeightsForNotes() + -- function has to be called outside of a tikzpicture-environment + tex.sprint(catcodeStart) + for k, v in ipairs(notesForPage) do + -- store height for note + -- (is determined by creating a box with the text and reading its size) + + -- left side + tex.sprint("\\savebox{\\@todonotes@heightcalcbox}" .. + "{" .. v:boxForNoteText(false) .. "}") + tex.sprint("\\@todonotes@heightcalcboxdepth=\\dp\\@todonotes@heightcalcbox") + tex.sprint("\\@todonotes@heightcalcboxheight=\\ht\\@todonotes@heightcalcbox") + tex.sprint("\\directlua{notesForPage[" .. k .. "].heightLeft = " .. + "tex.dimen[\"@todonotes@heightcalcboxheight\"]" .. + " + tex.dimen[\"@todonotes@heightcalcboxdepth\"]}") + + -- right side + tex.sprint("\\savebox{\\@todonotes@heightcalcbox}" .. + "{" .. v:boxForNoteText(true) .. "}") + tex.sprint("\\@todonotes@heightcalcboxdepth=\\dp\\@todonotes@heightcalcbox") + tex.sprint("\\@todonotes@heightcalcboxheight=\\ht\\@todonotes@heightcalcbox") + tex.sprint("\\directlua{notesForPage[" .. k .. "].heightRight = " .. + "tex.dimen[\"@todonotes@heightcalcboxheight\"]" .. + " + tex.dimen[\"@todonotes@heightcalcboxdepth\"]}") + + -- store pageNr for note + -- (is determined as reference to a label) + tex.sprint("\\directlua{notesForPage[" .. k .. "].pageNr = " .. + "\\zref@extract{@todonotes@" .. v.index .. "}{abspage}}") + if v.noteType == "area" then + tex.sprint("\\directlua{notesForPage[" .. k .. "].pageNrEnd = " .. + "\\zref@extract{@todonotes@" .. v.index .. "@end}{abspage}}") + end + end + tex.sprint(catcodeEnd) +end + +local inputShiftX = string.todimen("-0.05cm") -- sensible value depends on shape of mark +function printNotes() + print("drawing labels for page " .. currentPage) + + -- seperate notes that should be placed on another page + -- This can occur when note is in a paragraph which doesn't fit on the + -- current page and is thus moved to the next one. But the \todo-command is + -- still read before the shipout of the current page is done + notesForNextPage = {} + local k=1 + while k <= #notesForPage do + local v = notesForPage[k] + if v.pageNr ~= currentPage then + table.insert(notesForNextPage, v) + table.remove(notesForPage, k) + print("moving " .. k) + else + -- update index here (needed if a note was deleted before) + v.indexOnPage = k + k = k + 1 + end + end + + -- add offset to input coordinates + for _, v in pairs(notesForPage) do + if v.noteType ~= "area" then + v.inputX = v.origInputX + inputShiftX + v.inputY = v.origInputY - 1.3 * (v.baselineskip - v.fontsize) + else + v.inputX = v.origInputX + v.inputY = v.origInputY + end + end + + splitting.algo() + if positioning.twoSided then + local notesLeft, notesRight = segmentNotes(notesForPage) + if #notesLeft > 0 then + positioning.algo(notesLeft, false) + end + if #notesRight > 0 then + positioning.algo(notesRight, true) + end + else + positioning.algo() + end + for k, v in ipairs(notesForPage) do + if todonotesDebug then + local function outputWithPoints(val) + if val ~= nil then + return val .. " (" .. number.topoints(val) .. ")" + else + return "" + end + end + print("-----------------") + print(k .. ": ") + print("index: " .. v.index) + print("origInputX: " .. v.origInputX) + print("origInputY: " .. v.origInputY) + if (v.noteType ~= nil) then + print("noteType: " .. v.noteType) + print("origInputEndX:" .. v.origInputEndX) + print("origInputEndY:" .. v.origInputEndY) + print("lineCountInArea:" .. v.lineCountInArea) + print("linesInArea :" .. inspect(v.linesInArea)) + else + print("noteType: nil") + end + print("inputX: " .. v.inputX) + print("inputY: " .. v.inputY) + print("outputX: " .. v.outputX) + print("outputY: " .. v.outputY) + if (v.rasterSlots ~= nil) then + print("rasterSlots: " .. v.rasterSlots) + end + print("baselineskip: " .. outputWithPoints(v.baselineskip)) + print("fontsize: " .. outputWithPoints(v.fontsize)) + print("textbox: " .. inspect(v.textbox)) + print("height: " .. outputWithPoints(v:getHeight())) + print("heightLeft: " .. outputWithPoints(v.heightLeft)) + print("heightRight: " .. outputWithPoints(v.heightRight)) + print("rightSide: " .. tostring(v.rightSide)) + if v.pageNr ~= nil then + print("pageNr: " .. v.pageNr) + end + print("lineColor: " .. v.lineColor) + print("backgroundColor:" .. v.backgroundColor) + print("borderColor: " .. v.borderColor) + print("leaderWidth: " .. v.leaderWidth) + print("drawLeader: " .. tostring(v.drawLeader)) + end + + -- print note + tex.print(catcodeStart) + tex.print("\\node[@todonotes@notestyle,anchor=north west," .. + "fill=" .. v.backgroundColor .. ",draw=" .. v.borderColor .. "] " .. + "(@todonotes@" .. v.index .. + " note) at (" .. v.outputX .. "sp," .. v.outputY .. "sp) {" .. + v:boxForNoteText(v.rightSide) .. "};") + tex.print(catcodeEnd) + + + -- output debugging hints on page + if todonotesDebug then + tex.print("\\node[anchor=north west,text=blue,fill=white,rectangle] at (@todonotes@" .. v.index .. " inText) {" .. v.index .. "};") + tex.print("\\draw[green,fill] (@todonotes@" .. v.index .. " inText) circle(2pt);") + tex.print("\\draw[black,fill] (@todonotes@" .. v.index .. " inText) circle(0.2pt);") + if v.noteType == "area" then + tex.print("\\draw[red,fill] (@todonotes@" .. v.index .. " inTextEnd) circle(2pt);") + end + + if (v.noteType ~= nil) then + print(v:getClipPathForTodoArea()) + tex.print("\\draw[blue] " .. v:getClipPathForTodoArea() .. ";") + --for i=1, v.lineCountInArea do + --tex.print(" (@todonotes@" .. v.index .. "@" .. i .. " areaSW) -- ") + --end + --tex.print("cycle;") + end + end + end + -- draw leader + leaderType.algo() + + -- draw mark in text + for _, v in pairs(notesForPage) do + if v.drawLeader ~= false and v.noteType ~= "area" then + local shiftStr = "(" .. v.inputX .. "sp," .. v.inputY .. "sp)" + tex.print("\\draw[@todonotes@textmark," .. + "draw=" .. v.lineColor .. ",fill=" .. v.lineColor .. "," .. + "shift={" .. shiftStr .. "}," .. + "scale around={0.5:(-0,-0)},shift={(-0.5,-0.1)}]" .. + "(1,0) .. controls (0.5,0.2) and (0.65,0.3) .." .. + "(0.5,0.7) .. controls (0.35,0.3) and (0.5,0.2) .." .. + "(0,0) -- cycle;") + end + end + + --- draw label areas when requested + if todonotesDebug then + local area = labelArea.left + if area ~= nil then + tex.print("\\draw[blue] (" .. area.left .. "sp," .. area.top .. "sp) rectangle (" .. + area.right .. "sp," .. area.bottom .. "sp);") + end + area = labelArea.right + if area ~= nil then + tex.print("\\draw[blue] (" .. area.left .. "sp," .. area.top .. "sp) rectangle (" .. + area.right .. "sp," .. area.bottom .. "sp);") + end + end +end + + + + +-- ********** Helper Functions ********** + +-- * comparators for table.sort() * +-- (yields true if first parameter should be placed before second parameter in +-- sorted table) +function compareNoteInputXAsc(note1, note2) + if note1.inputX < note2.inputX then + return true + end +end + +function compareNoteIndInputXAsc(key1, key2) + if notesForPage[key1].inputX < notesForPage[key2].inputX then + return true + end +end + +function compareNoteIndInputXDesc(key1, key2) + if notesForPage[key1].inputX > notesForPage[key2].inputX then + return true + end +end + +function compareNoteIndInputYDesc(key1, key2) + local v1 = notesForPage[key1] + local v2 = notesForPage[key2] + if v1.inputY > v2.inputY then + return true + elseif v1.inputY == v2.inputY then + if v1.inputX < v2.inputX then + return true + end + end +end + +-- * callbacks for Luatex * +function appendStrToTokenlist(tokenlist, str) + str:gsub(".", function(c) + tokenlist[#tokenlist + 1] = {12, c:byte(), 0} + end) +end +-- writes commands into the node tree that print the absolute position on the +-- page to the output file (streamId is taken from lpoFileStream) at the +-- beginning of every line +-- should be called as post_linebreak_filter +function callbackOutputLinePositions(head) + while head do + if head.id == 0 then -- id 0 is a hlist + -- check if we are in the main text area (hlists in, e.g., + -- tikz nodes should have other widths) + if head.width == tex.dimen.textwidth then + -- check if there is a glyph in this hlist + -- -> then we consider it a text line + local foundGlyph = false + local glyphTest = head.head + while glyphTest do + if glyphTest.id == 37 then -- glyph + foundGlyph = true + break + end + glyphTest = glyphTest.next + end + + if foundGlyph then + local w = node.new("whatsit", "write") -- 8/1 + w.stream = lpoFileStream + local tokenlist = { + {12, 92, 0}, -- \ + {12, 64, 0}, -- @ + {12, 116, 0}, -- t + {12, 111, 0}, -- o + {12, 100, 0}, -- d + {12, 111, 0}, -- o + {12, 110, 0}, -- n + {12, 111, 0}, -- o + {12, 116, 0}, -- t + {12, 101, 0}, -- e + {12, 115, 0}, -- s + {12, 64, 0}, -- @ + {12, 108, 0}, -- l + {12, 105, 0}, -- i + {12, 110, 0}, -- n + {12, 101, 0}, -- e + {12, 112, 0}, -- p + {12, 111, 0}, -- o + {12, 115, 0}, -- s + {12, 105, 0}, -- i + {12, 116, 0}, -- t + {12, 105, 0}, -- i + {12, 111, 0}, -- o + {12, 110, 0}, -- n + {12, 123, 0} -- { + } + t = token.create("@todonotes@pdflastypos") + tokenlist[#tokenlist + 1] = t + tokenlist[#tokenlist + 1] = {12, 125, 0} + tokenlist[#tokenlist + 1] = {12, 123, 0} + appendStrToTokenlist(tokenlist, tostring(head.height)) + tokenlist[#tokenlist + 1] = {12, 125, 0} + tokenlist[#tokenlist + 1] = {12, 123, 0} + appendStrToTokenlist(tokenlist, tostring(head.depth)) + tokenlist[#tokenlist + 1] = {12, 125, 0} + w.data = tokenlist + head.head = node.insert_before(head.head,head.head,w) + + local w = node.new("whatsit", "pdf_save_pos") -- 8/23 + head.head = node.insert_before(head.head,head.head,w) + end + end + end + head = head.next + end + return true +end + + + + + +-- ********** Leader Drawing Algorithms ********** + +function drawLeaderPath(note, path) + if note.drawLeader == false then + return + end + local clipPath + if note.noteType == "area" then + clipPath = note:getClipPathForTodoArea() + tex.print("\\begin{scope}") + tex.print("\\clip (current page.north west) rectangle (current page.south east) ") + tex.print(clipPath) + tex.print(";") + end + tex.print("\\draw[@todonotes@leader,draw=" .. note.lineColor .. + ",line width=" .. note.leaderWidth .. ",name path=leader] " .. path .. ";") + if note.noteType == "area" then + tex.print("\\path[name path=clipping] " .. clipPath .. ";") + tex.print("\\fill[@todonotes@leader,name intersections={of=leader and clipping, by=x,sort by=leader}] (x) circle(3pt);") + tex.print("\\end{scope}") + end +end + + +-- ** leader drawing: s-leaders +function drawSLeaders() + for k, v in ipairs(notesForPage) do + drawLeaderPath(v, v:getLabelAnchorTikz() .. + " -- " .. v:getInTextAnchorTikz()) + end +end +leaderTypes["s"] = {algo = drawSLeaders} + + + +-- ** leader drawing: opo-leaders +function drawOpoLeader(v, opoShift, rightSide) + if rightSide then + opoShift = - opoShift + end + drawLeaderPath(v, v:getLabelAnchorTikz() .. " -- +(" .. opoShift .. "sp,0) " .. + "|- " .. v:getInTextAnchorTikz()) +end +function drawOpoGroup(group, directionDown, rightSide) + if directionDown == nil then + for _, v2 in ipairs(group) do + drawOpoLeader(notesForPage[v2], 0, rightSide) + end + else + if #group == 1 then + -- place p-section of leader in center of routing area + local opoShift = distanceNotesText / 2 + routingAreaWidth / 2 + drawOpoLeader(notesForPage[group[1]], opoShift, rightSide) + else + local leaderDistance = routingAreaWidth / (#group - 1) + + -- initialise shift value + local nextOpoShift, move + if directionDown then + nextOpoShift = distanceNotesText / 2 + routingAreaWidth + move = -leaderDistance + else + nextOpoShift = distanceNotesText / 2 + move = leaderDistance + end + + -- cycle through group + for _, v2 in ipairs(group) do + drawOpoLeader(notesForPage[v2], nextOpoShift, rightSide) + nextOpoShift = nextOpoShift + move + end + end + end +end +function drawOpoLeadersSide(notes, rightSide) + table.sort(notes, compareNoteIndInputYDesc) + + local lastDirectionDown = nil + local group = {} + local prevNote + for _, ind in ipairs(notes) do + local v = notesForPage[ind] + + local leaderAnchorY = v:getLabelAnchorY() + if leaderAnchorY > v.inputY then + newDirectionDown = true + elseif leaderAnchorY < v.inputY then + newDirectionDown = false + else + newDirectionDown = nil + end + + if lastDirectionDown == newDirectionDown and + prevNote ~= nil and + -- following conditions check that leaders would really intersect + -- otherwise we can start a new group + ((newDirectionDown and leaderAnchorY >= prevNote.inputY) or + (not newDirectionDown and v.inputY >= prevNote:getLabelAnchorY())) then + -- note belongs to group + table.insert(group, ind) + else + -- draw leaders for group + drawOpoGroup(group, lastDirectionDown, rightSide) + + -- initialise new group with this note + lastDirectionDown = newDirectionDown + group = {ind} + end + + prevNote = v + end + drawOpoGroup(group, lastDirectionDown, rightSide) +end +function drawOpoLeaders() + local notesLeft, notesRight = segmentNotes(notesForPage) + if #notesLeft > 0 then + drawOpoLeadersSide(notesLeft, false) + end + if #notesRight > 0 then + drawOpoLeadersSide(notesRight, true) + end +end +leaderTypes["opo"] = {algo = drawOpoLeaders, + needRoutingArea = true} + + + +-- ** leader drawing: po-leaders +function drawPoLeaders() + for _, v in ipairs(notesForPage) do + drawLeaderPath(v, v:getLabelAnchorTikz() .. " -| " .. v:getInTextAnchorTikz()) + end +end +leaderTypes["po"] = {algo = drawPoLeaders} + + + +-- ** leader drawing: os-leaders +function drawOsLeaders() + for _, v in ipairs(notesForPage) do + local cornerX + if v.rightSide then + cornerX = labelArea.right.left - distanceNotesText / 2 - routingAreaWidth + else + cornerX = labelArea.left.right + distanceNotesText / 2 + routingAreaWidth + end + drawLeaderPath(v, v:getInTextAnchorTikz() .. + " -- (" .. cornerX .. "sp,0 |- 0," .. v.inputY .. "sp) -- " .. + v:getLabelAnchorTikz()) + end +end +leaderTypes["os"] = {algo = drawOsLeaders, + needRoutingArea = true} + + + +-- ** leader drawing: s-Bezier-leaders +-- additional fields for each note: +-- leaderArmY +-- movableControlPointX +-- optimalPositionX +-- currentForce +-- forceLimitDec +-- forceLimitInc + +-- settings for algorithm +maxIterations = 1000 +factorRepulsiveControlPoint = 1 +factorAttractingControlPoint = 1 +stopCondition = 65536 -- corresponds to 1pt + +function constructCurve(l) + local curve = {} + + -- site + curve[1] = {} + curve[1].x = l.inputX + curve[1].y = l.inputY + + -- unmovable control point (middle point of site and movable control point) + curve[2] = {} + curve[2].x = (l.inputX + l.movableControlPointX) / 2 + curve[2].y = (l.inputY + l.leaderArmY) / 2 + + -- movable control point + curve[3] = {} + curve[3].x = l.movableControlPointX + curve[3].y = l.leaderArmY + + -- port + curve[4] = {} + curve[4].x = labelArea:getXTextSide(l.rightSide) + curve[4].y = l.leaderArmY + + return curve +end +function computeRepulsiveControlPointForces() + for k1, l1 in pairs(notesForPage) do + for k2, l2 in pairs(notesForPage) do + if k1 ~= k2 then + -- curves of the leaders + local curve1 = constructCurve(l1) + local curve2 = constructCurve(l2) + + local distance = checkCurveApproximation(curve1, curve2); + + -- check if R1 has to be increased or decreased to increase the distance of the 2 curves + -- if curve1 is bent into the direction of curve2, R1 has to be decreased + local actualR = math.abs(labelArea:getXTextSide(l1.rightSide) - l1.movableControlPointX) + if ((l1.inputY < l1.leaderArmY and + l2.leaderArmY < l1.leaderArmY) or + (l1.inputY > l1.leaderArmY and + l2.leaderArmY > l1.leaderArmY)) then + -- R1 has to be increased + local desiredR = math.abs(labelArea:getXTextSide(l1.rightSide) - l1.optimalPositionX) + local diff = math.abs(desiredR - actualR) + if distance == 0 then + distance = 0.01 + end + local force = diff / distance + local newForce = force * factorRepulsiveControlPoint + l1.currentForce = l1.currentForce + newForce + l1.forceLimitDec = math.min(l1.forceLimitDec, distance * 0.45) + else + -- R1 has to be decreased + if distance == 0 then + distance = 0.01 + end + local force = actualR / distance + local newForce = -force * factorRepulsiveControlPoint + l1.currentForce = l1.currentForce + newForce + local oldLim = l1.forceLimitInc + l1.forceLimitInc = math.min(l1.forceLimitInc, distance * 0.45) + --if oldLim ~= l1.forceLimitInc then + --print(k1 .. ": Reduced forceLimitInc from " .. oldLim .. " to " .. l1.forceLimitInc .. " because of " .. k2 .. " (distance: " .. distance .. ")") + --end + end + end + end + end +end +function computeAttractingControlPointForces() + for _, l in pairs(notesForPage) do + local desiredR = math.abs(labelArea:getXTextSide(l.rightSide) - l.optimalPositionX) + local actualR = math.abs(labelArea:getXTextSide(l.rightSide) - l.movableControlPointX) + local newForce = (desiredR - actualR) * factorAttractingControlPoint + l.currentForce = l.currentForce + newForce + end +end +function getPointOnCurve(t, curve) + if #curve ~= 4 then + error("4 points needed for a Bezier-curve. Given size was: " .. #curve) + end + + local x = (1 - t) * (1 - t) * (1 - t) * curve[1].x + + 3 * t * (1 - t) * (1 - t) * curve[2].x + + 3 * t * t * (1 - t) * curve[3].x + + t * t * t * curve[4].x + + local y = (1 - t) * (1 - t) * (1 - t) * curve[1].y + + 3 * t * (1 - t) * (1 - t) * curve[2].y + + 3 * t * t * (1 - t) * curve[3].y + + t * t * t * curve[4].y; + + return x, y +end +function checkCurveApproximation(curve1, curve2) + -- these lists will contain the sections of the approximation of the two curves + local sectionsCurve1 = {} + local sectionsCurve2 = {} + + -- get line segments of the first curve + local numberOfSectionsCurve1 = numberOfCurvePartitions + local temp1X, temp1Y = getPointOnCurve(0, curve1) + local i = 1 + while i <= numberOfSectionsCurve1 do + local t = i / numberOfSectionsCurve1 + local temp2X, temp2Y = getPointOnCurve(t, curve1) + local line = {} + line.x1 = temp1X + line.y1 = temp1Y + line.x2 = temp2X + line.y2 = temp2Y + table.insert(sectionsCurve1, line) + temp1X, temp1Y = temp2X, temp2Y + i = i + 1 + end + + -- get line segments of the second curve + local numberOfSectionsCurve2 = numberOfCurvePartitions + temp1X, temp1Y = getPointOnCurve(0, curve2) + i = 1 + while i <= numberOfSectionsCurve2 do + local t = i / numberOfSectionsCurve2 + local temp2X, temp2Y = getPointOnCurve(t, curve2) + local line = {} + line.x1 = temp1X + line.y1 = temp1Y + line.x2 = temp2X + line.y2 = temp2Y + table.insert(sectionsCurve2, line) + temp1X, temp1Y = temp2X, temp2Y + i = i + 1 + end + + -- get the minimal distance of the 2 curve approximations + local minDistance = math.huge + for _, line1 in pairs(sectionsCurve1) do + for _, line2 in pairs(sectionsCurve2) do + local distance = getDistance(line1, line2) + if distance <= minDistance then + minDistance = distance + end + end + end + + return minDistance +end +function getDistance(line1, line2) + local t1, t2 = pathLine.line_line_intersection(line1.x1, line1.y1, line1.x2, line1.y2, + line2.x1, line2.y1, line2.x2, line2.y2) + if 0 <= t1 and t1 <= 1 and 0 <= t2 and t2 <= 1 then + -- the lines do intersect + return 0 + end + + local d1 = pathLine.hit(line2.x1, line2.y1, line1.x1, line1.y1, line1.x2, line1.y2) + local d2 = pathLine.hit(line2.x2, line2.y2, line1.x1, line1.y1, line1.x2, line1.y2) + local d3 = pathLine.hit(line1.x1, line1.y1, line2.x1, line2.y1, line2.x2, line2.y2) + local d4 = pathLine.hit(line1.x2, line1.y2, line2.x1, line2.y1, line2.x2, line2.y2) + return math.sqrt(math.min(d1, d2, d3, d4)) +end +function applyForces(v) + --print("force on note " .. v.index .. ": " .. v.currentForce .. " (limit: +" .. v.forceLimitInc .. ", -" .. v.forceLimitDec .. ")") + + -- limit the force so the movable control point is between the port and the optimal position + local actualR = math.abs(labelArea:getXTextSide(v.rightSide) - v.movableControlPointX) + local differenceR = math.abs(labelArea:getXTextSide(v.rightSide) - v.optimalPositionX) - actualR + if (v.currentForce < 0 and math.abs(v.currentForce) > actualR) then + v.currentForce = (-1) * actualR + end + if (v.currentForce > 0 and v.currentForce > differenceR) then + v.currentForce = differenceR + end + + -- limit the force so 2 curves do not get too close to each other and do not cross + if v.currentForce > v.forceLimitInc then + v.currentForce = v.forceLimitInc + end + if v.currentForce < (-1) * v.forceLimitDec then + v.currentForce = (-1) * v.forceLimitDec + end + v.forceLimitDec = math.huge + v.forceLimitInc = math.huge + + if v.rightSide then + v.movableControlPointX = v.movableControlPointX - v.currentForce + else + v.movableControlPointX = v.movableControlPointX + v.currentForce + end + + --print("force on note " .. v.index .. ": " .. v.currentForce) + local c = v.currentForce + v.currentForce = 0 + return c +end +function getAngle(centerX, centerY, x, y) + local vectorX = x - centerX + local vectorY = y - centerY + local length = math.sqrt((vectorX ^ 2) + (vectorY ^ 2)) + + vectorX = vectorX / length + vectorY = vectorY / length + + local radAngle = math.acos(vectorX) + local degAngle = (radAngle * 180) / math.pi + + if vectorY < 0 then + degAngle = 360 - degAngle + end + + return degAngle +end +function solveQuadraticEquation(a, b, c) + local discr = (b * b) - (4 * a * c) + + if discr < 0 then + error("Fehler bei der Berechnung das optimalen Punktes") + end + + local solution1 = ((-b) + math.sqrt(discr)) / (2 * a) + local solution2 = ((-b) - math.sqrt(discr)) / (2 * a) + + if solution1 < 0 and solution2 < 0 then + error("no positive solution") + end + + if solution1 < solution2 then + return solution2 + else + return solution1 + end +end +function computeOptimalPosition(v) + local distance = point.distance(v.inputX, v.inputY, labelArea:getXTextSide(v.rightSide), v.leaderArmY) + + -- the angle at the port between the point and the movable control point + local tempAngle = getAngle(v.inputX, v.inputY, labelArea:getXTextSide(v.rightSide), v.leaderArmY) + + local gamma + if v.rightSide then + if tempAngle < 180 then + gamma = tempAngle + else + gamma = 360 - tempAngle + end + else + if tempAngle < 180 then + gamma = 180 - tempAngle + else + gamma = tempAngle - 180 + end + end + + -- a quadratic formula has to be solved to get the optimal position + local a = 3 + local b = 2 * distance * math.cos(math.rad(gamma)) + local c = -(distance * distance) + + local r = solveQuadraticEquation(a, b, c) + + if v.rightSide then + v.optimalPositionX = labelArea:getXTextSide(v.rightSide) - r + else + v.optimalPositionX = labelArea:getXTextSide(v.rightSide) + r + end +end +function drawSBezierLeaders() + for _, v in pairs(notesForPage) do + -- initialise leader + v.leaderArmY = v:getLabelAnchorY() + v.movableControlPointX = labelArea:getXTextSide(v.rightSide) + v.currentForce = 0 + v.forceLimitDec = math.huge + v.forceLimitInc = math.huge + end + + numberOfCurvePartitions = #notesForPage * 3 + + for _, v in pairs(notesForPage) do + computeOptimalPosition(v) + end + + -- main loop + local proceed = true + local loopCounter = 0 + while (proceed and loopCounter < maxIterations) do + --print("") + --print("--------------------------------------------------") + print("iteration " .. loopCounter .. ":") + + -- compute forces + computeRepulsiveControlPointForces() + computeAttractingControlPointForces() + + -- apply forces + proceed = false + for _, l in pairs(notesForPage) do + local diff = applyForces(l) + if diff > stopCondition then + proceed = true + end + end + + loopCounter = loopCounter + 1 + end + + print("") + print("End of Force-directed algo") + print("number of iterations: " .. loopCounter) + print("") + + -- draw + for _, v in pairs(notesForPage) do + local curve = constructCurve(v) + local unmovableStr = "(" .. curve[2].x .. "sp," .. curve[2].y .. "sp)" + local movableStr = "(" .. curve[3].x .. "sp," .. curve[3].y .. "sp)" + drawLeaderPath(v, v:getInTextAnchorTikz() .. " .. controls " .. + unmovableStr .. " and " .. movableStr .. " .. " .. + v:getLabelAnchorTikz()) + + -- draw control points when requested + if todonotesDebug then + local optimalStr = "(" .. v.optimalPositionX .. "sp," .. v.leaderArmY .. "sp)" + tex.print("\\node[anchor=north west,text=pink,fill=white,rectangle] at " .. optimalStr .. " {" .. v.index .. "};") + tex.print("\\node[anchor=north west,text=red,fill=white,rectangle] at " .. movableStr .. " {" .. v.index .. "};") + tex.print("\\node[anchor=north west,text=orange,fill=white,rectangle] at " .. unmovableStr .. " {" .. v.index .. "};") + tex.print("\\draw[red,fill] " .. movableStr .. " circle(2pt);") + tex.print("\\draw[orange,fill] " .. unmovableStr .. " circle(2pt);") + tex.print("\\draw[pink,fill] " .. optimalStr .. " circle(1pt);") + end + end +end +leaderTypes["sBezier"] = {algo = drawSBezierLeaders} + + + + + +-- ********** Positioning Algorithms ********** + +-- ** helper functions + +-- divides notes in two lists (for left and right side) +-- side must be stored in note.rightSide for every note before using this function +function segmentNotes(notes) + local availableNotesLeft = {} + local availableNotesRight = {} + for k, v in pairs(notes) do + if v.rightSide == true then + table.insert(availableNotesRight, k) + else + table.insert(availableNotesLeft, k) + end + end + return availableNotesLeft, availableNotesRight +end + +-- finds the index in the list given as parameter with the minimum angle +-- the function used for computation of the angle is given as second parameter +-- (the alphaFormula gets the note, to which the angle should be computed, as +-- the only parameter) +function findIndexMinAlpha(availableNotesIndex, alphaFormula) + local minAlpha = math.huge -- infinity + local minIndex = -1 + + for k, v in pairs(availableNotesIndex) do + local alpha = alphaFormula(notesForPage[v]) + if alpha < minAlpha then + minAlpha = alpha + minIndex = k + end + end + + return minIndex +end + + + +-- ** partition into stacks +function getMeanYHeight(stack) + -- TODO: Alternative: nicht das arithmetische Mittel verwenden, sondern + -- Mittelpunkt zwischen dem obersten und untersten Punkt + local sumY = 0 + local height = 0 + for _, v in pairs(stack) do + sumY = sumY + notesForPage[v].inputY + height = height + notesForPage[v]:getHeight() + 2 * noteInnerSep + noteInterSpace + end + + local area = labelArea:getArea(notesForPage[stack[1]].rightSide) + + local meanY = sumY / #stack + local height = height - noteInterSpace + if meanY + (height/2) > area.top then + meanY = area.top - (height/2) + elseif meanY - (height/2) < area.bottom then + meanY = area.bottom + (height/2) + end + return meanY, height +end +function stacksIntersect(stackTop, stackBottom) + local topMeanY, topHeight = getMeanYHeight(stackTop) + local topLower = topMeanY - topHeight/2 + + local bottomMeanY, bottomHeight = getMeanYHeight(stackBottom) + local bottomUpper = bottomMeanY + bottomHeight/2 + + if topLower - bottomUpper < noteInterSpace then + return true + else + return false + end +end +function findStacks(notesOnSide) + local notes = table.copy(notesOnSide) + table.sort(notes, compareNoteIndInputYDesc) + + -- list that contains stacks + -- is initialized by putting all notes as single stacks ordered by their inputY + local stacks = {} + for _, v in pairs(notes) do + table.insert(stacks, {v}) + end + + -- Collapse Stacks where needed + local i = 1 + while i <= #stacks - 1 do + if stacksIntersect(stacks[i], stacks[i+1]) then + collapsedStacks = true + for _, v in pairs(stacks[i+1]) do + table.insert(stacks[i], v) + end + table.remove(stacks,i+1) + if i > 1 then + -- as stack i has increased in size we look at the previous + -- stack again in next iteration + i = i - 1 + end + else + -- look at next stack in next iteration + i = i + 1 + end + end + + return stacks +end + + +-- ** positioning: inText +function posInText() + -- trivial algorithm + -- places notes in text on position where todo-command was issued + for k, v in ipairs(notesForPage) do + v.outputX = v.inputX + v.outputY = v.inputY + end +end +positioningAlgos["inText"] = {algo = posInText, + leaderAnchor = "north west", + leaderShift = false, + twoSided = false} + + + +-- ** positioning: inputOrder +-- start at top and place notes below each other on left/right side +-- notes are placed in the order induced by their y-coordinates +function posInputOrder(notes, rightSide) + table.sort(notes, compareNoteIndInputYDesc) + placeNotesInputOrder(notes, labelArea:getArea(rightSide).top, rightSide) +end +positioningAlgos["inputOrder"] = {algo = posInputOrder, + leaderAnchor = "east", + leaderShift = false, + twoSided = true} + + + +-- ** positioning: inputOrderStacks +function placeNotesInputOrder(stack, yStart, rightSide) + local freeY = yStart + + for _, k in ipairs(stack) do + local v = notesForPage[k] + v.outputX = labelArea:getArea(rightSide).left + v.outputY = freeY + freeY = freeY - v:getHeight() - 2 * noteInnerSep - noteInterSpace + end +end +function posInputOrderStacks(notesOnSide, rightSide) + table.sort(notesOnSide, compareNoteIndInputYDesc) + + local stacks = findStacks(notesOnSide) + + -- place stacks + for k, stack in pairs(stacks) do + local meanY, height = getMeanYHeight(stack) + local stackStart = meanY + height / 2 + placeNotesInputOrder(stack, stackStart, rightSide) + end +end +positioningAlgos["inputOrderStacks"] = {algo = posInputOrderStacks, + leaderAnchor = "east", + leaderShift = false, + twoSided = true} + + + +-- ** positioning: sLeaderNorthEast +function posSLeaderNorthEast(notes, rightSide) + local noteY = labelArea:getArea(rightSide).top + + local alphaFormula + local noteX = labelArea:getXTextSide(rightSide) + local outputX = labelArea:getArea(rightSide).left + if rightSide then + alphaFormula = function (note) + return (noteY - note.inputY) / (noteX - note.inputX) + end + else + alphaFormula = function (note) + return (noteY - note.inputY) / (note.inputX - noteX) + end + end + + while #notes > 0 do + local minIndex = findIndexMinAlpha(notes, alphaFormula) + + -- place note identified by minIndex + local note = notesForPage[notes[minIndex]] + note.outputX = outputX + note.outputY = noteY + noteY = noteY - note:getHeight() - 2 * noteInnerSep - noteInterSpace + + table.remove(notes, minIndex) + end +end +positioningAlgos["sLeaderNorthEast"] = {algo = posSLeaderNorthEast, + leaderAnchor = "north east", + leaderShift = false, + twoSided = true} + + + +-- ** positioning: sLeaderNorthEastBelow +function placeNotesNorthEastBelow(stack, yStart, rightSide) + -- calculate minimum height of all notes + local minHeight = math.huge -- (infinity) + for _, v in pairs(stack) do + if notesForPage[v]:getHeight() < minHeight then + minHeight = notesForPage[v]:getHeight() + end + end + local leaderShiftY = (- minHeight - 2 * noteInnerSep) / 2 + + local noteY = yStart + local availableNotes = table.copy(stack) + + local alphaFormula + local noteX = labelArea:getXTextSide(rightSide) + local outputX = labelArea:getArea(rightSide).left + if rightSide == true then + alphaFormula = function (note) + return ((noteY + leaderShiftY) - note.inputY) / (noteX - note.inputX) + end + else + alphaFormula = function (note) + return ((noteY + leaderShiftY) - note.inputY) / (note.inputX - noteX) + end + end + while #availableNotes > 0 do + local minIndex = findIndexMinAlpha(availableNotes, alphaFormula) + + -- place note identified by minIndex + local note = notesForPage[availableNotes[minIndex]] + note.outputX = outputX + note.outputY = noteY + note.leaderShiftX = 0 + note.leaderShiftY = leaderShiftY + noteY = noteY - note:getHeight() - 2 * noteInnerSep - noteInterSpace + + table.remove(availableNotes, minIndex) + end +end +function posSLeaderNorthEastBelow(notes, rightSide) + placeNotesNorthEastBelow(notes, labelArea:getArea(rightSide).top, rightSide) +end +positioningAlgos["sLeaderNorthEastBelow"] = {algo = posSLeaderNorthEastBelow, + leaderAnchor = "north east", + leaderShift = true, + twoSided = true} + + + +-- ** positioning: sLeaderNorthEastBelowStacks +function posSLeaderNorthEastBelowStacks(notesOnSide, rightSide) + local stacks = findStacks(notesOnSide) + + -- place stacks + for k, stack in pairs(stacks) do + local meanY, height = getMeanYHeight(stack) + local stackStart = meanY + height / 2 + placeNotesNorthEastBelow(stack, stackStart, rightSide) + end +end +positioningAlgos["sLeaderNorthEastBelowStacks"] = {algo = posSLeaderNorthEastBelowStacks, + leaderAnchor = "north east", + leaderShift = true, + twoSided = true} + + + +-- ** positioning: sLeaderEast +function posSLeaderEast(notes, rightSide) + local leaderPosY + local noteY = labelArea:getArea(rightSide).top + + local alphaFormula + local noteX = labelArea:getXTextSide(rightSide) + local outputX = labelArea:getArea(rightSide).left + if rightSide == true then + alphaFormula = function (note) + return (leaderPosY - note.inputY) / (noteX - note.inputX) + end + else + alphaFormula = function (note) + return (leaderPosY - note.inputY) / (note.inputX - noteX) + end + end + + local placedNotes = {} + while #notes > 0 do + -- build a array with all distinct heights of the notes + -- first create a set and then convert to sorted array + local heights = {} + print(#heights) + for _, v in pairs(notes) do + heights[notesForPage[v]:getHeight()] = true + end + heights = table.keys(heights) + table.sort(heights) + + local chosenIndex = -1 + local chosenH = -1 + for _, h in pairs(heights) do + print("testing height: " .. h) -- DEBUG + leaderPosY = noteY - noteInnerSep - h/2 + + -- find point with highest angle + local minIndex = findIndexMinAlpha(notes, alphaFormula) + + -- found a valid note + if notesForPage[notes[minIndex]]:getHeight() <= h then + chosenIndex = minIndex + chosenH = h + break + end + end + + -- place note identified by chosenIndex + local note = notesForPage[notes[chosenIndex]] + note.outputX = outputX + -- let free space above note if needed (if chosenH ~= note:getHeight()) + note.outputY = noteY - (chosenH - note:getHeight()) / 2 + -- no extraordinary free space below note (even if chosenH ~= note:getHeight()) + noteY = note.outputY - note:getHeight() - 2 * noteInnerSep - noteInterSpace + -- DEBUG + if chosenH ~= note:getHeight() then + print("Creating free space above note " .. note.index) + end + + table.insert(placedNotes, notes[chosenIndex]) + table.remove(notes, chosenIndex) + end + + -- postprocessing: reduce spaces between notes where possible + for ind, noteNr in pairs(placedNotes) do + local note = notesForPage[noteNr] + + local aimedPos + if ind == 1 then + aimedPos = labelArea:getArea(rightSide).top + else + local prevNote = notesForPage[placedNotes[ind-1]] + aimedPos = prevNote.outputY - prevNote:getHeight() - 2 * noteInnerSep - noteInterSpace + end + + -- DEBUG + if aimedPos ~= note.outputY then + print("note " .. note.index .. " got moved:") + print("aimed: " .. aimedPos) + print("real: " .. note.outputY) + end + + local aimedLeaderAnchorY = aimedPos - noteInnerSep - note:getHeight() / 2 + local realLeaderAnchorY = note.outputY - noteInnerSep - note:getHeight() / 2 + -- it holds: realLeaderAnchorY < aimedLeaderAnchorY (realLeaderAnchor is lower on page) + + -- check if there are points in triangle (aimedLeaderAnchor, note.input, realLeaderAnchor) + -- we perform this check by calculating the angle of the points referred to note.input + local pointsInTriangle = false + local denom + if rightSide then + denom = noteX - note.inputX + else + denom = note.inputX - noteX + end + local aimedLeaderAnchorAngle = (aimedLeaderAnchorY - note.inputY) / denom + local realLeaderAnchorAngle = (realLeaderAnchorY - note.inputY) / denom + local minAngle = math.huge + local minAngleIndex = -1 -- takes index of lowest point in triangle + for otherInd, otherNote in pairs(notesForPage) do + if otherInd ~= noteNr and + ((not rightSide and otherNote.inputX < note.inputX) or + (rightSide and otherNote.inputX > note.inputX)) then + local otherNoteAngle + if rightSide then + otherNoteAngle = (otherNote.inputY - note.inputY) / (otherNote.inputX - note.inputX) + else + otherNoteAngle = (otherNote.inputY - note.inputY) / (note.inputX - otherNote.inputX) + end + + if (realLeaderAnchorAngle < otherNoteAngle) + and (otherNoteAngle < aimedLeaderAnchorAngle) then + pointsInTriangle = true + if otherNoteAngle < minAngle then + minAngle = otherNoteAngle + minAngleIndex = otherInd + end + print(otherNote.index .. " is in triangle for " .. note.index) -- DEBUG + end + end + end + + if not pointsInTriangle then + -- no points in triangle + -- => we can move this note to aimedPos + note.outputY = aimedPos + else + -- move note upwards so that leader touches lowest point in triangle + -- new point for leader anchor is determined by the ray from note.input through the lowest point in triangle (otherNote.input) + -- TODO: force a certain distance between leader and other points (at the moment a leader can contain endpoints of other leaders) + local otherNote = notesForPage[minAngleIndex] + local aimedLeaderAnchorY = note.inputY - (note.inputY - otherNote.inputY) * (note.inputX - noteX) / (note.inputX - otherNote.inputX) + note.outputY = aimedLeaderAnchorY + noteInnerSep + note:getHeight() / 2 + end + end +end +positioningAlgos["sLeaderEast"] = {algo = posSLeaderEast, + leaderAnchor = "east", + leaderShift = false, + twoSided = true} + + + +-- ** positioning: poLeaders +function getRasterAbsolute(rasterHeight, top, rasterIndex) + return top - (rasterIndex - 1) * rasterHeight +end + -- distance between line and leader that algorithm tries to reach when there is + -- no neighbouring line +local poMinDistLine = string.todimen("4pt") +function getPosAboveLine(linePositionsCurPage, lineInd) + local line = linePositionsCurPage[lineInd] + local posAbove + if linePositionsCurPage[lineInd - 1] ~= nil then + posAbove = (line[2] + linePositionsCurPage[lineInd - 1][3]) / 2 + end + if posAbove == nil or posAbove - line[2] > poMinDistLine then + posAbove = line[2] + poMinDistLine + end + return posAbove +end +function getPosBelowLine(linePositionsCurPage, lineInd) + local line = linePositionsCurPage[lineInd] + local posBelow + if linePositionsCurPage[lineInd + 1] ~= nil then + posBelow = (line[3] + (linePositionsCurPage[lineInd + 1][2])) / 2 + end + if posBelow == nil or line[3] - posBelow > poMinDistLine then + posBelow = line[3] - poMinDistLine + end + return posBelow +end +function posPoLeaders(notes, rightSide, avoidLines) + print("rasterHeight: " .. rasterHeight) + + local linePositionsCurPage + if avoidLines then + linePositionsCurPage = linePositions[currentPage] or {} + end + + -- number of slots on the whole page + local area = labelArea:getArea(rightSide) + local totalNumSlots = math.floor((area.top - area.bottom) / rasterHeight) + + -- calculate number of raster slots for each note + for _, ind in pairs(notes) do + local v = notesForPage[ind] + local height = v:getHeight() + 2 * noteInnerSep + noteInterSpace + v.rasterSlots = math.ceil(height / rasterHeight) + end + + -- sort notes by inputY + table.sort(notes, compareNoteIndInputYDesc) + + -- draw slots + if todonotesDebug then + for i = 1,totalNumSlots+1 do + local pos = area.top - (i-1) * rasterHeight + tex.print("\\draw[blue,dashed] (0," .. pos .. "sp) -- +(21cm,0);") + end + end + + -- initialise table opt for dynamic program + -- opt[topPoint, bottomPoint, topSlot, bottomSlot, numberLabeledSites] + -- opt[a][b][c][d][e] describes length-minimal placement of the labels for + -- sites from a to b in the raster slots c to d + -- the leftmost/rightmost e sites between a and b are selected until there + -- are no more free slots + local opt = {} + for a = 1, #notes do + opt[a] = {} + for b = a, #notes do + opt[a][b] = {} + -- TODO: needed label slots are restricted by points + -- only create tables for needed slots + for i = 1, totalNumSlots do + opt[a][b][i] = {} + for j = i, totalNumSlots do + if i == j then + opt[a][b][i][j] = {} + opt[a][b][i][j][0] = {} + opt[a][b][i][j][0].totalLength = 0 + opt[a][b][i][j][0].positions = {} + opt[a][b][i][j][0].leaderShiftY = {} + end + end + end + end + end + + -- constant use an partial solution without labeled points + local optEmpty = {} + optEmpty.totalLength = 0 + optEmpty.positions = {} + optEmpty.leaderShiftY = {} + + -- fill table opt for dynamic program + -- numberOfPoints is difference between topPoint and bottomPoint when computing opt + for numberOfPoints = 1, #notes do + for topPoint = 1, (#notes - numberOfPoints + 1) do + -- compute opt[topStrip][bottomStrip] + local bottomPoint = topPoint + numberOfPoints - 1 + + local pointsBetween = {} + for i = topPoint, bottomPoint do + table.insert(pointsBetween, notes[i]) + end + if rightSide then + table.sort(pointsBetween, compareNoteIndInputXDesc) + else + table.sort(pointsBetween, compareNoteIndInputXAsc) + end + + -- TODO: Einschränken, nicht alle Kombinationen von Slots notwendig + -- (siehe auch oben) + -- numberOfSlots is difference between topSlot and bottomSlot when computing opt + for numberOfSlots = 1, totalNumSlots do + for topSlot = 1, (totalNumSlots - numberOfSlots + 1) do + local bottomSlot = topSlot + numberOfSlots - 1 + + -- DEBUG + --print("computing opt[" .. topPoint .. "][" .. bottomPoint .. + --"][" .. topSlot .. "][" .. bottomSlot .. "]") + + opt[topPoint][bottomPoint][topSlot][bottomSlot] = {} + opt[topPoint][bottomPoint][topSlot][bottomSlot][0] = {} + opt[topPoint][bottomPoint][topSlot][bottomSlot][0].totalLength = 0 + opt[topPoint][bottomPoint][topSlot][bottomSlot][0].positions = {} + opt[topPoint][bottomPoint][topSlot][bottomSlot][0].leaderShiftY = {} + + -- stelle fest, wie viele Punkte gelabelt werden (bestimme also r) + local labeledSites = {} + local usedSlots = 0 + for _, v in pairs(pointsBetween) do + local note = notesForPage[v] + if usedSlots + note.rasterSlots <= numberOfSlots then + usedSlots = usedSlots + note.rasterSlots + table.insert(labeledSites, v) + else + break + end + end + + -- TODO: Teste, ob bei kleinerer Anzahl an Slots gleiche Punkte gelabelt werden + -- -> dann kann Teillösung ĂĽbernommen werden + + -- Mögliche Aufteilungen (Positionierung des Labels fĂĽr r) testen + -- und Optimum auswählen + if #labeledSites > 0 then + for numLabeledSites = 1, #labeledSites do + -- we place rightmost point: labeledSites[#labeledSites] + local rIndex = labeledSites[numLabeledSites] + local r = notesForPage[rIndex] + + -- slotIndexR is the slot in that the label for r begins (topmost slot) + local bestVal = math.huge + local bestOpt = {} + -- try all label positions for r (leader should enter the label at east-anchor) + for slotIndexR = topSlot, (bottomSlot + 1 - r.rasterSlots) do + -- calculate position in which leader arm is placed + local labelTopR = getRasterAbsolute(rasterHeight, area.top, slotIndexR) + local leaderArmR, leaderShiftR + if avoidLines then + leaderArmR = labelTopR - noteInnerSep - r:getHeight() / 2 -- east anchor + + -- find first line (from the top) which lower bound is below leaderArmR + local lineBelowInd + for ind, v in pairs(linePositionsCurPage) do + if v[3] <= leaderArmR then + lineBelowInd = ind + break + end + end + + -- choose the desired position for the leader arm + -- (later we check if the label is high enough to shift the port to this position) + local desiredPos = leaderArmR + if lineBelowInd == nil then + -- there is no line below the leaderArmR + local lowestLine = linePositionsCurPage[#linePositionsCurPage] + if lowestLine ~= nil and lowestLine[3] - leaderArmR < poMinDistLine then + -- leader is too near to lowest line on page + -- -> use the valid position below this line + desiredPos = getPosBelowLine(linePositionsCurPage, #linePositionsCurPage) + end + else + local lineBelow = linePositionsCurPage[lineBelowInd] + local lineAbove = linePositionsCurPage[lineBelowInd - 1] + if lineBelow ~= nil and leaderArmR - lineBelow[2] < poMinDistLine then + -- leader is too near (or conflicting) to the line below + -- -> move below or above this line (using the position closer to the original one) + + local posAbove = getPosAboveLine(linePositionsCurPage, lineBelowInd) + local posBelow = getPosBelowLine(linePositionsCurPage, lineBelowInd) + + -- chose position which is closer to east anchor + if posAbove - leaderArmR <= leaderArmR - posBelow then + desiredPos = posAbove + else + desiredPos = posBelow + end + elseif lineAbove ~= nil and lineAbove[3] - leaderArmR < poMinDistLine then + -- leader is too near to the line below + -- -> use the valid position above this line + desiredPos = getPosAboveLine(linePositionsCurPage, lineBelowInd) + end + end + + -- check if label is high enough to move leader to desired position + if math.abs(desiredPos - leaderArmR) <= r:getHeight() / 2 + noteInnerSep then + leaderShiftR = desiredPos - leaderArmR + leaderArmR = desiredPos + else + leaderShiftR = 0 + end + else + leaderArmR = labelTopR - noteInnerSep - r:getHeight() / 2 + end + + -- determine index of last point above arm + local pointAboveArm = 0 + for k, ind in pairs(notes) do + v = notesForPage[ind] + if v.inputY >= leaderArmR then + pointAboveArm = k + else + break + end + end + local numPointsAbove = 0 + local numPointsBelow = 0 + for _, v in pairs(pointsBetween) do + if v == rIndex then + break + end + + local note = notesForPage[v] + if note.inputY >= leaderArmR then + numPointsAbove = numPointsAbove + 1 + else + numPointsBelow = numPointsBelow + 1 + end + end + + local optAbove, optBelow + if pointAboveArm < topPoint then + optAbove = optEmpty + elseif slotIndexR - 1 < topSlot then + optAbove = optEmpty + elseif pointAboveArm > bottomPoint then + optAbove = opt[topPoint][bottomPoint][topSlot][slotIndexR - 1][numPointsAbove] + else + optAbove = opt[topPoint][pointAboveArm][topSlot][slotIndexR - 1][numPointsAbove] + end + if pointAboveArm + 1 > bottomPoint then + optBelow = optEmpty + elseif slotIndexR + r.rasterSlots > bottomSlot then + optBelow = optEmpty + elseif pointAboveArm + 1 < topPoint then + optBelow = opt[topPoint][bottomPoint][slotIndexR + r.rasterSlots][bottomSlot][numPointsBelow] + else + optBelow = opt[pointAboveArm + 1][bottomPoint][slotIndexR + r.rasterSlots][bottomSlot][numPointsBelow] + end + + local partitionValid = true + if optAbove == nil or optBelow == nil then + partitionValid = false + else + local labeledAboveArm = table.keys(optAbove.positions) + local labeledBelowArm = table.keys(optBelow.positions) + if #labeledAboveArm + #labeledBelowArm + 1 ~= numLabeledSites then + partitionValid = false + else + -- test if all of labeledNotes are in one of the sets + -- last element (= r) must not be tested + for testIndex = 1, (numLabeledSites - 1) do + if not table.contains(labeledAboveArm, labeledSites[testIndex]) + and not table.contains(labeledBelowArm, labeledSites[testIndex]) then + partitionValid = false + end + end + end + end + + if partitionValid then + local newVal = math.abs(r.inputY - leaderArmR) + optAbove.totalLength + optBelow.totalLength + if newVal < bestVal then + bestVal = newVal + bestOpt = {} + bestOpt.totalLength = newVal + bestOpt.positions = {} + for k, v in pairs(optAbove.positions) do + bestOpt.positions[k] = v + end + for k, v in pairs(optBelow.positions) do + bestOpt.positions[k] = v + end + -- DEBUG + if bestOpt.positions[rIndex] ~= nil then + error("WARNING: Overwriting position of " .. rIndex .. " from " .. bestOpt.positions[rIndex] .. " to " .. slotIndexR) + end + bestOpt.positions[rIndex] = slotIndexR + bestOpt.leaderShiftY = {} + if avoidLines then + for k, v in pairs(optAbove.leaderShiftY) do + bestOpt.leaderShiftY[k] = v + end + for k, v in pairs(optBelow.leaderShiftY) do + bestOpt.leaderShiftY[k] = v + end + bestOpt.leaderShiftY[rIndex] = leaderShiftR + end + end + end + end + if next(bestOpt) ~= nil then -- bestOpt is not an empty table + --print("setting opt[" .. topPoint .. "][" .. bottomPoint .. + --"][" .. topSlot .. "][" .. bottomSlot .. "][" .. numLabeledSites .. "] = " .. inspect(bestOpt)) + opt[topPoint][bottomPoint][topSlot][bottomSlot][numLabeledSites] = bestOpt + else + --print ("WARNING: Found no valid position for label in opt[" .. topPoint .. "][" .. bottomPoint .. + --"][" .. topSlot .. "][" .. bottomSlot .. "][" .. numLabeledSites .. "] = " .. inspect(bestOpt)) + opt[topPoint][bottomPoint][topSlot][bottomSlot][numLabeledSites] = nil + end + end + end + end + end + end + end + + if #notes > 0 then + --DEBUG + local maxPlaced = 0 + for i = 1,#notes do + if opt[1][#notes][1][totalNumSlots][i] ~= nil then + maxPlaced = i + end + end + + if maxPlaced < #notes then + print("WARNING: could not fit all labels on page") + end + + print("Using result: opt[1][" .. #notes .. + "][1][" .. totalNumSlots .. "][" .. maxPlaced .. "]") + local res = opt[1][#notes][1][totalNumSlots][maxPlaced] + local length = res.totalLength + local positions = res.positions + local leaderShiftY = res.leaderShiftY + + print("") + print("----------------") + print("resulting length: " .. length) + print("") + print("resulting positions:") + print(inspect(positions)) + print("") + print("resulting leaderShifts:") + print(inspect(leaderShiftY)) + print("") + print("") + print("") + + + -- DEBUG on page + if todonotesDebug then + tex.print("\\node[text=blue,fill=white,rectangle,align=center] at (10.5cm,-27cm) {" .. + "total length: " .. number.tocentimeters(length) .. "\\\\ " .. + "rasterHeight: " .. number.tocentimeters(rasterHeight) .. + "};") + end + + for _, ind in pairs(notes) do + local v = notesForPage[ind] + v.outputX = area.left + v.leaderShiftX = 0 + if positions[ind] == nil then + print("did not define a position for note " .. v.index) + v.outputY = 0 + v.leaderShiftY = 0 + else + v.outputY = getRasterAbsolute(rasterHeight, area.top, positions[ind]) + if leaderShiftY[ind] ~= nil then + v.leaderShiftY = leaderShiftY[ind] + end + end + end + end +end +positioningAlgos["poLeaders"] = {algo = posPoLeaders, + leaderAnchor = "east", + leaderShift = false, + twoSided = true} + +function posPoLeadersAvoid(notes, rightSide) + posPoLeaders(notes, rightSide, true) +end +positioningAlgos["poLeadersAvoidLines"] = {algo = posPoLeadersAvoid, + leaderAnchor = "east", + leaderShift = true, + twoSided = true, + needLinePositions = true} + + + + +-- ********** Splitting Algorithms ********** + +-- ** splittingAlgorithm: none +-- place all notes on the wider side +function splitNone() + local rightSideSelected = false + if labelArea.left == nil and labelArea.right == nil then + error("Cannot place labels on any side of text (not enough space). " .. + "Consider using the minNoteWidth option of the package to " .. + "decrease the minimum width required") + elseif labelArea.left == nil then + rightSideSelected = true + elseif labelArea.right ~= nil and + labelArea.right.noteWidth > labelArea.left.noteWidth then + rightSideSelected = true + end + + for _, v in pairs(notesForPage) do + v.rightSide = rightSideSelected + end +end +splittingAlgos["none"] = {algo = splitNone} + + + +-- ** splittingAlgorithm: middle +-- split on middle of page +function splitMiddle() + if labelArea:isOneSided() then + splitNone() + else + local splitLine = (labelArea.text.right + labelArea.text.left)/2 + for _, v in pairs(notesForPage) do + if v.inputX <= splitLine then + v.rightSide = false + else + v.rightSide = true + end + end + end +end +splittingAlgos["middle"] = {algo = splitMiddle} + + + +-- ** splittingAlgorithm: median +-- split at median (sites sorted by x-coordinate) +function splitMedian() + if labelArea:isOneSided() then + splitNone() + else + if #notesForPage == 0 then + return + end + + -- list that contains notes sorted by their inputX-coordinate + local notesSorted = {} + for _, v in pairs(notesForPage) do + table.insert(notesSorted, v) + end + table.sort(notesSorted, compareNoteInputXAsc) + + local maxIndLeft + if #notesSorted % 2 == 1 then + maxIndLeft = math.ceil(#notesSorted / 2) + else + maxIndLeft = #notesSorted / 2 + end + + for k, v in pairs(notesSorted) do + if k <= maxIndLeft then + v.rightSide = false + else + v.rightSide = true + end + end + end +end +splittingAlgos["median"] = {algo = splitMedian} + + + +-- ** splittingAlgorithm: weightedMedian +-- split at weighted median (sites sorted by x-coordinate) +-- sum of heights of labels on both sides are similiar to each other +function splitWeightedMedian() + if labelArea:isOneSided() then + splitNone() + else + if #notesForPage == 0 then + return + end + + -- list that contains notes sorted by their inputX-coordinate + local notesSorted = {} + for _, v in pairs(notesForPage) do + table.insert(notesSorted, v) + end + table.sort(notesSorted, compareNoteInputXAsc) + + local heightLeft = 0 + local heightRight = 0 + while #notesSorted > 0 do + if heightRight < heightLeft then + -- place next note on the right side + local note = notesSorted[#notesSorted] + note.rightSide = true + heightRight = heightRight + note:getHeight() + 2 * noteInnerSep + noteInterSpace + table.remove(notesSorted, #notesSorted) + else + -- place next note on the left side + local note = notesSorted[1] + note.rightSide = false + heightLeft = heightLeft + note:getHeight() + 2 * noteInnerSep + noteInterSpace + table.remove(notesSorted, 1) + end + end + end +end +splittingAlgos["weightedMedian"] = {algo = splitWeightedMedian} + diff --git a/Master/texmf-dist/tex/lualatex/luatodonotes/luatodonotes.sty b/Master/texmf-dist/tex/lualatex/luatodonotes/luatodonotes.sty new file mode 100644 index 00000000000..6f7a193ccce --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luatodonotes/luatodonotes.sty @@ -0,0 +1,634 @@ +%% +%% This is file `luatodonotes.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% luatodonotes.dtx (with options: `package') +%% +%% This is a generated file. +%% +%% Copyright (C) 2014 by Fabian Lipp <fabian.lipp@gmx.de> +%% based on the todonotes package by +%% Henrik Skov Midtiby <henrikmidtiby@gmail.com> +%% +%% This file may be distributed and/or modified under the conditions of +%% the LaTeX Project Public License, either version 1.2 of this license +%% or (at your option) any later version. The latest version of this +%% license is in: +%% +%% http://www.latex-project.org/lppl.txt +%% +%% and version 1.2 or later is part of all distributions of LaTeX version +%% 1999/12/01 or later. +%% +\NeedsTeXFormat{LaTeX2e}[1999/12/01] +\ProvidesPackage{luatodonotes} + [2014/08/07 v0.1 luatodonotes source and documentation.] + +\ProvidesPackage{luatodonotes}[2014/06/08] +\RequirePackage{ifthen} +\RequirePackage{xkeyval} +\RequirePackage{xcolor} +\RequirePackage{tikz} +\usetikzlibrary{positioning} +\usetikzlibrary{intersections} +\usetikzlibrary{decorations.pathmorphing} +\RequirePackage{luacode} +\RequirePackage{luatex} +\RequirePackage{atbegshi} +\RequirePackage{xstring} +\RequirePackage{zref-abspage} +\RequirePackage{ifoddpage} +\RequirePackage{soul} +\RequirePackage{soulpos} +\newcommand{\@todonotes@text}{}% +\newcommand{\@todonotes@backgroundcolor}{orange} +\newcommand{\@todonotes@linecolor}{black!30} +\newcommand{\@todonotes@bordercolor}{black} +\newcommand{\@todonotes@leaderwidth}{1.6pt} +\newcommand{\@todonotes@textsize}{\normalsize} +\newcommand{\@todonotes@figwidth}{\columnwidth} +\newcommand{\@todonotes@figheight}{4cm} +\newcommand{\@todonotes@positioning}{inputOrderStacks} +\newcommand{\@todonotes@splitting}{none} +\newcommand{\@todonotes@leadertype}{opo} +\newcommand{\@todonotes@interNoteSpace}{5pt} +\newcommand{\@todonotes@noteInnerSep}{5pt} +\newcommand{\@todonotes@routingAreaWidth}{0.4cm} +\newcommand{\@todonotes@minNoteWidth}{2.0cm} +\newcommand{\@todonotes@distanceNotesPageBorder}{0.5cm} +\newcommand{\@todonotes@distanceNotesText}{0.2cm} +\newcommand{\@todonotes@rasterHeight}{1cm} +\AtBeginDocument{ +\ifx\undefined\phantomsection +\newcommand{\phantomsection}{} +\fi +} +\newcommand{\@todonotes@todolistname}{Todo list} +\newcommand{\@todonotes@MissingFigureText}{Figure} +\newcommand{\@todonotes@MissingFigureUp}{Missing} +\newcommand{\@todonotes@MissingFigureDown}{figure} +\newcommand{\@todonotes@SetTodoListName}[1] + {\renewcommand{\@todonotes@todolistname}{#1}} +\newcommand{\@todonotes@SetMissingFigureText}[1] + {\renewcommand{\@todonotes@MissingFigureText}{#1}} +\newcommand{\@todonotes@SetMissingFigureUp}[1] + {\renewcommand{\@todonotes@MissingFigureUp}{#1}} +\newcommand{\@todonotes@SetMissingFigureDown}[1] + {\renewcommand{\@todonotes@MissingFigureDown}{#1}} +\newif{\if@todonotes@reverseMissingFigureTriangle} +\DeclareOptionX{catalan}{ + \@todonotes@SetTodoListName{Llista de feines pendents}% + \@todonotes@SetMissingFigureText{Figura}% + \@todonotes@SetMissingFigureUp{Figura}% + \@todonotes@SetMissingFigureDown{pendent}% +} +\DeclareOptionX{danish}{% + \@todonotes@SetTodoListName{G\o{}rem\aa{}lsliste}% + \@todonotes@SetMissingFigureText{Figur}% + \@todonotes@SetMissingFigureUp{Manglende}% + \@todonotes@SetMissingFigureDown{figur}% +} +\DeclareOptionX{dutch}{% + \@todonotes@SetTodoListName{Lijst van onafgewerkte taken}% + \@todonotes@SetMissingFigureText{Figuur}% + \@todonotes@SetMissingFigureUp{Ontbrekende}% + \@todonotes@SetMissingFigureDown{figuur}% +} +\DeclareOptionX{english}{% + \@todonotes@SetTodoListName{Todo list}% + \@todonotes@SetMissingFigureText{Figure}% + \@todonotes@SetMissingFigureUp{Missing}% + \@todonotes@SetMissingFigureDown{figure}% +} +\DeclareOptionX{french}{% + \@todonotes@SetTodoListName{Liste des points \`a traiter}% + \@todonotes@SetMissingFigureText{Figure}% + \@todonotes@SetMissingFigureUp{Figure}% + \@todonotes@SetMissingFigureDown{manquante}% + \@todonotes@reverseMissingFigureTrianglefalse +} +\DeclareOptionX{german}{% + \@todonotes@SetTodoListName{Liste der noch zu erledigenden Punkte}% + \@todonotes@SetMissingFigureText{Abbildung}% + \@todonotes@SetMissingFigureUp{Fehlende}% + \@todonotes@SetMissingFigureDown{Abbildung}% +} +\DeclareOptionX{italian}{ + \@todonotes@SetTodoListName{Elenco delle cose da fare}% + \@todonotes@SetMissingFigureText{Figura}% + \@todonotes@SetMissingFigureUp{Figura}% + \@todonotes@SetMissingFigureDown{mancante}% +} +\DeclareOptionX{ngerman}{% + \@todonotes@SetTodoListName{Liste der noch zu erledigenden Punkte}% + \@todonotes@SetMissingFigureText{Abbildung}% + \@todonotes@SetMissingFigureUp{Fehlende}% + \@todonotes@SetMissingFigureDown{Abbildung}% +} +\DeclareOptionX{portuguese}{ + \@todonotes@SetTodoListName{Lista de tarefas pendentes}% + \@todonotes@SetMissingFigureText{Figura}% + \@todonotes@SetMissingFigureUp{Figura}% + \@todonotes@SetMissingFigureDown{pendente}% +} +\DeclareOptionX{spanish}{ + \@todonotes@SetTodoListName{Lista de tareas pendientes}% + \@todonotes@SetMissingFigureText{Figura}% + \@todonotes@SetMissingFigureUp{Figura}% + \@todonotes@SetMissingFigureDown{pendiente}% +} +\DeclareOptionX{swedish}{% + \@todonotes@SetTodoListName{Att g\"{o}ra-lista}% + \@todonotes@SetMissingFigureText{Figur}% + \@todonotes@SetMissingFigureUp{Figur}% + \@todonotes@SetMissingFigureDown{saknas}% +} +\newcounter{@todonotes@numberoftodonotes} +\newcounter{@todonotes@numberofLinesInArea} +\newif{\if@todonotes@obeyDraft} +\DeclareOptionX{obeyDraft}{\@todonotes@obeyDrafttrue} +\newif{\if@todonotes@isDraft} +\DeclareOptionX{draft}{\@todonotes@isDrafttrue} +\DeclareOptionX{draftcls}{\@todonotes@isDrafttrue} +\DeclareOptionX{draftclsnofoot}{\@todonotes@isDrafttrue} +\newif{\if@todonotes@obeyFinal} +\DeclareOptionX{obeyFinal}{\@todonotes@obeyFinaltrue} +\newif{\if@todonotes@isFinal} +\DeclareOptionX{final}{\@todonotes@isFinaltrue} +\newif{\if@todonotes@disabled} +\DeclareOptionX{disable}{\@todonotes@disabledtrue} +\newif{\if@todonotes@colorinlistoftodos} +\DeclareOptionX{colorinlistoftodos}{\@todonotes@colorinlistoftodostrue} +\DeclareOptionX{dvistyle}{\PackageWarningNoLine{luatodonotes} + {Parameter dvistyle is not supported by luatodonotes. + Ignoring this option}} +\define@key{luatodonotes.sty}% + {color}{ + \renewcommand{\@todonotes@backgroundcolor}{#1} + \renewcommand{\@todonotes@linecolor}{#1}} +\define@key{luatodonotes.sty}% + {backgroundcolor}{\renewcommand{\@todonotes@backgroundcolor}{#1}} +\define@key{luatodonotes.sty}% + {linecolor}{\renewcommand{\@todonotes@linecolor}{#1}} +\define@key{luatodonotes.sty}% + {bordercolor}{\renewcommand{\@todonotes@bordercolor}{#1}} +\define@key{luatodonotes.sty}% + {leaderwidth}{\renewcommand{\@todonotes@leaderwidth}{#1}} +\newif{\if@todonotes@prependcaptionglobal} +\@todonotes@prependcaptionglobalfalse +\DeclareOptionX{prependcaption}{\@todonotes@prependcaptionglobaltrue} +\define@key{luatodonotes.sty}% + {textwidth}{\PackageWarningNoLine{luatodonotes} + {Parameter textwidth is not supported by luatodonotes}} +\define@key{luatodonotes.sty}% + {textsize}{\renewcommand{\@todonotes@textsize}{\csname #1\endcsname}} +\newif{\if@todonotes@shadowenabled} +\@todonotes@shadowenabledfalse +\DeclareOptionX{shadow}{\@todonotes@shadowenabledtrue +\usetikzlibrary{shadows}} +\define@key{luatodonotes.sty}% + {figwidth}{\renewcommand{\@todonotes@figwidth}{#1}} +\define@key{luatodonotes.sty}% + {figheight}{\renewcommand{\@todonotes@figheight}{#1}} +\define@key{luatodonotes.sty}% + {positioning}{\renewcommand{\@todonotes@positioning}{#1}} +\define@key{luatodonotes.sty}% + {splitting}{\renewcommand{\@todonotes@splitting}{#1}} +\define@key{luatodonotes.sty}% + {leadertype}{\renewcommand{\@todonotes@leadertype}{#1}} +\define@key{luatodonotes.sty}% + {interNoteSpace}{\renewcommand{\@todonotes@interNoteSpace}{#1}} +\define@key{luatodonotes.sty}% + {noteInnerSep}{\renewcommand{\@todonotes@noteInnerSep}{#1}} +\define@key{luatodonotes.sty}% + {routingAreaWidth}{\renewcommand{\@todonotes@routingAreaWidth}{#1}} +\define@key{luatodonotes.sty}% + {minNoteWidth}{\renewcommand{\@todonotes@minNoteWidth}{#1}} +\define@key{luatodonotes.sty}% + {distanceNotesPageBorder}% + {\renewcommand{\@todonotes@distanceNotesPageBorder}{#1}} +\define@key{luatodonotes.sty}% + {distanceNotesText}{\renewcommand{\@todonotes@distanceNotesText}{#1}} +\define@key{luatodonotes.sty}% + {rasterHeight}{\renewcommand{\@todonotes@rasterHeight}{#1}} +\newif{\if@todonotes@debugenabled} +\@todonotes@debugenabledfalse +\DeclareOptionX{debug}{\@todonotes@debugenabledtrue} +\ProcessOptionsX* +\if@todonotes@disabled +\else + \if@todonotes@obeyDraft + \@todonotes@disabledtrue + \if@todonotes@isDraft + \@todonotes@disabledfalse + \fi + \fi + \if@todonotes@obeyFinal + \@todonotes@disabledfalse + \if@todonotes@isFinal + \@todonotes@disabledtrue + \fi + \fi +\fi +\newdimen\@todonotes@extractx +\newdimen\@todonotes@extracty +\newsavebox\@todonotes@heightcalcbox +\newdimen\@todonotes@heightcalcboxdepth +\newdimen\@todonotes@heightcalcboxheight +\newsavebox\@todonotes@notetextbox +\newdimen\@todonotes@baselineskip +\newdimen\@todonotes@fontsize +\newdimen\@todonotes@currentsidemargin +\directlua{require("luatodonotes")} +\directlua{noteInnerSep = + string.todimen("\luatexluaescapestring{\@todonotes@noteInnerSep}")} +\directlua{noteInterSpace = + string.todimen("\luatexluaescapestring{\@todonotes@interNoteSpace}")} +\directlua{routingAreaWidth = + string.todimen("\luatexluaescapestring{\@todonotes@routingAreaWidth}")} +\directlua{minNoteWidth = + string.todimen("\luatexluaescapestring{\@todonotes@minNoteWidth}")} +\directlua{distanceNotesPageBorder = + string.todimen("\luatexluaescapestring{\@todonotes@distanceNotesPageBorder}")} +\directlua{distanceNotesText = + string.todimen("\luatexluaescapestring{\@todonotes@distanceNotesText}")} +\directlua{rasterHeight = + string.todimen("\luatexluaescapestring{\@todonotes@rasterHeight}")} +\IfStrEqCase{\@todonotes@positioning}{% + {inText}{\directlua{positioning = positioningAlgos["inText"]}}% + {inputOrder}{\directlua{positioning = positioningAlgos["inputOrder"]}}% + {inputOrderStacks}{\directlua{positioning = + positioningAlgos["inputOrderStacks"]}}% + {sLeaderNorthEast}{\directlua{positioning = + positioningAlgos["sLeaderNorthEast"]}}% + {sLeaderNorthEastBelow}{\directlua{positioning = + positioningAlgos["sLeaderNorthEastBelow"]}}% + {sLeaderEast}{\directlua{positioning = + positioningAlgos["sLeaderEast"]}}% + {poLeaders}{\directlua{positioning = positioningAlgos["poLeaders"]}}% + {poLeadersAvoidLines}{\directlua{positioning = + positioningAlgos["poLeadersAvoidLines"]}}% + {sLeaderNorthEastBelowStacks}{\directlua{positioning = + positioningAlgos["sLeaderNorthEastBelowStacks"]}}}% + [\directlua{positioning = positioningAlgos["inputOrderStacks"]} + \PackageWarningNoLine{luatodonotes} + {Invalid value for parameter positioning: \@todonotes@positioning}] +\IfStrEqCase{\@todonotes@splitting}{% + {none}{\directlua{splitting = splittingAlgos["none"]}}% + {middle}{\directlua{splitting = splittingAlgos["middle"]}}% + {median}{\directlua{splitting = splittingAlgos["median"]}}% + {weightedMedian}{\directlua{splitting = splittingAlgos["weightedMedian"]}}}% + [\directlua{splitting = splittingAlgos["none"]} + \PackageWarningNoLine{luatodonotes} + {Invalid value for parameter split: \@todonotes@splitting}] +\IfStrEqCase{\@todonotes@leadertype}{% + {s}{\directlua{leaderType = leaderTypes["s"]}}% + {opo}{\directlua{leaderType = leaderTypes["opo"]}}% + {po}{\directlua{leaderType = leaderTypes["po"]}}% + {sBezier}{\directlua{leaderType = leaderTypes["sBezier"]}}% + {os}{\directlua{leaderType = leaderTypes["os"]}}}% + [\directlua{leaderType = leaderTypes["opo"]} + \PackageWarningNoLine{luatodonotes} + {Invalid value for parameter leadertype: \@todonotes@leadertype}] +\def\@todonotes@pdflastypos{\the\pdflastypos} +\newcommand{\@todonotes@lineposition}[3]{% + \directlua{linePositionsAddLine(#1,#2,#3)}% +} +\newcommand{\@todonotes@nextpage}{% + \directlua{linePositionsNextPage()}% +}% +\newcommand{\@todonotes@writeNextpageToLpo}{% + \ifdefined\tf@lpo% + \immediate\write\tf@lpo{\@backslashchar @todonotes@nextpage}% + \fi +} +\if@todonotes@debugenabled + \directlua{todonotesDebug = true} + \newcommand{\@todonotes@AtBeginShipoutUpperLeft} + {\AtBeginShipoutUpperLeftForeground} +\else + \directlua{todonotesDebug = false} + \newcommand{\@todonotes@AtBeginShipoutUpperLeft} + {\AtBeginShipoutUpperLeft} +\fi +\directlua{initTodonotes()} +\soulregister{\ }{0} +\newlength{\todonotes@textmark@width} +\newlength{\todonotes@textmark@fontsize} +\newlength{\todonotes@textmark@linebelow} +\newlength{\todonotes@textmark@lineabove} +\ulposdef{\todonotes@textmark@highlight}{% + \setlength\todonotes@textmark@width\ulwidth% + \setlength\todonotes@textmark@fontsize{\f@size pt}% + \stepcounter{@todonotes@numberofLinesInArea}% + \ifulstarttype{0}% + {% begin of area + \def\todonotes@textmark@decoLeft{}% + \def\todonotes@textmark@shift{-2pt}% + \addtolength\todonotes@textmark@width{2pt}% + \setcounter{@todonotes@numberofLinesInArea}{1}}% + {\def\todonotes@textmark@decoLeft{@todonotes@todoarea}% + \def\todonotes@textmark@shift{-4pt}% + \addtolength\todonotes@textmark@width{4pt}}% + \ifulendtype{0}% + {% last line of area + \def\todonotes@textmark@decoRight{}% + \addtolength\todonotes@textmark@width{2pt}% + \directlua{processLastLineInTodoArea()}}% + {\def\todonotes@textmark@decoRight{@todonotes@todoarea}% + \addtolength\todonotes@textmark@width{4pt}}% + \newcommand{\@todonotes@nodeNamePrefix}% + {@todonotes@\arabic{@todonotes@numberoftodonotes}% + @\arabic{@todonotes@numberofLinesInArea} }% + \hspace*{\todonotes@textmark@shift}{\smash{% + \begin{tikzpicture}[overlay,remember picture, + deco/.style={}]% + \setlength\todonotes@textmark@linebelow% + {-0.95\dimexpr\baselineskip-\f@size pt\relax}% + \setlength\todonotes@textmark@lineabove% + {\dimexpr\f@size pt+\todonotes@textmark@linebelow\relax}% + \coordinate + (\@todonotes@nodeNamePrefix areaSW) + at (0,\todonotes@textmark@linebelow); + \coordinate + (\@todonotes@nodeNamePrefix areaSE) + at (\todonotes@textmark@width, \todonotes@textmark@linebelow); + \coordinate + (\@todonotes@nodeNamePrefix areaNE) + at (\todonotes@textmark@width,\todonotes@textmark@lineabove); + \coordinate + (\@todonotes@nodeNamePrefix areaNW) + at (0,\todonotes@textmark@lineabove); + \draw[draw=green!70,fill=green,fill opacity=.2] + (\@todonotes@nodeNamePrefix areaSW) + decorate[\todonotes@textmark@decoLeft] { + -- (\@todonotes@nodeNamePrefix areaNW) + } + -- (\@todonotes@nodeNamePrefix areaNE) + decorate[\todonotes@textmark@decoRight] { + -- (\@todonotes@nodeNamePrefix areaSE) + } + -- cycle; + \end{tikzpicture}% + }}% +}% +\newcommand{\@todonotes@currentlinecolor}{}% +\newcommand{\@todonotes@currentbackgroundcolor}{}% +\newcommand{\@todonotes@currentbordercolor}{}% +\define@key{todonotes}{color}{% + \renewcommand{\@todonotes@currentlinecolor}{#1}% + \renewcommand{\@todonotes@currentbackgroundcolor}{#1}}% +\define@key{todonotes}{linecolor}{% + \renewcommand{\@todonotes@currentlinecolor}{#1}}% +\define@key{todonotes}{backgroundcolor}{% + \renewcommand{\@todonotes@currentbackgroundcolor}{#1}}% +\define@key{todonotes}{bordercolor}{% + \renewcommand{\@todonotes@currentbordercolor}{#1}}% +\newcommand{\@todonotes@currentleaderwidth}{}% +\define@key{todonotes}{leaderwidth}{% + \renewcommand{\@todonotes@currentleaderwidth}{#1}}% +\newcommand{\@todonotes@sizecommand}{}% +\define@key{todonotes}{size}{\renewcommand{\@todonotes@sizecommand}{#1}}% +\newif\if@todonotes@localdisable% +\define@key{todonotes}{disable}[]{\@todonotes@localdisabletrue}% +\define@key{todonotes}{nodisable}[]{\@todonotes@localdisablefalse}% +\newif\if@todonotes@appendtolistoftodos% +\define@key{todonotes}{list}[]{\@todonotes@appendtolistoftodostrue}% +\define@key{todonotes}{nolist}[]{\@todonotes@appendtolistoftodosfalse}% +\newif\if@todonotes@inlinenote% +\define@key{todonotes}{inline}[]{\@todonotes@inlinenotetrue}% +\define@key{todonotes}{noinline}[]{\@todonotes@inlinenotefalse}% +\newif\if@todonotes@prependcaption% +\define@key{todonotes}{prepend}[]{\@todonotes@prependcaptiontrue}% +\define@key{todonotes}{noprepend}[]{\@todonotes@prependcaptionfalse}% +\newif\if@todonotes@line% +\define@key{todonotes}{line}[]{\@todonotes@linetrue}% +\define@key{todonotes}{noline}[]{\@todonotes@linefalse}% +\define@key{todonotes}{fancyline}[]{\PackageWarningNoLine{luatodonotes} + {Parameter fancyline is not supported by luatodonotes}}% +\define@key{todonotes}{nofancyline}[]{}% +\newcommand{\@todonotes@author}{}% +\newif\if@todonotes@authorgiven% +\define@key{todonotes}{author}{% + \renewcommand{\@todonotes@author}{#1}% + \@todonotes@authorgiventrue}% +\define@key{todonotes}{noauthor}[]{\@todonotes@authorgivenfalse}% +\newcommand{\@todonotes@caption}{}% +\newif\if@todonotes@captiongiven% +\define@key{todonotes}{caption}% + {\renewcommand{\@todonotes@caption}{#1}% + \@todonotes@captiongiventrue}% +\define@key{todonotes}{nocaption}[]{\@todonotes@captiongivenfalse}% +\newcommand{\@todonotes@currentfigwidth}{\@todonotes@figwidth} +\define@key{todonotes}% + {figwidth}{\renewcommand{\@todonotes@currentfigwidth}{#1}} +\newcommand{\@todonotes@currentfigheight}{\@todonotes@figheight} +\define@key{todonotes}% + {figheight}{\renewcommand{\@todonotes@currentfigheight}{#1}} +\presetkeys% + {todonotes}% + {linecolor=\@todonotes@linecolor,% + backgroundcolor=\@todonotes@backgroundcolor,% + bordercolor=\@todonotes@bordercolor,% + leaderwidth=\@todonotes@leaderwidth,% + nodisable,% + noinline,% + nocaption,% + noauthor,% + figwidth=\@todonotes@figwidth,% + figheight=\@todonotes@figheight,% + line, list, size=\@todonotes@textsize}{}% +\newif\if@todonotes@areaselected% +\newtoks\@todonotes@toks@currentlinecolor% +\newtoks\@todonotes@toks@currentbackgroundcolor% +\newtoks\@todonotes@toks@currentbordercolor% +\newtoks\@todonotes@toks@currentleaderwidth% +\newtoks\@todonotes@toks@sizecommand% +\if@todonotes@disabled% + \newcommand{\listoftodos}[1][]{} + \newcommand{\@todo}[2][]{} + \newcommand{\@todoarea}[3][]{} + \newcommand{\missingfigure}[2][]{} +\else % \if@todonotes@disabled +\newcommand{\listoftodos}[1][\@todonotes@todolistname] + {\@ifundefined{chapter}{\section*{#1}}{\chapter*{#1}} \@starttoc{tdo}} +\newcommand{\l@todo} + {\@dottedtocline{1}{0em}{2.3em}} +\tikzset{@todonotes@todoarea/.style={ + decoration={snake,amplitude=3.5pt,segment length=5pt}}} +\tikzset{@todonotes@notestyleraw/.style={ + line width=0.5pt, + inner sep = \@todonotes@noteInnerSep, + rounded corners=4pt}} +\if@todonotes@shadowenabled + \tikzset{@todonotes@notestyle/.style={@todonotes@notestyleraw, + general shadow={shadow xshift=.5ex, shadow yshift=-.5ex, + opacity=1,fill=black!50}}} +\else + \tikzset{@todonotes@notestyle/.style={@todonotes@notestyleraw}} +\fi +\tikzset{@todonotes@leader/.style={}} +\tikzset{@todonotes@textmark/.style={rounded corners}} +\tikzset{@todonotes@inlinenote/.style={ + @todonotes@notestyle, + draw=\@todonotes@currentbordercolor, + fill=\@todonotes@currentbackgroundcolor, + text width=\linewidth - 1.6 ex - 1 pt}} +\newcommand{\@todocommon}[2]{% +\if@todonotes@prependcaptionglobal% +\@todonotes@prependcaptiontrue% +\else% +\@todonotes@prependcaptionfalse% +\fi% +\renewcommand{\@todonotes@text}{#2}% +\renewcommand{\@todonotes@caption}{#2}% +\setkeys{todonotes}{#1}% +\if@todonotes@localdisable% +\else% +\addtocounter{@todonotes@numberoftodonotes}{1}% +\if@todonotes@appendtolistoftodos% + \phantomsection% + \if@todonotes@captiongiven% + \else% + \renewcommand{\@todonotes@caption}{#2}% + \fi% + \@todonotes@addElementToListOfTodos% +\fi% +\if@todonotes@captiongiven% + \if@todonotes@prependcaption% + \renewcommand{\@todonotes@text}{\@todonotes@caption: #2}% + \fi% +\fi% +\if@todonotes@inlinenote% + \@todonotes@drawInlineNote% +\else% + \@todonotes@drawMarginNoteWithLine% +\fi%\if@todonotes@inlinenote +\fi%\if@todonotes@localdisable +}% +\newcommand{\@todo}[2][]{% + \@todonotes@areaselectedfalse% + \@todocommon{#1}{#2}% +}% +\newcommand{\@todoarea}[3][]{% + \@todonotes@areaselectedtrue% + \@todocommon{#1}{#2}% + \todonotes@textmark@highlight{#3}% + \begin{tikzpicture}[remember picture, overlay]% + \node [coordinate] (@todonotes@\arabic{@todonotes@numberoftodonotes} % + inTextEnd) {};% + \end{tikzpicture}% + \zref@label{@todonotes@\arabic{@todonotes@numberoftodonotes}@end}% +}% +\newcommand{\@todonotes@drawMarginNoteWithLine}{% + \begin{tikzpicture}[remember picture, overlay]% + \node [coordinate] (@todonotes@\arabic{@todonotes@numberoftodonotes} % + inText) {};% + \end{tikzpicture}% + \@todonotes@baselineskip=\baselineskip% + \@todonotes@fontsize=\f@size pt% + \zref@label{@todonotes@\arabic{@todonotes@numberoftodonotes}}% + \if@todonotes@authorgiven% + \let\@todonotes@text@old=\@todonotes@text + \renewcommand{\@todonotes@text}{\@todonotes@author: \@todonotes@text@old}% + \fi% + \edef\@todonotes@tmp{\@todonotes@currentlinecolor}% + \@todonotes@toks@currentlinecolor=\expandafter{\@todonotes@tmp}% + \edef\@todonotes@tmp{\@todonotes@currentbackgroundcolor}% + \@todonotes@toks@currentbackgroundcolor=\expandafter{\@todonotes@tmp}% + \edef\@todonotes@tmp{\@todonotes@currentbordercolor}% + \@todonotes@toks@currentbordercolor=\expandafter{\@todonotes@tmp}% + \edef\@todonotes@tmp{\@todonotes@currentleaderwidth}% + \@todonotes@toks@currentleaderwidth=\expandafter{\@todonotes@tmp}% + \@todonotes@toks@sizecommand=\expandafter{\@todonotes@sizecommand}% + \savebox\@todonotes@notetextbox{\@todonotes@sizecommand\@todonotes@text}% + \if@todonotes@line% + \def\@todonotes@param@drawLeader{true}% + \else% + \def\@todonotes@param@drawLeader{false}% + \fi% + \if@todonotes@areaselected% + \def\@todonotes@param@noteType{area}% + \else% + \def\@todonotes@param@noteType{}% + \fi% + \directlua{addNoteToList(\arabic{@todonotes@numberoftodonotes},% + \@todonotes@param@drawLeader,\luastringO{\@todonotes@param@noteType})}% +}% +\newcommand{\@todonotes@addElementToListOfTodos}{% + \if@todonotes@colorinlistoftodos% + \addcontentsline{tdo}{todo}{% + \fcolorbox{\@todonotes@currentbordercolor}% + {\@todonotes@currentbackgroundcolor}% + {\textcolor{\@todonotes@currentbackgroundcolor}{o}}% + \ \@todonotes@caption}% + \else% + \addcontentsline{tdo}{todo}{\@todonotes@caption}% + \fi}% +\newcommand{\@todonotes@drawInlineNote}{% + {\par\noindent\begin{tikzpicture}[remember picture]% + \draw node[@todonotes@inlinenote,font=\@todonotes@sizecommand]{% + \if@todonotes@authorgiven% + {\noindent \@todonotes@sizecommand % + \@todonotes@author:\,\@todonotes@text}% + \else% + {\noindent \@todonotes@sizecommand \@todonotes@text}% + \fi};% + \end{tikzpicture}\par}% + }% +\newcommand{\missingfigure}[2][]{% +\setkeys{todonotes}{#1}% +\addcontentsline{tdo}{todo}{\@todonotes@MissingFigureText: #2}% +\par +\noindent +\begin{tikzpicture} +\draw[fill=black!40, draw = white, line width=0pt] + (-2, -2.5) rectangle +(\@todonotes@currentfigwidth, \@todonotes@currentfigheight); +\draw (2, -0.3) node[right, text + width=\@todonotes@currentfigwidth-4.5cm] {#2}; +\draw[red, fill=white, rounded corners = 5pt, line width=10pt] + (30:2cm) -- (150:2cm) -- (270:2cm) -- cycle; +\draw (0, 0.3) node {\@todonotes@MissingFigureUp}; +\draw (0, -0.3) node {\@todonotes@MissingFigureDown}; +\end{tikzpicture} +}% Ending \missingfigure command +\fi % Ending \@todonotes@ifdisabled +\newcommand{\todototoc} +{ + \if@todonotes@disabled + \else + \addcontentsline{toc}{\@ifundefined{chapter}{section}{chapter}}{\@todonotes@todolistname} + \fi +} +\newcommand{\todo}[2][]{\@bsphack\@todo[#1]{#2}\@esphack\ignorespaces}% +\newcommand{\todoarea}[3][]{\@bsphack\@todoarea[#1]{#2}{#3}\@esphack}% +\if@todonotes@disabled +\else +\AtBeginShipout{% + \@todonotes@AtBeginShipoutUpperLeft{ + \@todonotes@writeNextpageToLpo + \checkoddpage% + \ifoddpageoroneside% + \@todonotes@currentsidemargin=\the\oddsidemargin% + \directlua{currentPageOdd = true}% + \else% + \@todonotes@currentsidemargin=\the\evensidemargin% + \directlua{currentPageOdd = false}% + \fi\relax% + \BeginCatcodeRegime\CatcodeTableLaTeX + \directlua{calcLabelAreaDimensions()}% + \directlua{calcHeightsForNotes()}% has to be outside of tikzpicture + \begin{tikzpicture}[remember picture,overlay] + \directlua{getInputCoordinatesForNotes()} + \directlua{printNotes()} + \end{tikzpicture}% + \directlua{clearNotes()}% + \EndCatcodeRegime + }% +} +\fi % Ending \@todonotes@ifdisabled +\endinput +%% +%% End of file `luatodonotes.sty'. diff --git a/Master/texmf-dist/tex/lualatex/luatodonotes/path_line.lua b/Master/texmf-dist/tex/lualatex/luatodonotes/path_line.lua new file mode 100644 index 00000000000..e365a36bf48 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luatodonotes/path_line.lua @@ -0,0 +1,120 @@ +-- Taken from luapower.com (Public domain) +-- +--math for 2D line segments defined as (x1, y1, x2, y2). + +local abs, min, max = math.abs, math.min, math.max + +local distance = require'path_point'.distance +local distance2 = require'path_point'.distance2 + +--evaluate a line at time t using linear interpolation. +--the time between 0..1 covers the segment interval. +local function point(t, x1, y1, x2, y2) + return x1 + t * (x2 - x1), y1 + t * (y2 - y1) +end + +--length of line at time t. +local function length(t, x1, y1, x2, y2) + return t * distance(x1, y1, x2, y2) +end + +--bounding box of line in (x,y,w,h) form. +local function bounding_box(x1, y1, x2, y2) + if x1 > x2 then x1, x2 = x2, x1 end + if y1 > y2 then y1, y2 = y2, y1 end + return x1, y1, x2-x1, y2-y1 +end + +--split line segment into two line segments at time t (t is capped between 0..1). +local function split(t, x1, y1, x3, y3) + t = min(max(t,0),1) + local x2, y2 = point(t, x1, y1, x3, y3) + return + x1, y1, x2, y2, --first segment + x2, y2, x3, y3 --second segment +end + +--intersect infinite line with its perpendicular from point (x, y); return the intersection point. +local function point_line_intersection(x, y, x1, y1, x2, y2) + local dx = x2 - x1 + local dy = y2 - y1 + local k = dx^2 + dy^2 + if k == 0 then return x1, y1 end --line has no length + local k = ((x - x1) * dy - (y - y1) * dx) / k + return x - k * dy, y + k * dx +end + +--return shortest distance-squared from point (x0, y0) to line, plus the touch point, and the time in the line +--where the touch point splits the line. +local function hit(x0, y0, x1, y1, x2, y2) + local x, y = point_line_intersection(x0, y0, x1, y1, x2, y2) + local tx = x2 == x1 and 0 or (x - x1) / (x2 - x1) + local ty = y2 == y1 and 0 or (y - y1) / (y2 - y1) + if tx < 0 or ty < 0 then --intersection occurs outside the segment, closer to the first endpoint + return distance2(x0, y0, x1, y1), x1, y1, 0 + elseif tx > 1 or ty > 1 then --intersection occurs outside the segment, closer to the second endpoint + return distance2(x0, y0, x2, y2), x2, y2, 1 + end + return distance2(x0, y0, x, y), x, y, max(tx, ty) +end + +--intersect line segment (x1, y1, x2, y2) with line segment (x3, y3, x4, y4). +--returns the time on the first line and the time on the second line where intersection occurs. +--if the intersection occurs outside the segments themselves, then t1 and t2 are outside the 0..1 range. +--if the lines are parallel or coincidental then t1 and t2 are infinite. +local function line_line_intersection(x1, y1, x2, y2, x3, y3, x4, y4) + local d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1) + if d == 0 then return 1/0, 1/0 end --lines are parallel or coincidental + return + ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d, + ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d +end + +--transform to a quad bezier that advances linearly i.e. the point on the line at t +--best matches the point on the curve at t. +local function to_bezier2(x1, y1, x2, y2) + return + x1, y1, + (x1 + x2) / 2, + (y1 + y2) / 2, + x2, y2 +end + +--transform to a cubic bezier that advances linearly i.e. the point on the line at t +--best matches the point on the curve at t. +local function to_bezier3(x1, y1, x2, y2) + return + x1, y1, + (2 * x1 + x2) / 3, + (2 * y1 + y2) / 3, + (x1 + 2 * x2) / 3, + (y1 + 2 * y2) / 3, + x2, y2 +end + +--parallel line segment at a distance on the right side of a segment. +--use a negative distance for the left side, or reflect the returned points against their respective initial points +local function offset(d, x1, y1, x2, y2) + local dx, dy = -(y2-y1), x2-x1 --normal vector of the same length as original segment + local k = d / distance(x1, y1, x2, y2) --normal vector scale factor + return --normal vector scaled and translated to (x1,y1) and (x2,y2) + x1 + dx * k, y1 + dy * k, + x2 + dx * k, y2 + dy * k +end + +if not ... then require'path_line_demo' end + +return { + point_line_intersection = point_line_intersection, + line_line_intersection = line_line_intersection, + to_bezier2 = to_bezier2, + to_bezier3 = to_bezier3, + offset = offset, + --path API + bounding_box = bounding_box, + point = point, + length = length, + split = split, + hit = hit, +} + diff --git a/Master/texmf-dist/tex/lualatex/luatodonotes/path_point.lua b/Master/texmf-dist/tex/lualatex/luatodonotes/path_point.lua new file mode 100644 index 00000000000..1144e6de093 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luatodonotes/path_point.lua @@ -0,0 +1,73 @@ +-- Taken from luapower.com (Public domain) + +--basic math for the cartesian plane. +--angles are expressed in degrees, not radians. + +local sqrt, abs, min, max, sin, cos, radians, degrees, atan2 = + math.sqrt, math.abs, math.min, math.max, math.sin, math.cos, math.rad, math.deg, math.atan2 + +--hypotenuse function: computes sqrt(a^2 + b^2) without underflow / overflow problems. +local function hypot(a, b) + if a == 0 and b == 0 then return 0 end + a, b = abs(a), abs(b) + a, b = max(a,b), min(a,b) + return a * sqrt(1 + (b / a)^2) +end + +--distance between two points. avoids underflow and overflow. +local function distance(x1, y1, x2, y2) + return hypot(x2-x1, y2-y1) +end + +--distance between two points squared. +local function distance2(x1, y1, x2, y2) + return (x2-x1)^2 + (y2-y1)^2 +end + +--point at a specified angle on a circle. +local function point_around(cx, cy, r, angle) + angle = radians(angle) + return + cx + cos(angle) * r, + cy + sin(angle) * r +end + +--rotate point (x,y) around origin (cx,cy) by angle. +local function rotate_point(x, y, cx, cy, angle) + if angle == 0 then return x, y end + angle = radians(angle) + x, y = x-cx, y-cy + local c, s = cos(angle), sin(angle) + return cx + x*c - y*s, cy + y*c + x*s +end + +--angle between two points in -360..360 degree range. +local function point_angle(x, y, cx, cy) + return degrees(atan2(y - cy, x - cx)) +end + +--reflect point through origin (i.e. rotate point 180deg around another point). +local function reflect_point(x, y, cx, cy) + return 2 * cx - x, 2 * cy - y +end + +--reflect point through origin at a specified distance. +local function reflect_point_distance(x, y, cx, cy, length) + local d = distance(x, y, cx, cy) + if d == 0 then return cx, cy end + local scale = length / d + return + cx + (cx - x) * scale, + cy + (cy - y) * scale +end + +return { + hypot = hypot, + distance = distance, + distance2 = distance2, + point_around = point_around, + rotate_point = rotate_point, + point_angle = point_angle, + reflect_point = reflect_point, + reflect_point_distance = reflect_point_distance, +} diff --git a/Master/tlpkg/bin/tlpkg-ctan-check b/Master/tlpkg/bin/tlpkg-ctan-check index dccdf988b7e..0b588ce947d 100755 --- a/Master/tlpkg/bin/tlpkg-ctan-check +++ b/Master/tlpkg/bin/tlpkg-ctan-check @@ -308,7 +308,7 @@ my @TLP_working = qw( luaindex luainputenc luaintro lualatex-doc lualatex-doc-de lualatex-math lualibs luamplib luaotfload - luasseq luatexbase luatexja luatexko luatextra luaxml + luasseq luatexbase luatexja luatexko luatextra luatodonotes luaxml lxfonts ly1 m-tx macros2e macroswap mafr magaz mailing mailmerge makebarcode makebox makecell makecirc makecmds makedtx makeglos makeplot diff --git a/Master/tlpkg/libexec/ctan2tds b/Master/tlpkg/libexec/ctan2tds index a6e9d016000..219f47f6238 100755 --- a/Master/tlpkg/libexec/ctan2tds +++ b/Master/tlpkg/libexec/ctan2tds @@ -1497,6 +1497,7 @@ $standardtex='\.(.bx|cfg|sty|clo|ldf|cls|def|fd|cmap|4ht)$'; 'luaintro', 'NULL', # doc pkg 'lualatex-math', '\.sty', # not phst-doc.cls 'luatexko', '\.lua|' . $standardtex, + 'luatodonotes','\.lua|' . $standardtex, 'luaxml', '\.lua|' . $standardtex, 'magyar', 'NULL', # do not install in runtime, conflicts with babel 'manjutex', '\.sty|\.clo|\.ldf|\.cls|\.def|\.fd$|manju.tex', diff --git a/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc b/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc index 88a92f004a7..118a5807890 100644 --- a/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc +++ b/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc @@ -27,6 +27,7 @@ depend luasseq depend luatexbase depend luatexko depend luatextra +depend luatodonotes depend luaxml depend odsfile depend placeat diff --git a/Master/tlpkg/tlpsrc/luatodonotes.tlpsrc b/Master/tlpkg/tlpsrc/luatodonotes.tlpsrc new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/Master/tlpkg/tlpsrc/luatodonotes.tlpsrc |