summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/cloze
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/generic/cloze')
-rw-r--r--macros/luatex/generic/cloze/README.md105
-rw-r--r--macros/luatex/generic/cloze/cloze.dtx907
-rw-r--r--macros/luatex/generic/cloze/cloze.ins57
-rw-r--r--macros/luatex/generic/cloze/cloze.lua1045
-rw-r--r--macros/luatex/generic/cloze/cloze.pdfbin0 -> 600199 bytes
5 files changed, 2114 insertions, 0 deletions
diff --git a/macros/luatex/generic/cloze/README.md b/macros/luatex/generic/cloze/README.md
new file mode 100644
index 0000000000..d6919d4c8d
--- /dev/null
+++ b/macros/luatex/generic/cloze/README.md
@@ -0,0 +1,105 @@
+# Description
+
+EN: `cloze` is a LuaLaTeX/LaTeX package to generate cloze. It uses the
+capabilities of the modern TeX engine LuaTex.
+
+DE: `cloze` ist a LuaLaTeX/LaTeX-Paket zum Erstellen von Lückentexten.
+Es nutzt die Möglichkeiten der modernen TeX-Engine LuaTeX.
+
+# License
+
+Copyright (C) 2015-2020 by Josef Friedrich <josef@friedrich.rocks>
+------------------------------------------------------------------------
+This work may be distributed and/or modified under the conditions of
+the LaTeX Project Public License, either version 1.3 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.3 or later is part of all distributions of LaTeX
+version 2005/12/01 or later.
+
+# CTAN
+
+Since July 2015 the cloze package is included in the Comprehensive TeX
+Archive Network (CTAN).
+
+* TeX archive: http://mirror.ctan.org/tex-archive/macros/luatex/latex/cloze
+* Package page: https://www.ctan.org/pkg/cloze
+
+# Repository
+
+https://github.com/Josef-Friedrich/cloze
+
+# Installation
+
+Get source:
+
+ git clone git@github.com:Josef-Friedrich/cloze.git
+ cd cloze
+
+Compile:
+
+ make
+
+or manually:
+
+ luatex cloze.ins
+ lualatex --shell-escape cloze.dtx
+ makeindex -s gglo.ist -o cloze.gls cloze.glo
+ makeindex -s gind.ist -o cloze.ind cloze.idx
+ lualatex --shell-escape cloze.dtx
+
+# Development
+
+First delete the stable version installed by TeX Live. Because the
+package `cloze` belongs to the collection `collection-latexextra`, the
+option `--force` must be used to delete the package.
+
+ tlmgr remove --force cloze
+
+## Deploying a new version
+
+Update the version number in the file `cloze.dtx` on this locations:
+
+### In the markup for the file `cloze.sty` (approximately at the line number 30)
+
+ %<*package>
+ [2020/05/20 v1.4 Package to typeset cloze worksheets or cloze tests]
+ %<*package>
+
+Add a changes entry (approximately at the line 90):
+
+```latex
+\changes{v1.4}{2020/05/20}{...}
+```
+
+### In the package documentation `documentation.tex` (approximately at the line number 125)
+
+```latex
+\date{v1.6~from 2020/06/30}
+```
+
+### In the markup for the file `cloze.lua` (approximately at the line number 1900)
+
+```lua
+if not modules then modules = { } end modules ['cloze'] = {
+ version = '1.4'
+}
+```
+
+### Update the copyright year:
+
+```
+sed -i 's/(C) 2015-2020/(C) 2015-2021/g' cloze.ins
+sed -i 's/(C) 2015-2020/(C) 2015-2021/g' cloze.dtx
+```
+
+### Command line tasks:
+
+```
+git tag v1.4
+make
+make ctan
+```
diff --git a/macros/luatex/generic/cloze/cloze.dtx b/macros/luatex/generic/cloze/cloze.dtx
new file mode 100644
index 0000000000..1c18af683b
--- /dev/null
+++ b/macros/luatex/generic/cloze/cloze.dtx
@@ -0,0 +1,907 @@
+% \iffalse meta-comment
+%
+% Copyright (C) 2015-2020 by Josef Friedrich <josef@friedrich.rocks>
+% ----------------------------------------------------------------------
+% This work may be distributed and/or modified under the conditions of
+% the LaTeX Project Public License, either version 1.3 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.3 or later is part of all distributions of LaTeX
+% version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is Josef Friedrich.
+%
+% This work consists of the files cloze.dtx and cloze.ins
+% and the derived filebase cloze.sty and cloze.lua.
+%
+% \fi
+%
+% \iffalse
+%<*driver>
+\ProvidesFile{cloze.dtx}
+%</driver>
+%<latex>\NeedsTeXFormat{LaTeX2e}[1999/12/01]
+%<latex>\ProvidesPackage{cloze}
+%<*latex>
+ [2020/06/60 v1.6 Package to typeset cloze worksheets or cloze tests]
+%</latex>
+% \fi
+%
+% \CheckSum{0}
+%
+% \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{v0.1}{2015/06/16}{Converted to DTX file}
+%
+% \changes{v1.0}{2015/07/08}{Inital release}
+%
+% \changes{v1.1}{2016/06/13}{Make cloze compatible to LuaTeX version
+% 0.95}
+%
+% \changes{v1.2}{2016/06/23}{The cloze makros are now working in
+% tabular, tabbing and picture environments}
+%
+% \changes{v1.3}{2017/03/13}{Add the new macros \cmd{\clozenol} and
+% \cmd{\clozeextend} and the environments \texttt{clozebox} and
+% \texttt{clozespace} (This version was not published on CTAN.)}
+%
+% \changes{v1.4}{2020/05/20}{Add the new macro \cmd{\clozestrike} and
+% improve the documentation}
+%
+% \changes{v1.5}{2020/05/27}{
+% * The Lua part of the package (cloze.lua) is now being developed in a
+% separate file.
+% * The readme file is now a standalone mardown file and not embedded in
+% the dtx file any more.
+% * \href{https://github.com/stevedonovan/LDoc}{LDoc} is being used
+% to generate
+% \href{https://josef-friedrich.github.io/cloze}{source code documentation}.
+% * This version fixes two bugs (cloze in display math, line color and
+% hide).
+% }
+%
+% \changes{v1.6}{2020/06/30}{
+% * Implement basic plain \TeX{} respectively plain Lua\TeX{} interface.
+% * Fix issue: Duplicate line generation on the second line in cloze.
+% * Fix issue: width of first line wrong in itemize, mdframed.
+% * Fix issue \#4: \texttt{\string\clozenol} not transparent.
+% * Fix issue: \texttt{clozebox} not transparent.
+% }
+%
+% \DoNotIndex{\newcommand,\newenvironment,\def,\directlua}
+%
+% \StopEventually{}
+% \pagebreak
+% \section{Implementation}
+%
+% \iffalse
+%<*plaintex>
+% \fi
+% \MacroTopsep = 10pt plus 2pt minus 2pt
+% \MacrocodeTopsep = 10pt plus 1.2pt minus 1pt
+%
+% \makeatletter
+% \c@CodelineNo 25 \relax
+% \makeatother
+%
+% \subsection{The file \tt{cloze.tex}}
+%
+% The cloze package uses following naming conventions: Internal / private
+% macros / commands / envirnoments are written in PascalCase, public
+% ones are written in lowercase. Earlier versions of this package used
+% @ characters for private macros. The lower level / private macros
+% are now defined in the plain Lua\TeX{} version of the package and
+% used to set cloze text in plain Lua\TeX{}.
+%
+% \begin{macrocode}
+\directlua{
+ cloze = require('cloze')
+}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\newif\ifclozeshow\clozeshowtrue
+% \end{macrocode}
+%
+% \subsubsection{Internal macros}
+%
+% \begin{macro}{\ClozeSetToGlobal}
+% Set the Lua variable |registry.is_global| to |true|. All options
+% are then stored in the variable |registry.global_options|.
+% \begin{macrocode}
+\def\ClozeSetToGlobal{%
+ \directlua{cloze.set_is_global(true)}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeSetToLocal}
+% First unset the variable |registry.local_options|. Now set the Lua
+% variable |registry.is_global| to |false|. All options are then stored
+% in the variable |registry.local_options|.
+% \begin{macrocode}
+\def\ClozeSetToLocal{%
+ \directlua{
+ cloze.unset_local_options()
+ cloze.set_is_global(false)
+ }%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeGetOption}
+% This macro is used in the documentation to show the default values
+% of some options.
+% \begin{macrocode}
+\def\ClozeGetOption#1{%
+ \directlua{
+ tex.print(cloze.get_value('#1'))
+ }%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeColor}
+% Convert a color definiton name to a PDF colorstack string, for example
+% convert the color name |blue| to the colorstack string
+% |0 0 1 rg 0 0 1 RG|. The macro definition |\ClozeColor{blue}|
+% builds itself the macro |\color@blue|, which expands to the PDF
+% colorstack string. The colorstack string is necessary to generate a
+% PDF colorstack whatsit.
+% \begin{macrocode}
+\def\ClozeColor#1{\csname\string\color@#1\endcsname}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeStartMarker}
+% At the begining |\ClozeStartMarker| registers the required Lua
+% callbacks. Then it inserts a whatsit marker which marks the begin of a
+% gap.
+% \begin{macrocode}
+\def\ClozeStartMarker#1{%
+ \strut\directlua{
+ cloze.register('#1')
+ cloze.marker('#1', 'start')
+ }%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeStopMarker}
+% \cmd{\ClozeStopMarker} inserts a whatsit marker that marks the end
+% of gap.
+% \begin{macrocode}
+\def\ClozeStopMarker#1{%
+ \strut\directlua{
+ cloze.marker('#1', 'stop')
+ }%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeMargin}
+% \cmd{\ClozeMargin} surrounds a text in a gap with two |kerns|.
+% \begin{macrocode}
+\def\ClozeMargin#1{%
+ \directlua{cloze.margin()}%
+ #1%
+ \directlua{cloze.margin()}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Public plain \TeX{} macros}
+%
+% \begin{macro}{\clozesetoption}
+% \cmd{\clozesetoption} is a wrapper for the Lua function
+% |registry.set_option|. \cmd{\clozesetoption}\marg{key}\marg{value}
+% sets a key \meta{key} to the value \meta{value}.
+% \begin{macrocode}
+\def\clozesetoption#1#2{%
+ \ClozeSetToGlobal%
+ \directlua{cloze.set_option('#1', '#2')}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozereset}
+% The usage of the command \cmd{\clozereset} is described in detail in
+% section \TmpSecRef{sec:command-clozereset}.
+% \begin{macrocode}
+\def\clozereset{%
+ \ClozeSetToGlobal%
+ \directlua{cloze.reset()}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozeshow}
+% The usage of the command \cmd{\clozeshow} is described in detail in
+% section \TmpSecRef{sec:command-clozeshow}.
+% \begin{macrocode}
+\def\clozeshow{%
+ \clozeshowtrue%
+ \ClozeSetToGlobal%
+ \clozesetoption{show}{true}%
+ \clozesetoption{hide}{false}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozehide}
+% The usage of the command \cmd{\clozehide} is described in detail in
+% section \TmpSecRef{sec:command-clozehide}.
+% \begin{macrocode}
+\def\clozehide{%
+ \clozeshowfalse%
+ \ClozeSetToGlobal%
+ \clozesetoption{hide}{true}%
+ \clozesetoption{show}{false}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\clozefont}
+% The usage of the command \cmd{\clozefont} is described in detail in
+% section \TmpSecRef{sec:command-clozefont}.
+% \begin{macrocode}
+\def\clozefont{\it}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozesetfont}
+% The usage of the command \cmd{\clozesetfont} is described in detail in
+% section \TmpSecRef{sec:command-clozesetfont}.
+% \begin{macrocode}
+\def\clozesetfont#1{%
+ \def\clozefont{%
+ #1%
+ }%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\cloze}
+% This is the plain Lua\TeX-Version of the macro \cmd{\cloze}.
+% \begin{macrocode}
+\def\cloze#1{%
+ \ClozeStartMarker{basic}%
+ {%
+ \clozefont\relax%
+ \ClozeMargin{#1}%
+ }%
+ \ClozeStopMarker{basic}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozefix}
+% This is the plain Lua\TeX-Version of \cmd{\clozefix}. The usage of the
+% command \cmd{\clozefix} is described in detail in
+% section \TmpSecRef{sec:command-clozefix}.
+% \begin{macrocode}
+\def\clozefix#1{%
+ \ClozeStartMarker{fix}%
+ {%
+ \clozefont\relax%
+ \ClozeMargin{#1}%
+ }%
+ \ClozeStopMarker{fix}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozenol}
+% This is the plain Lua\TeX-Version of the macro \cmd{\clozenol}. The
+% usage of the command \cmd{\clozenol} is described in detail in
+% section \TmpSecRef{sec:command-clozenol}.
+% \begin{macrocode}
+\def\clozenol#1{%
+ \clozesetoption{thickness}{0pt}%
+ \ClozeStartMarker{basic}%
+ {%
+ \clozefont\relax%
+ \ClozeMargin{#1}%
+ }%
+ \ClozeStopMarker{basic}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozeline}
+% This is the plain Lua\TeX-Version of the macro \cmd{\clozeline}. The
+% usage of the command \cmd{\clozeline} is described in detail in
+% section \TmpSecRef{sec:command-clozeline}.
+% \begin{macrocode}
+\def\clozeline{%
+ \directlua{cloze.line()}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozelinefil}
+% This is the plain Lua\TeX-Version of the macro \cmd{\clozelinefil}.
+% The usage of the command \cmd{\clozelinefil} is described in detail in
+% section \TmpSecRef{sec:command-clozelinefil}.
+% \begin{macrocode}
+\def\clozelinefil{%
+ \strut%
+ \directlua{cloze.linefil()}%
+ \strut%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozefil}
+% This is the plain Lua\TeX-Version of the macro \cmd{\clozefil}.
+% The usage of the command \cmd{\clozefil} is described in detail in
+% section \TmpSecRef{sec:command-clozefil}.
+% \begin{macrocode}
+\def\clozefil#1{%
+ \cloze{#1}\clozelinefil%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozeparcmd}
+% The usage of the macro \cmd{\clozeparcmd} is described in detail in
+% section \TmpSecRef{sec:command-clozeparcmd}.
+% \begin{macrocode}
+\def\clozeparcmd#1\par {%
+ \par%
+ \ClozeStartMarker{par}%
+ \clozefont\relax%
+ #1%
+ \ClozeStopMarker{par}%
+ \par%
+ \directlua{cloze.unregister('par')}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \iffalse
+%</plaintex>
+% \fi
+%
+% \iffalse
+%<*latex>
+% \fi
+% \MacroTopsep = 10pt plus 2pt minus 2pt
+% \MacrocodeTopsep = 10pt plus 1.2pt minus 1pt
+%
+% \makeatletter
+% \c@CodelineNo 25 \relax
+% \makeatother
+%
+% \subsection{The file \tt{cloze.sty}}
+%
+% \begin{macrocode}
+\input{cloze.tex}
+% \end{macrocode}
+%
+% This packages are used to build \emph{cloze}:
+%
+% \subsubsection{Dependencies}
+%
+% The package \href{https://www.ctan.org/pkg/fontspec}{fontspec} is not
+% necessarily required. When using Lua\LaTeX{} it is good form to load
+% it. Apart from this the package supplies helpful messages, when you
+% compile a Lua\LaTeX{} document with pdf\LaTeX{}.
+% \begin{macrocode}
+\RequirePackage{fontspec}
+% \end{macrocode}
+% The package \href{https://www.ctan.org/pkg/luatexbase}{luatexbase}
+% allows to register multiple Lua callbacks.
+% \begin{macrocode}
+\RequirePackage{luatexbase-mcb}
+% \end{macrocode}
+% The package \href{https://www.ctan.org/pkg/kvoptions}{kvoptions}
+% takes the handling of the options.
+% \begin{macrocode}
+\RequirePackage{kvoptions}
+% \end{macrocode}
+% The package \href{https://www.ctan.org/pkg/setspace}{setspace} is used
+% by the environment |clozespace|.
+% \begin{macrocode}
+\RequirePackage{setspace}
+% \end{macrocode}
+% The package \href{https://www.ctan.org/pkg/xcolor}{xcolor} is required
+% to colorize the text and the line of a gap.
+% \begin{macrocode}
+\RequirePackage{xcolor}
+% \end{macrocode}
+% The package \href{https://www.ctan.org/pkg/xcolor}{xparse} is used by
+% the environment |clozebox|.
+% \begin{macrocode}
+\RequirePackage{xparse}
+% \end{macrocode}
+% The package \href{https://www.ctan.org/pkg/stackengine}{stackengine}
+% is required by the command |\clozestrike{}{}|.
+% \begin{macrocode}
+\RequirePackage{stackengine}
+% \end{macrocode}
+% The package \href{https://www.ctan.org/pkg/ulem}{ulem} is required by
+% the command |\clozestrike{}{}|.
+% \begin{macrocode}
+\RequirePackage{ulem}
+\normalem
+% \end{macrocode}
+%
+% \begin{macrocode}
+\RequirePackage{transparent}
+% \end{macrocode}
+%
+% Load the cloze lua module and put all return values in the variable
+% |cloze|.
+%
+% \begin{macro}{\clozesetoption}
+% \cmd{\clozesetoption} is a wrapper for the Lua function
+% |registry.set_option|. \cmd{\clozesetoption}\marg{key}\marg{value}
+% sets a key \meta{key} to the value \meta{value}. The plain Lua\TeX{}
+% version always sets to the global options. The Lua\LaTeX-version
+% can set values both to the local and the global options store.
+% \begin{macrocode}
+\let\clozesetoption=\undefined
+\newcommand{\clozesetoption}[2]{%
+ \directlua{cloze.set_option('#1', '#2')}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeSetLocalOptions}
+% This macro is used in all cloze commands to handle the optional
+% arguments. First it sets the option storage to local and then it
+% commits the options to the package \emph{kvoptions} via the macro
+% |\kvsetkeys{CLZ}{}|.
+% \begin{macrocode}
+\def\ClozeSetLocalOptions#1{%
+ \ClozeSetToLocal%
+ \kvsetkeys{CLZ}{#1}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Options}
+%
+% \emph{cloze} offers key-value pairs to use as options. For processing
+% the key-value pairs we use the package
+% \href{https://www.ctan.org/pkg/kvoptions}{kvoptions}.
+% To make all key-value pairs accessibly to Lua code, we use the
+% declaration
+% \cmd{\define@key}\marg{CLZ}\marg{option}\oarg[]\marg{\dots}. This
+% declaration comes from the package
+% \href{https://www.ctan.org/pkg/keyval}{keyval}.
+%
+% At start all values are declared as global options. At the Lua side
+% all values are now stored in the |registry.global_options| table.
+%
+% \begin{macrocode}
+\ClozeSetToGlobal
+% \end{macrocode}
+%
+% We use the abbreviation |CLZ| for \emph{cloze} as family name and
+% prefix.
+%
+% \begin{macrocode}
+\SetupKeyvalOptions{
+ family=CLZ,
+ prefix=CLZ@
+}
+% \end{macrocode}
+%
+% \paragraph{\tt{align}}
+%
+% \noindent Please read the section \TmpSecRef{sec:option-align} how to use
+% the option |align|. |align| affects only the command \cmd{\clozefix}
+% \TmpSecRef{sec:command-clozefix}.
+%
+% \begin{macrocode}
+\DeclareStringOption{align}
+\define@key{CLZ}{align}[]{\clozesetoption{align}{#1}}
+% \end{macrocode}
+%
+% \paragraph{\tt{boxheight}}
+%
+% \noindent Please read the section \TmpSecRef{sec:command-clozebox}
+% how to use the option |boxheight|. |boxheight| affects only the
+% environment |clozebox|.
+% \TmpSecRef{sec:command-clozefix}.
+%
+% \begin{macrocode}
+\DeclareStringOption{boxheight}
+\define@key{CLZ}{boxheight}[]{\clozesetoption{boxheight}{#1}}
+% \end{macrocode}
+%
+% \paragraph{\tt{boxwidth}}
+%
+% \noindent Please read the section \TmpSecRef{sec:command-clozebox}
+% how to use the option |boxwidth|. |boxwidth| affects only the
+% environment |clozebox|.
+% \TmpSecRef{sec:command-clozefix}.
+%
+% \begin{macrocode}
+\DeclareStringOption{boxwidth}
+\define@key{CLZ}{boxwidth}[]{\clozesetoption{boxwidth}{#1}}
+% \end{macrocode}
+%
+% \paragraph{\tt{distance}}
+%
+% \noindent Please read the section \TmpSecRef{sec:option-distance} how to
+% use the option |distance|.
+%
+% \begin{macrocode}
+\DeclareStringOption{distance}
+\define@key{CLZ}{distance}[]{\clozesetoption{distance}{#1}}
+% \end{macrocode}
+%
+% \paragraph{\tt{hide}}
+%
+% \noindent If the option |hide| appears in the commands, |hide| will be
+% set to \emph{true} and |show| to \emph{false} on the Lua side. Please
+% read the section \TmpSecRef{sec:option-hide} how to use the option
+% |hide|.
+%
+% \begin{macrocode}
+\DeclareVoidOption{hide}{%
+ \clozeshowfalse%
+ \clozesetoption{hide}{true}%
+ \clozesetoption{show}{false}%
+}
+% \end{macrocode}
+%
+% \paragraph{\tt{linecolor}}
+%
+% \noindent Please read the section \TmpSecRef{sec:option-linecolor} how to
+% use the option |linecolor|.
+%
+% \begin{macrocode}
+\DeclareStringOption{linecolor}
+\define@key{CLZ}{linecolor}[]{%
+ \clozesetoption{linecolor}{\ClozeColor{#1}}%
+ \clozesetoption{linecolor_name}{#1}%
+}
+% \end{macrocode}
+%
+% \paragraph{\tt{margin}}
+%
+% \noindent Please read the section \TmpSecRef{sec:option-margin} how to
+% use the option |margin|.
+%
+% \begin{macrocode}
+\DeclareStringOption{margin}
+\define@key{CLZ}{margin}[]{\clozesetoption{margin}{#1}}
+% \end{macrocode}
+%
+% \paragraph{\tt{show}}
+%
+% \noindent If the option |show| appears in the commands, |show| will be
+% set to \emph{true} and |true| to \emph{false} on the Lua side. Please
+% read the section \TmpSecRef{sec:option-show} how to use the option
+% |show|.
+%
+% \begin{macrocode}
+\DeclareVoidOption{show}{%
+ \clozeshowtrue%
+ \clozesetoption{show}{true}%
+ \clozesetoption{hide}{false}%
+}
+% \end{macrocode}
+%
+% \paragraph{\tt{spacing}}
+%
+% \noindent Please read the section \TmpSecRef{sec:option-spacing} how to
+% use the option |spacing|.
+%
+% \begin{macrocode}
+\DeclareStringOption{spacing}
+\define@key{CLZ}{spacing}[]{\clozesetoption{spacing}{#1}}
+% \end{macrocode}
+%
+% \paragraph{\tt{textcolor}}
+%
+% \noindent Please read the section \TmpSecRef{sec:option-textcolor} how to
+% use the option |textcolor|.
+%
+% \begin{macrocode}
+\DeclareStringOption{textcolor}
+\define@key{CLZ}{textcolor}[]{%
+ \clozesetoption{textcolor}{\ClozeColor{#1}}%
+ \clozesetoption{textcolor_name}{#1}%
+}
+% \end{macrocode}
+%
+% \paragraph{\tt{thickness}}
+%
+% \noindent Please read the section \TmpSecRef{sec:option-thickness} how to
+% use the option |thickness|.
+%
+% \begin{macrocode}
+\DeclareStringOption{thickness}
+\define@key{CLZ}{thickness}[]{\clozesetoption{thickness}{#1}}
+% \end{macrocode}
+%
+% \paragraph{\tt{width}}
+%
+% \noindent Please read the section \TmpSecRef{sec:option-width} how to use
+% the option |width|. |width| affects only the command \cmd{\clozefix}
+% \TmpSecRef{sec:command-clozefix}.
+%
+% \begin{macrocode}
+\DeclareStringOption{width}
+\define@key{CLZ}{width}[]{\clozesetoption{width}{#1}}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\ProcessKeyvalOptions{CLZ}
+% \end{macrocode}
+%
+% \subsubsection{Public macros}
+%
+% All public macros are prefixed with |\cloze|.
+%
+% \begin{macro}{\clozeset}
+% The usage of the command \cmd{\clozeset} is described in detail in
+% section \TmpSecRef{sec:command-clozeset}.
+% \begin{macrocode}
+\newcommand{\clozeset}[1]{%
+ \ClozeSetToGlobal%
+ \kvsetkeys{CLZ}{#1}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozeshow}
+% The usage of the command \cmd{\clozeshow} is described in detail in
+% section \TmpSecRef{sec:command-clozeshow}.
+% \begin{macrocode}
+\let\clozeshow=\undefined
+\newcommand{\clozeshow}{%
+ \clozeset{show}
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozehide}
+% The usage of the command \cmd{\clozehide} is described in detail in
+% section \TmpSecRef{sec:command-clozehide}.
+% \begin{macrocode}
+\let\clozehide=\undefined
+\newcommand{\clozehide}{%
+ \clozeset{hide}
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\cloze}
+% The usage of the command \cmd{\cloze} is described in detail in
+% section \TmpSecRef{sec:command-cloze}.
+% \begin{macrocode}
+\let\clozeplain=\cloze
+\let\cloze=\undefined
+\newcommand{\cloze}[2][]{%
+ \ClozeSetLocalOptions{#1}%
+ \clozeplain{#2}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozefix}
+% The usage of the command \cmd{\clozefix} is described in detail in
+% section \TmpSecRef{sec:command-clozefix}.
+% \begin{macrocode}
+\let\clozefixplain=\clozefix
+\let\clozefix=\undefined
+\newcommand{\clozefix}[2][]{%
+ \ClozeSetLocalOptions{#1}%
+ \clozefixplain{#2}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozenol}
+% The usage of the command \cmd{\clozenol} is described in detail in
+% section \TmpSecRef{sec:command-clozenol}.
+% \begin{macrocode}
+\let\clozenolplain=\clozenol
+\let\clozenol=\undefined
+\newcommand{\clozenol}[2][]{%
+ \ClozeSetLocalOptions{#1}%
+ \clozenolplain{#2}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{environment}{clozepar}
+% The usage of the environment |clozepar| is described in detail in
+% section \TmpSecRef{sec:command-clozepar}.
+% \begin{macrocode}
+\newenvironment{clozepar}[1][]%
+{%
+ \par%
+ \ClozeSetLocalOptions{#1}%
+ \ClozeStartMarker{par}%
+ \clozefont\relax%
+}%
+{%
+ \ClozeStopMarker{par}%
+ \par%
+ \directlua{cloze.unregister('par')}%
+}
+% \end{macrocode}
+% \end{environment}
+%
+% \begin{environment}{clozebox}
+% The usage of the environment |clozebox| is described in detail in
+% section \TmpSecRef{sec:command-clozebox}.
+% TODO: Realize this macro with lua code, without ugly |\color{white}|
+% command.
+% \begin{macrocode}
+\newsavebox{\ClozeBox}%
+\NewDocumentEnvironment{clozebox}{ s O{} +b}{%
+ \ClozeSetLocalOptions{#2}%
+ \noindent%
+ \begin{lrbox}{\ClozeBox}%
+ \directlua{
+ local boxheight = cloze.get_value('boxheight')
+ local boxwidth = cloze.get_value('boxwidth')
+ if boxheight then
+ tex.print('\\begin{minipage}[t][' .. boxheight .. '][t]{' .. boxwidth .. '}')
+ else
+ tex.print('\\begin{minipage}[t]{' .. boxwidth .. '}')
+ end
+ }%
+ \setlength{\parindent}{0pt}%
+ \clozenol[margin=0pt]{#3}%
+ \end{minipage}%
+ \end{lrbox}%
+ \IfBooleanTF{#1}%
+ {\usebox{\ClozeBox}}%
+ {\fbox{\usebox{\ClozeBox}}}%
+}{}
+% \end{macrocode}
+% \end{environment}
+%
+% \begin{environment}{clozespace}
+% The usage of the environment |clozespace| is described in detail in
+% section \TmpSecRef{sec:command-clozespace}.
+% TODO: Realization without setspace package.
+% \begin{macrocode}
+\newenvironment{clozespace}[1][]%
+{%
+ \ClozeSetLocalOptions{#1}%
+ \begin{spacing}{\directlua{tex.print(cloze.get_value('spacing'))}}%
+}{\end{spacing}}
+% \end{macrocode}
+% \end{environment}
+%
+% \begin{macro}{\clozeline}
+% The usage of the command \cmd{\clozeline} is described in detail in
+% section \TmpSecRef{sec:command-clozeline}.
+% \begin{macrocode}
+\let\clozelineplain=\clozeline
+\let\clozeline=\undefined
+\newcommand{\clozeline}[1][]{%
+ \ClozeSetLocalOptions{#1}%
+ \clozelineplain%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozelinefil}
+% The usage of the command \cmd{\clozelinefil} is described in detail in
+% section \TmpSecRef{sec:command-clozelinefil}.
+% \begin{macrocode}
+\let\clozelinefilplain=\clozelinefil
+\let\clozelinefil=\undefined
+\newcommand{\clozelinefil}[1][]{%
+ \ClozeSetLocalOptions{#1}%
+ \clozelinefilplain%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozefil}
+% The usage of the command \cmd{\clozefil} is described in detail in
+% section \TmpSecRef{sec:command-clozefil}.
+% \begin{macrocode}
+\let\clozefil=\undefined
+\newcommand{\clozefil}[2][]{%
+ \cloze[#1]{#2}\clozelinefil[#1]%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozeextend}
+% TODO: Use node library to create kern nodes.
+% \begin{macrocode}
+\newcommand{\clozeextend}[1][1]{%
+ \directlua{
+ local loop = #1
+ for variable = 1, loop do
+ tex.print(' \string\\hspace{1em} \string\\strut')
+ end
+ }
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeTextColor}
+% \begin{macrocode}
+\newcommand{\ClozeTextColor}[1]{%
+ \textcolor%
+ {\directlua{tex.print(cloze.get_value('textcolor_name'))}}%
+ {#1}%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClozeStrikeLine}
+% \begin{macrocode}
+\newcommand\ClozeStrikeLine{%
+ \bgroup%
+ \markoverwith{%
+ \ClozeTextColor{%
+ \rule[0.5ex]{2pt}{1pt}%
+ }%
+ }%
+ \ULon%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\clozestrike}
+% \begin{macrocode}
+\newcommand{\clozestrike}[3][]{%
+ \ClozeSetLocalOptions{#1}%
+ \ifclozeshow%
+ \stackengine%
+ {\Sstackgap}% \Sstackgap or \Lstackgap or \stackgap or stacklength
+ {\ClozeStrikeLine{#2}}% anchor
+ {\ClozeTextColor{\clozefont{}#3}}% item
+ {O}% O or U
+ {c}% \stackalignment or l or c or r
+ {\quietstack}% \quietstack or T or F
+ {T}% \useanchorwidth or T or F
+ {\stacktype}% \stacktype or S or L
+ \else%
+ \stackengine%
+ {\Sstackgap}% \Sstackgap or \Lstackgap or \stackgap or stacklength
+ {#2}% anchor
+ {\texttransparent{0}{\clozefont{}#3}}% item
+ {O}% O or U
+ {c}% \stackalignment or l or c or r
+ {\quietstack}% \quietstack or T or F
+ {T}% \useanchorwidth or T or F
+ {\stacktype}% \stacktype or S or L
+ \fi%
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \iffalse
+%</latex>
+% \fi
+%
+% \Finale
+\endinput
diff --git a/macros/luatex/generic/cloze/cloze.ins b/macros/luatex/generic/cloze/cloze.ins
new file mode 100644
index 0000000000..9ed14f6199
--- /dev/null
+++ b/macros/luatex/generic/cloze/cloze.ins
@@ -0,0 +1,57 @@
+% Copyright (C) 2015-2020 by Josef Friedrich <josef@friedrich.rocks>
+% ----------------------------------------------------------------------
+% This work may be distributed and/or modified under the conditions of
+% the LaTeX Project Public License, either version 1.3c 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.3c or later is part of all distributions of LaTeX
+% version 2008/05/05 or later.
+
+
+\input docstrip.tex
+\keepsilent
+
+\usedir{tex/lualatex/cloze}
+
+\askforoverwritefalse
+
+\preamble
+
+This is a generated file.
+
+Copyright (C) 2015-2020 by Josef Friedrich <josef@friedrich.rocks>
+----------------------------------------------------------------------
+This work may be distributed and/or modified under the conditions of
+the LaTeX Project Public License, either version 1.3c 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.3c or later is part of all distributions of LaTeX
+version 2008/05/05 or later.
+
+\endpreamble
+
+\generate{\file{cloze.tex}{\from{cloze.dtx}{plaintex}}}
+\generate{\file{cloze.sty}{\from{cloze.dtx}{latex}}}
+
+\obeyspaces
+\Msg{*************************************************************}
+\Msg{* *}
+\Msg{* To finish the installation you have to move the following *}
+\Msg{* file into a directory searched by TeX: *}
+\Msg{* *}
+\Msg{* cloze.sty *}
+\Msg{* *}
+\Msg{* To produce the documentation run the file cloze.dtx *}
+\Msg{* through LaTeX. *}
+\Msg{* *}
+\Msg{* Happy TeXing! *}
+\Msg{* *}
+\Msg{*************************************************************}
+
+\endbatchfile
diff --git a/macros/luatex/generic/cloze/cloze.lua b/macros/luatex/generic/cloze/cloze.lua
new file mode 100644
index 0000000000..1563155a1b
--- /dev/null
+++ b/macros/luatex/generic/cloze/cloze.lua
@@ -0,0 +1,1045 @@
+--- Cloze uses [LDoc](https://github.com/stevedonovan/ldoc) for the
+-- source code documentation. The supported tags are described on in
+-- the [wiki](https://github.com/stevedonovan/LDoc/wiki).
+--
+-- <h3>Naming conventions</h3>
+--
+-- * _Variable_ names for _nodes_ are suffixed with `_node`, for example
+-- `head_node`.
+-- * _Variable_ names for _lengths_ (dimensions) are suffixed with
+-- `_length`, for example `width`.
+--
+-- @module cloze
+
+-- luacheck: globals node tex modules luatexbase callback
+
+-- __cloze.lua__
+
+-- __Initialisation of the function tables__
+
+-- It is good form to provide some background informations about this Lua
+-- module.
+if not modules then modules = { } end modules ['cloze'] = {
+ version = '1.6',
+ comment = 'cloze',
+ author = 'Josef Friedrich, R.-M. Huber',
+ copyright = 'Josef Friedrich, R.-M. Huber',
+ license = 'The LaTeX Project Public License Version 1.3c 2008-05-04'
+}
+
+--- `nodex` is a abbreviation for __node eXtended__.
+local nodex = {}
+
+--- All values and functions, which are related to the option management,
+-- are stored in a table called `registry`.
+local registry = {}
+
+--- I didn't know what value I should take as `user_id`. Therefore I
+-- took my birthday and transformed it to a large number.
+registry.user_id = 3121978
+registry.storage = {}
+registry.defaults = {
+ ['align'] = 'l',
+ ['boxheight'] = false,
+ ['boxwidth'] = '\\linewidth',
+ ['distance'] = '1.5pt',
+ ['hide'] = false,
+ ['linecolor'] = '0 0 0 rg 0 0 0 RG', -- black
+ ['linecolor_name'] = 'black',
+ ['margin'] = '3pt',
+ ['resetcolor'] = '0 0 0 rg 0 0 0 RG', -- black
+ ['resetcolor_name'] = 'black',
+ ['show_text'] = true,
+ ['show'] = true,
+ ['spacing'] = '1.6',
+ ['textcolor'] = '0 0 1 rg 0 0 1 RG', -- blue
+ ['textcolor_name'] = 'blue', -- blue
+ ['thickness'] = '0.4pt',
+ ['width'] = '2cm',
+}
+registry.global_options = {}
+registry.local_options = {}
+
+-- The `base` table contains some basic functions. `base` is the only
+-- table of this Lua module that will be exported.
+local base = {}
+base.is_registered = {}
+
+--- Node precessing (nodex)
+-- @section nodex
+
+-- All functions in this section are stored in a table called `nodex`.
+-- `nodex` is a abbreviation for __node eXtended__. The `nodex` table
+-- bundles all functions, which extend the built-in `node` library.
+
+-- __Color handling (color)__
+
+-- __create_colorstack__
+-- Create a whatsit node of the subtype `pdf_colorstack`. `data` is a PDF
+-- colorstack string like `0 0 0 rg 0 0 0 RG`.
+function nodex.create_colorstack(data)
+ if not data then
+ data = '0 0 0 rg 0 0 0 RG' -- black
+ end
+ local whatsit = node.new('whatsit', 'pdf_colorstack')
+ whatsit.stack = 0
+ whatsit.data = data
+ return whatsit
+end
+
+---
+-- `nodex.create_color()` is a wrapper for the function
+-- `nodex.create_colorstack()`. It queries the current values of the
+-- options `linecolor` and `textcolor`. The argument `option` accepts the
+-- strings `line`, `text` and `reset`.
+function nodex.create_color(option)
+ local data
+ if option == 'line' then
+ data = registry.get_value('linecolor')
+ elseif option == 'text' then
+ data = registry.get_value('textcolor')
+ elseif option == 'reset' then
+ data = nil
+ else
+ data = nil
+ end
+ return nodex.create_colorstack(data)
+end
+
+-- __Line handling (line)__
+
+--- Create a rule node, which is used as a line for the cloze texts. The
+-- `depth` and the `height` of the rule are calculated form the options
+-- `thickness` and `distance`. The argument `width` must have the length
+-- unit __scaled points__.
+function nodex.create_line(width)
+ local rule = node.new(node.id('rule'))
+ local thickness = tex.sp(registry.get_value('thickness'))
+ local distance = tex.sp(registry.get_value('distance'))
+ rule.depth = distance + thickness
+ rule.height = - distance
+ rule.width = width
+ return rule
+end
+
+--- Insert a `list` of nodes after or before the `current`. The `head`
+-- argument is optional. In some edge cases it is unfortately necessary.
+-- if `head` is omitted the `current` node is used. The argument
+-- `position` can take the values `'after'` or `'before'`.
+function nodex.insert_list(position, current, list, head_node)
+ if not head_node then
+ head_node = current
+ end
+ for _, insert in ipairs(list) do
+ if position == 'after' then
+ head_node, current = node.insert_after(head_node, current, insert)
+ elseif position == 'before' then
+ head_node, current = node.insert_before(head_node, current, insert)
+ end
+ end
+ return current
+end
+
+--- Enclose a rule node (cloze line) with two PDF colorstack whatsits.
+-- The first colorstack node dyes the line, the seccond resets the
+-- color.
+--
+-- __Node list__
+--
+-- <table>
+-- <thead>
+-- <tr>
+-- <th>`color_line_node`</th>
+-- <th>`whatsit`</th>
+-- <th>`pdf_colorstack`</th>
+-- <th>Line color</th>
+-- </tr>
+-- </thead>
+-- <tbody>
+-- <tr>
+-- <td>`line_node`</td>
+-- <td>`rule`</td>
+-- <td></td>
+-- <td>`width`</td>
+-- </tr>
+-- <tr>
+-- <td>`color_reset_node`</td>
+-- <td>`whatsit`</td>
+-- <td>`pdf_colorstack`</td>
+-- <td>Reset color</td>
+-- </tr>
+-- </tbody>
+-- </table>
+function nodex.insert_line(current, width)
+ return nodex.insert_list(
+ 'after',
+ current,
+ {
+ nodex.create_color('line'),
+ nodex.create_line(width),
+ nodex.create_color('reset')
+ }
+ )
+end
+
+--- This function enclozes a rule node with color nodes as it the function
+-- `nodex.insert_line` does. In contrast to `nodex.insert_line` the three
+-- nodes are appended to \TeX’s ‘current list’. They are not inserted in
+-- a node list, which is accessed by a Lua callback.
+--
+-- __Node list__
+--
+-- <table>
+-- <thead>
+-- <tr>
+-- <th>-</th>
+-- <th>`whatsit`</th>
+-- <th>`pdf_colorstack`</th>
+-- <th>Line color</th>
+-- </tr>
+-- </thead>
+-- <tbody>
+-- <tr>
+-- <td>-</td>
+-- <td>`rule`</td>
+-- <td></td>
+-- <td>`width`</td>
+-- </tr>
+-- <tr>
+-- <td>-</td>
+-- <td>`whatsit`</td>
+-- <td>`pdf_colorstack`</td>
+-- <td>Reset color</td>
+-- </tr>
+-- </tbody>
+-- </table>
+function nodex.write_line()
+ node.write(nodex.create_color('line'))
+ node.write(nodex.create_line(tex.sp(registry.get_value('width'))))
+ node.write(nodex.create_color('reset'))
+end
+
+-- __Handling of extendable lines (linefil)__
+
+--- This function creates a line which stretchs indefinitely in the
+-- horizontal direction.
+function nodex.create_linefil()
+ local glue = node.new('glue')
+ glue.subtype = 100
+ glue.stretch = 65536
+ glue.stretch_order = 3
+ local rule = nodex.create_line(0)
+ rule.dir = 'TLT'
+ glue.leader = rule
+ return glue
+end
+
+--- The function `nodex.write_linefil` surrounds a indefinitely strechable
+-- line with color whatsits and puts it to \TeX’s ‘current (node) list’.
+function nodex.write_linefil()
+ node.write(nodex.create_color('line'))
+ node.write(nodex.create_linefil())
+ node.write(nodex.create_color('reset'))
+end
+
+-- __Kern handling (kern)__
+
+--- This function creates a kern node with a given width. The argument
+-- `width` had to be specified in scaled points.
+local function create_kern_node(width)
+ local kern_node = node.new(node.id('kern'))
+ kern_node.kern = width
+ return kern_node
+end
+
+--- Add at the beginning of each `hlist` node list a strut (a invisible
+-- character).
+--
+-- Now we can add line, color etc. nodes after the first node of a hlist
+-- not before - after is much more easier.
+--
+-- @tparam node hlist_node
+--
+-- @treturn node hlist_node
+-- @treturn node strut_node
+-- @treturn node prev_head_node
+local function insert_strut_into_hlist(hlist_node)
+ local prev_head_node = hlist_node.head
+ local kern_node = create_kern_node(0)
+ local strut_node = node.insert_before(hlist_node.head, prev_head_node, kern_node)
+ hlist_node.head = prev_head_node.prev
+ return hlist_node, strut_node, prev_head_node
+end
+
+--- Write kern nodes to the current node list. This kern nodes can be used
+-- to build a margin.
+function nodex.write_margin()
+ local kern = create_kern_node(tex.sp(registry.get_value('margin')))
+ node.write(kern)
+end
+
+--- Search for a `hlist` (subtype `line`) and nsert a strut node into
+-- the list if a hlist is found.
+--
+-- @tparam node head_node The head of a node list.
+--
+-- @treturn node hlist_node
+-- @treturn node strut_node
+-- @treturn node prev_head_node
+local function search_hlist(head_node)
+ while head_node do
+ if head_node.id == node.id('hlist') and head_node.subtype == 1 then
+ return insert_strut_into_hlist(head_node)
+ end
+ head_node = head_node.next
+ end
+end
+
+--- Option handling.
+--
+-- The table `registry` bundels functions that deal with option handling.
+--
+-- <h2>Marker processing (marker)</h2>
+--
+-- A marker is a whatsit node of the subtype `user_defined`. A marker has
+-- two purposes:
+--
+-- * Mark the begin and the end of a gap.
+-- * Store a index number, that points to a Lua table, which holds
+-- some additional data like the local options.
+-- @section registry
+
+--- We create a user defined whatsit node that can store a number (type
+-- = 100).
+--
+-- In order to distinguish this node from other user defined whatsit
+-- nodes we set the `user_id` to a large number. We call this whatsit
+-- node a marker. The argument `index` is a number, which is associated
+-- to values in the `registry.storage` table.
+function registry.create_marker(index)
+ local marker = node.new('whatsit','user_defined')
+ marker.type = 100 -- number
+ marker.user_id = registry.user_id
+ marker.value = index
+ return marker
+end
+
+--- Write a marker node to TeX's current node list.
+--
+-- The argument `mode` accepts the string values `basic`, `fix` and
+-- `par`. The argument `position`. The argument `position` is either set
+-- to `start` or to `stop`.
+function registry.write_marker(mode, position)
+ local index = registry.set_storage(mode, position)
+ local marker = registry.create_marker(index)
+ node.write(marker)
+end
+
+--- This functions checks if the given node `item` is a marker.
+function registry.is_marker(item)
+ if item.id == node.id('whatsit')
+ and item.subtype == node.subtype('user_defined')
+ and item.user_id == registry.user_id then
+ return true
+ else
+ return false
+ end
+end
+
+--- This functions tests, whether the given node `item` is a marker.
+--
+-- The argument `item` is a node. The argument `mode` accepts the string
+-- values `basic`, `fix` and `par`. The argument `position` is either
+-- set to `start` or to `stop`.
+function registry.check_marker(item, mode, position)
+ local data = registry.get_marker_data(item)
+ if data and data.mode == mode and data.position == position then
+ return true
+ else
+ return false
+ end
+end
+
+--- `registry.get_marker` returns the given marker.
+--
+-- The argument `item` is a node. The argument `mode` accepts the string
+-- values `basic`, `fix` and `par`. The argument `position` is either
+-- set to `start` or to `stop`.
+function registry.get_marker(item, mode, position)
+ local out
+ if registry.check_marker(item, mode, position) then
+ out = item
+ else
+ out = false
+ end
+ if out and position == 'start' then
+ registry.get_marker_values(item)
+ end
+ return out
+end
+
+--- `registry.get_marker_data` tests whether the node `item` is a
+-- marker.
+--
+-- The argument `item` is a node of unspecified type.
+function registry.get_marker_data(item)
+ if item.id == node.id('whatsit')
+ and item.subtype == node.subtype('user_defined')
+ and item.user_id == registry.user_id then
+ return registry.get_storage(item.value)
+ else
+ return false
+ end
+end
+
+--- First this function saves the associatied values of a marker to the
+-- local options table. Second it returns this values. The argument
+-- `marker` is a whatsit node.
+function registry.get_marker_values(marker)
+ local data = registry.get_marker_data(marker)
+ registry.local_options = data.values
+ return data.values
+end
+
+--- This function removes a given whatsit marker.
+--
+-- It only deletes a node, if a marker is given.
+--
+-- @treturn node head
+-- @treturn node current
+function registry.remove_marker(marker)
+ if registry.is_marker(marker) then
+ return node.remove(marker, marker)
+ end
+end
+
+-- __Storage functions (storage)__
+
+--- `registry.index` is a counter. The functions `registry.get_index()`
+-- increases the counter by one and then returns it.
+function registry.get_index()
+ if not registry.index then
+ registry.index = 0
+ end
+ registry.index = registry.index + 1
+ return registry.index
+end
+
+--- `registry.set_storage()` stores the local options in the Lua table
+-- `registry.storage`.
+--
+-- It returns a numeric index number. This index number is the key,
+-- where the local options in the Lua table are stored. The argument
+-- `mode` accepts the string values `basic`, `fix` and `par`.
+function registry.set_storage(mode, position)
+ local index = registry.get_index()
+ local data = {
+ ['mode'] = mode,
+ ['position'] = position
+ }
+ data.values = registry.local_options
+ registry.storage[index] = data
+ return index
+end
+
+--- The function `registry.get_storage()` retrieves values which belong
+-- to a whatsit marker.
+--
+-- The argument `index` is a numeric value.
+function registry.get_storage(index)
+ return registry.storage[index]
+end
+
+-- __Option processing (option)__
+
+--- This function stores a value `value` and his associated key `key`
+-- either to the global (`registry.global_options`) or to the local
+-- (`registry.local_options`) option table.
+--
+-- The global boolean variable `registry.local_options` controls in
+-- which table the values are stored.
+function registry.set_option(key, value)
+ if value == '' or value == '\\color@ ' then
+ return false
+ end
+ if registry.is_global == true then
+ registry.global_options[key] = value
+ else
+ registry.local_options[key] = value
+ end
+end
+
+--- `registry.set_is_global()` sets the variable `registry.is_global` to
+-- the value `value`. It is intended, that the variable takes boolean
+-- values.
+function registry.set_is_global(value)
+ registry.is_global = value
+end
+
+--- This function unsets the local options.
+function registry.unset_local_options()
+ registry.local_options = {}
+end
+
+--- `registry.unset_global_options` empties the global options storage.
+function registry.unset_global_options()
+ registry.global_options = {}
+end
+
+--- Retrieve a value from a given key. First search for the value in the
+-- local options, then in the global options. If both option storages are
+-- empty, the default value will be returned.
+function registry.get_value(key)
+ if registry.has_value(registry.local_options[key]) then
+ return registry.local_options[key]
+ end
+ if registry.has_value(registry.global_options[key]) then
+ return registry.global_options[key]
+ end
+ return registry.defaults[key]
+end
+
+
+--- The function `registry.get_value_show()` returns the boolean value
+-- `true` if the option `show` is true. In contrast to the function
+-- `registry.get_value()` it converts the string value `true' to a
+-- boolean value.
+function registry.get_value_show()
+ if
+ registry.get_value('show') == true
+ or
+ registry.get_value('show') == 'true'
+ then
+ return true
+ else
+ return false
+ end
+end
+
+--- This function tests whether the value `value` is not empty and has a
+-- value.
+function registry.has_value(value)
+ if value == nil or value == '' or value == '\\color@ ' then
+ return false
+ else
+ return true
+ end
+end
+
+--- `registry.get_defaults(option)` returns a the default value of the
+-- given option.
+function registry.get_defaults(option)
+ return registry.defaults[option]
+end
+
+--- Assembly to cloze texts.
+-- @section cloze_functions
+
+--- Assemble a possibly muliline cloze.
+--
+-- The corresponding LaTeX command to this Lua function is `\cloze`.
+-- This function is used by other cloze TeX macros too: `\clozenol`,
+-- `\clozefil`
+--
+-- @tparam node head_node_input The head of a node list.
+--
+-- @treturn node The head of the node list.
+local function make_basic(head_node_input)
+ -- This local variables are overloaded by function who
+ -- call each other.
+ local continue_cloze, search_stop
+
+ --- The function `make_single()` makes one gap. The argument
+ -- `start_node` is the node where the gap begins. The argument
+ -- `stop_node` is the node where the gap ends.
+ --
+ -- @tparam node start_node The node to start / begin a new cloze.
+ -- @tparam node stop_node The node to stop / end a new cloze.
+ -- @tparam node parent_node The parent node (hlist) of the start and
+ -- the stop node.
+ --
+ -- @treturn node stop_node The stop node.
+ -- @treturn parent_node The parent node (hlist) of the stop node.
+ local function make_single(start_node, stop_node, parent_node)
+ local node_head = start_node
+ local line_width = node.dimensions(
+ parent_node.glue_set,
+ parent_node.glue_sign,
+ parent_node.glue_order,
+ start_node,
+ stop_node
+ )
+ local line_node = nodex.insert_line(start_node, line_width)
+ local color_text_node = nodex.insert_list('after', line_node, {nodex.create_color('text')})
+ if registry.get_value_show() then
+ nodex.insert_list('after', color_text_node, {create_kern_node(-line_width)})
+ nodex.insert_list('before', stop_node, {nodex.create_color('reset')}, node_head)
+ else
+ line_node.next = stop_node.next
+ stop_node.prev = line_node -- not line_node.prev -> line color leaks out
+ end
+ -- In some edge cases the lua callbacks get fired up twice. After the
+ -- cloze has been created, the start and stop whatsit markers can be
+ -- deleted.
+ registry.remove_marker(start_node)
+ return registry.remove_marker(stop_node), parent_node
+ end
+
+ --- Search for a stop marker or make a cloze up to the end of the node
+ -- list.
+ --
+ -- @tparam node start_node The node to start a new cloze.
+ -- @tparam node parent_node The parent node (hlist) of the start node.
+ --
+ -- @treturn head_node The fast forwarded new head of the node list.
+ -- @treturn parent_node The parent node (hlist) of the head node.
+ function search_stop(start_node, parent_node)
+ local head_node = start_node
+ local last_node
+ while head_node do
+ if registry.check_marker(head_node, 'basic', 'stop') then
+ return make_single(start_node, head_node, parent_node)
+ end
+ last_node = head_node
+ head_node = head_node.next
+ end
+ -- Make a cloze until the end of the node list.
+ head_node = make_single(start_node, last_node, parent_node)
+ if parent_node.next then
+ return continue_cloze(parent_node.next)
+ else
+ return head_node, parent_node
+ end
+ end
+
+ --- Continue a multiline cloze.
+ --
+ -- @tparam node parent_node A parent node to search for a hlist node.
+ --
+ -- @treturn head_node The fast forwarded new head of the node list.
+ -- @treturn parent_node The parent node (hlist) of the head node.
+ function continue_cloze(parent_node)
+ local hlist_node = search_hlist(parent_node)
+ if hlist_node then
+ local start_node = hlist_node.head
+ return search_stop(start_node, hlist_node)
+ end
+ end
+
+ --- Search for a start marker.
+ --
+ -- @tparam node head_node The head of a node list.
+ -- @tparam node parent_node The parent node (hlist) of the head node.
+ local function search_start(head_node, parent_node)
+ while head_node do
+ if head_node.head then
+ search_start(head_node.head, head_node)
+ elseif registry.check_marker(head_node, 'basic', 'start') and
+ parent_node and
+ parent_node.id == node.id('hlist') then
+ -- Adds also a strut at the first position. It prepars the
+ -- hlist and makes it ready to build a cloze.
+ search_hlist(parent_node)
+ head_node, parent_node = search_stop(head_node, parent_node)
+ end
+ if head_node then
+ head_node = head_node.next
+ else
+ break
+ end
+ end
+ end
+
+ search_start(head_node_input)
+ return head_node_input
+end
+
+--- The corresponding LaTeX command to this Lua function is `\clozefix`.
+--
+-- @tparam node head_node_input The head of a node list.
+local function make_fix(head_node_input)
+
+ --- Calculate the length of the whitespace before (`kern_start_length`) and
+ -- after (`kern_stop_length`) the text.
+ local function calculate_length(start, stop)
+ local width, kern_start_length, kern_stop_length, text_width, half_length, align
+ width = tex.sp(registry.get_value('width'))
+ text_width = node.dimensions(start, stop)
+ align = registry.get_value('align')
+ if align == 'right' then
+ kern_start_length = - text_width
+ kern_stop_length = 0
+ elseif align == 'center' then
+ half_length = (width - text_width) / 2
+ kern_start_length = - half_length - text_width
+ kern_stop_length = half_length
+ else
+ kern_start_length = - width
+ kern_stop_length = width - text_width
+ end
+ return width, kern_start_length, kern_stop_length
+ end
+
+ --- The function `make_single` generates a gap of fixed width.
+ --
+ -- __Node lists__
+ --
+ -- __Show text:__
+ --
+ -- <table>
+ -- <tbody>
+ -- <tr>
+ -- <td>`start_node`</td>
+ -- <td>`whatsit`</td>
+ -- <td>`user_definded`</td>
+ -- <td>`index`</td>
+ -- </tr>
+ -- <tr>
+ -- <td>`line_node`</td>
+ -- <td>`rule`</td>
+ -- <td></td>
+ -- <td>`width`</td>
+ -- </tr>
+ -- <tr>
+ -- <td>`kern_start_node`</td>
+ -- <td>`kern`</td>
+ -- <td>&amp; Depends on `align`</td>
+ -- <td></td>
+ -- </tr>
+ -- <tr>
+ -- <td>`color_text_node`</td>
+ -- <td>`whatsit`</td>
+ -- <td>`pdf_colorstack`</td>
+ -- <td>Text color</td>
+ -- </tr>
+ -- <tr>
+ -- <td></td>
+ -- <td>`glyphs`</td>
+ -- <td>&amp; Text to show</td>
+ -- <td></td>
+ -- </tr>
+ -- <tr>
+ -- <td>`color_reset_node`</td>
+ -- <td>`whatsit`</td>
+ -- <td>`pdf_colorstack`</td>
+ -- <td>Reset color</td>
+ -- </tr>
+ -- <tr>
+ -- <td>`kern_stop_node`</td>
+ -- <td>`kern`</td>
+ -- <td>&amp; Depends on `align`</td>
+ -- <td></td>
+ -- </tr>
+ -- <tr>
+ -- <td>`stop_node`</td>
+ -- <td>`whatsit`</td>
+ -- <td>`user_definded`</td>
+ -- <td>`index`</td>
+ -- </tr>
+ -- </tbody>
+ -- </table>
+ --
+ -- __Hide text:__
+ --
+ -- <table>
+ -- <thead>
+ -- <tr>
+ -- <th>`start_node`</th>
+ -- <th>`whatsit`</th>
+ -- <th>`user_definded`</th>
+ -- <th>`index`</th>
+ -- </tr>
+ -- </thead>
+ -- <tbody>
+ -- <tr>
+ -- <td>`line_node`</td>
+ -- <td>`rule`</td>
+ -- <td></td>
+ -- <td>`width`</td>
+ -- </tr>
+ -- <tr>
+ -- <td>`stop_node`</td>
+ -- <td>`whatsit`</td>
+ -- <td>`user_definded`</td>
+ -- <td>`index`</td>
+ -- </tr>
+ -- </tbody>
+ -- </table>
+ --
+ -- Make fixed size cloze.
+ --
+ -- @param start The node, where the gap begins
+ -- @param stop The node, where the gap ends
+ local function make_single(start, stop)
+ local width, kern_start_length, kern_stop_length, line_node
+ width, kern_start_length, kern_stop_length = calculate_length(start, stop)
+ line_node = nodex.insert_line(start, width)
+ if registry.get_value_show() then
+ nodex.insert_list(
+ 'after',
+ line_node,
+ {
+ create_kern_node(kern_start_length),
+ nodex.create_color('text')
+ }
+ )
+ nodex.insert_list(
+ 'before',
+ stop,
+ {
+ nodex.create_color('reset'),
+ create_kern_node(kern_stop_length)
+ },
+ start
+ )
+ else
+ line_node.next = stop.next
+ end
+ registry.remove_marker(start)
+ registry.remove_marker(stop)
+ end
+
+ --- Function to recurse the node list and search after marker.
+ --
+ -- @tparam node head_node The head of a node list.
+ local function make_fix_recursion(head_node)
+ local start_node, stop_node = false, false
+ while head_node do
+ if head_node.head then
+ make_fix_recursion(head_node.head)
+ else
+ if not start_node then
+ start_node = registry.get_marker(head_node, 'fix', 'start')
+ end
+ if not stop_node then
+ stop_node = registry.get_marker(head_node, 'fix', 'stop')
+ end
+ if start_node and stop_node then
+ make_single(start_node, stop_node)
+ start_node, stop_node = false, false
+ end
+ end
+ head_node = head_node.next
+ end
+ end
+
+ make_fix_recursion(head_node_input)
+ return head_node_input
+end
+
+--- The corresponding LaTeX environment to this lua function is
+-- `clozepar`.
+--
+-- __Node lists__
+--
+-- __Show text:__
+--
+-- <table>
+-- <thead>
+-- <tr>
+-- <th>`strut_node`</th>
+-- <th>`kern`</th>
+-- <th></th>
+-- <th>width = 0</th>
+-- </tr>
+-- </thead>
+-- <tbody>
+-- <tr>
+-- <td>`line_node`</td>
+-- <td>`rule`</td>
+-- <td></td>
+-- <td>`width` (Width from hlist)</td>
+-- </tr>
+-- <tr>
+-- <td>`kern_node`</td>
+-- <td>`kern`</td>
+-- <td></td>
+-- <td>`-width`</td>
+-- </tr>
+-- <tr>
+-- <td>`color_text_node`</td>
+-- <td>`whatsit`</td>
+-- <td>`pdf_colorstack`</td>
+-- <td>Text color</td>
+-- </tr>
+-- <tr>
+-- <td></td>
+-- <td>`glyphs`</td>
+-- <td></td>
+-- <td>Text to show</td>
+-- </tr>
+-- <tr>
+-- <td>`tail_node`</td>
+-- <td>`glyph`</td>
+-- <td></td>
+-- <td>Last glyph in hlist</td>
+-- </tr>
+-- <tr>
+-- <td>`color_reset_node`</td>
+-- <td>`whatsit`</td>
+-- <td>`pdf_colorstack`</td>
+-- <td>Reset color</td>
+-- </tr>
+-- </tbody>
+-- </table>
+--
+-- __Hide text:__
+--
+-- <table>
+-- <thead>
+-- <tr>
+-- <th>`strut_node`</th>
+-- <th>`kern`</th>
+-- <th></th>
+-- <th>width = 0</th>
+-- </tr>
+-- </thead>
+-- <tbody>
+-- <tr>
+-- <td>`line_node`</td>
+-- <td>`rule`</td>
+-- <td></td>
+-- <td>`width` (Width from hlist)</td>
+-- </tr>
+-- </tbody>
+-- </table>
+--
+-- @tparam node head_node The head of a node list.
+local function make_par(head_node)
+ local strut_node, line_node, width
+ for hlist_node in node.traverse_id(node.id('hlist'), head_node) do
+ for whatsit in node.traverse_id(node.id('whatsit'), hlist_node.head) do
+ registry.get_marker(whatsit, 'par', 'start')
+ end
+ width = hlist_node.width
+ hlist_node, strut_node, _ = insert_strut_into_hlist(hlist_node)
+ line_node = nodex.insert_line(strut_node, width)
+ if registry.get_value_show() then
+ nodex.insert_list(
+ 'after',
+ line_node,
+ {
+ create_kern_node(-width),
+ nodex.create_color('text')
+ }
+ )
+ nodex.insert_list(
+ 'after',
+ node.tail(head_node),
+ {nodex.create_color('reset')}
+ )
+ else
+ line_node.next = nil
+ end
+ end
+ return head_node
+end
+
+---
+-- @tparam string callback_name The name of a callback
+-- @tparam function func A function to register for the callback
+-- @tparam string description Only used in LuaLatex
+local function register_callback(callback_name, func, description)
+ if luatexbase then
+ luatexbase.add_to_callback(
+ callback_name,
+ func,
+ description
+ )
+ else
+ callback.register(callback_name, func)
+ end
+end
+
+---
+-- @tparam string callback_name The name of a callback
+-- @tparam string description Only used in LuaLatex
+local function unregister_callback(callback_name, description)
+ if luatexbase then
+ luatexbase.remove_from_callback(
+ callback_name,
+ description
+ )
+ else
+ callback.register(callback_name, nil)
+ end
+end
+
+--- Basic module functions.
+-- The `base` table contains functions which are published to the
+-- `cloze.sty` file.
+-- @section base
+
+--- This function registers the functions `make_par`, `make_basic` and
+-- `make_fix` the Lua callbacks.
+--
+-- `make_par` and `make_basic` are registered to the callback
+-- `post_linebreak_filter` and `make_fix` to the callback
+-- `pre_linebreak_filter`. The argument `mode` accepts the string values
+-- `basic`, `fix` and `par`. A special treatment is needed for clozes in
+-- display math mode. The `post_linebreak_filter` is not called on
+-- display math formulas. I’m not sure if the `pre_output_filter` is the
+-- right choice to capture the display math formulas.
+function base.register(mode)
+ if mode == 'par' then
+ register_callback(
+ 'post_linebreak_filter',
+ make_par,
+ mode
+ )
+ return true
+ end
+ if not base.is_registered[mode] then
+ if mode == 'basic' then
+ register_callback(
+ 'post_linebreak_filter',
+ make_basic,
+ mode
+ )
+ register_callback(
+ 'pre_output_filter',
+ make_basic,
+ mode
+ )
+ elseif mode == 'fix' then
+ register_callback(
+ 'pre_linebreak_filter',
+ make_fix,
+ mode
+ )
+ else
+ return false
+ end
+ base.is_registered[mode] = true
+ end
+end
+
+--- `base.unregister(mode)` deletes the registered functions from the
+-- Lua callbacks.
+--
+-- @tparam string mode The argument `mode` accepts the string values
+-- `basic`, `fix` and `par`.
+function base.unregister(mode)
+ if mode == 'basic' then
+ unregister_callback('post_linebreak_filter', mode)
+ unregister_callback('pre_output_filter', mode)
+ elseif mode == 'fix' then
+ unregister_callback('pre_linebreak_filter', mode)
+ else
+ unregister_callback('post_linebreak_filter', mode)
+ end
+end
+
+-- Publish some functions to the `cloze.sty` file.
+base.linefil = nodex.write_linefil
+base.line = nodex.write_line
+base.margin = nodex.write_margin
+base.set_option = registry.set_option
+base.set_is_global = registry.set_is_global
+base.unset_local_options = registry.unset_local_options
+base.reset = registry.unset_global_options
+base.get_defaults = registry.get_defaults
+base.get_value = registry.get_value
+base.marker = registry.write_marker
+
+return base
diff --git a/macros/luatex/generic/cloze/cloze.pdf b/macros/luatex/generic/cloze/cloze.pdf
new file mode 100644
index 0000000000..5901e8dd85
--- /dev/null
+++ b/macros/luatex/generic/cloze/cloze.pdf
Binary files differ