diff options
Diffstat (limited to 'macros/luatex/latex/lua-ul/lua-ul.dtx')
-rw-r--r-- | macros/luatex/latex/lua-ul/lua-ul.dtx | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/macros/luatex/latex/lua-ul/lua-ul.dtx b/macros/luatex/latex/lua-ul/lua-ul.dtx new file mode 100644 index 0000000000..be033be0b5 --- /dev/null +++ b/macros/luatex/latex/lua-ul/lua-ul.dtx @@ -0,0 +1,480 @@ +% \iffalse meta-comment +% +%% Copyright (C) 2020 by Marcel Krueger +%% +%% This file 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.3 or later is part of all distributions of +%% LaTeX version 2005/12/01 or later. +% +%<*batch> +%<*gobble> +\ifx\jobname\relax\let\documentclass\undefined\fi +\ifx\documentclass\undefined +\csname fi\endcsname +%</gobble> +\input docstrip.tex +\keepsilent +\let\MetaPrefix\relax +\preamble +\endpreamble +\postamble +\endpostamble +\def\MetaPrefix{--} +\generate{% + \file{lua-ul.lua}{\from{lua-ul.dtx}{luacode}}% + \file{pre_append_to_vlist_filter.lua}{\from{lua-ul.dtx}{callback}}% +} +\let\MetaPrefix\DoubleperCent +\generate{\file{lua-ul.sty}{\from{lua-ul.dtx}{package}}} +\endbatchfile +%</batch> +%<*gobble> +\fi +\expandafter\ifx\csname @currname\endcsname\empty +\csname fi\endcsname +%</gobble> +%<*driver> +\documentclass{article} +\usepackage{lua-ul} +\usepackage{csquotes,doc,framed,metalogo,hyperref,luacolor,tikzducks,pict2e} +\begin{document} +\DocInput{lua-ul.dtx} +\PrintIndex +\PrintChanges +\end{document} +%</driver> +%<*gobble> +\fi +%</gobble> +% \fi +% +% +% \catcode`\§=14 +% \newunderlinetype\beginUnderDuck{\cleaders\hbox{§ +% \begin{tikzpicture}[x=.5ex,y=.5ex,baseline=.8ex]§ +% \duck +% \end{tikzpicture}§ +% }} +% \newcommand\underDuck[1]{{\beginUnderDuck#1}} +% \newunderlinetype\beginUnderWavy[\number\dimexpr1ex]{\cleaders\hbox{§ +% \setlength\unitlength{.3ex}§ +% \begin{picture}(4,0)(0,1) +% \thicklines +% \color{red}§ +% \qbezier(0,0)(1,1)(2,0) +% \qbezier(2,0)(3,-1)(4,0) +% \end{picture}§ +% }} +% \newcommand\underWavy[1]{{\beginUnderWavy#1}} +% \newunderlinetype\beginStrikeThough{\leaders\hbox{§ +% \normalfont\bfseries/§ +% }} +% \newcommand\StrikeThough[1]{{\beginStrikeThough#1}} +% +% \newcommand\Luaul{Lua-UL} +% \newcommand\luaul{Lua-UL} +% \GetFileInfo{lua-ul.sty} +% \title{The \luaul\ package\thanks{This document +% corresponds to \luaul~\fileversion, dated~\filedate.}} +% \author{Marcel Kr\"uger \\ \href{mailto:tex@2krueger.de}{tex@2krueger.de}} +% +% \maketitle +% \section{User-level interface} +% \Luaul\ uses new capabilities of the LuaTeX engine to provide +% underlining/\hspace{0pt}strikethrough/\hspace{0pt}highlighting +% etc.\ support without breaking ligatures, kerning or restricting input. +% The predefined user-level commands are \verb+\underLine+, \verb+\highLight+, and \verb+\strikeThrough+. +% (\verb+\highLight+ will only work correctly if the \texttt{luacolor} package is loaded) +% They are used as +% +% \begin{verbatim} +% \documentclass{article} +% \usepackage{lua-ul} +% \begin{document} +% This package is \strikeThrough{useless}\underLine{awesome}! +% \end{document} +% \end{verbatim} +% +% \begin{framed} +% This package is \strikeThrough{useless}\underLine{awesome}! +% \end{framed} +% +% For limited compatibility with \texttt{soul}, the \texttt{soul} package option allows you +% to use the traditional macro names from \texttt{soul} instead: +% +% \begin{verbatim} +% \documentclass{article} +% \usepackage[soul]{lua-ul} +% \begin{document} +% This package is \st{useless}\ul{awesome}! +% \end{document} +% \end{verbatim} +% +% \section{Expert interface} +% \DescribeMacro{\newunderlinetype}§ +% Sometimes, you might try to solve more interesting problems than boring underlining, +% strikethrough or highlighting. Maybe you always wanted \underWavy{to be your own +% spell checker}, want to demonstrate \underDuck{your love for ducks} or you think +% that the traditional \verb+\strikeThrough+ \StrikeThough{is not visible enough}. +% For all these cases, you can define your own \enquote{underlining} command based on a \TeX\ +% \verb+\[cxg]leaders+ command. +% +% For this, use\\ +% \verb+\newunderlinetype+\meta{macro name}\verb+[+\meta{context specifier}\verb+]{+\meta{\texttt{\string\leaders} command}\verb+}+ +% +% First, you have to pass the name of the command which should enable your new kind of underlining. +% (If you want to have an additional command which takes an argument and underlines this argument, +% you will have to define this manually.) +% The optional argument provides a \enquote{context}: This \enquote{context} has to expand to a string +% (something that can appear in a csname) which changes if the leaders box should be recalculated. +% The leader will be chached and reused whenever the context evaluates to the same string. +% So if your leaders should depend on the fontsize, +% the expansion of the context should contain the font size. If you leaders contain text in the current font, +% your context should include \verb+\fontname+. The default context includes the current size of \texttt{1ex} +% and the current color if luacolor is loaded. +% +% The final argument contains the actual leader command. You should omit the final glue normally passed to \verb+\leaders+, +% so e.g.\ write \verb+\leaders\hbox{ . }+ without appending \verb+\hfill+ or \verb+\hskip1pt+ etc. +% The height and depth of your leaders is ignored to ensure that adding underlines etc. does not change the general text layout. +% It is your responsibility to ensure that you are not too high or too low and intercept other lines. On the other hand, +% running dimensions work fine if you use a rule. +% +% For example, the special underline commands demonstrated above are implemented as +% +% \begin{verbatim} +% \usepackage{luacolor,tikzducks,pict2e} +% \newunderlinetype\beginUnderDuck{\cleaders\hbox{% +% \begin{tikzpicture}[x=.5ex,y=.5ex,baseline=.8ex]% +% \duck +% \end{tikzpicture}% +% }} +% \newcommand\underDuck[1]{{\beginUnderDuck#1}} +% \newunderlinetype\beginUnderWavy[\number\dimexpr1ex]{\cleaders\hbox{% +% \setlength\unitlength{.3ex}% +% \begin{picture}(4,0)(0,1) +% \thicklines +% \color{red}% +% \qbezier(0,0)(1,1)(2,0) +% \qbezier(2,0)(3,-1)(4,0) +% \end{picture}% +% }} +% \newcommand\underWavy[1]{{\beginUnderWavy#1}} +% \newunderlinetype\beginStrikeThough{\leaders\hbox{% +% \normalfont\bfseries/% +% }} +% \newcommand\StrikeThough[1]{{\beginStrikeThough#1}} +% \end{verbatim} +% +% Here \verb+\underWavy+ uses a custom context because it doesn't change depending on the current font color. +% +% If you only want to use \verb+\newunderlinetype+ and do not want to use the predefined underline types, you +% can use the \texttt{minimal} package option to disable them. +% +% \StopEventually{} +% \section{The implementation} +% \subsection{Helper modules} +% First we need a separate Lua module \verb+pre_append_to_vlist_filter+ which +% provides a variant of the \verb+append_to_vlist_filter+ callback which can be +% used by multiple packages. This ensures that we are compatible with other packages +% implementing \verb+append_to_vlist_filter+. +% \iffalse +%<*gobble> +\RequirePackage{docstrip-luacode} +\begin{docstrip-luacode}{pre_append_to_vlist_filter} +%</gobble> +%<*callback> +% \fi +% First check if an equivalent to \verb+pre_append_to_vlist_filter+ already exists. +% The idea is that this might eventually get added to the kernel directly. +% \begin{macrocode} +if luatexbase.callbacktypes.pre_append_to_vlist_filter then + return +end + +local call_callback = luatexbase.call_callback +local flush_node = node.flush_node +local prepend_prevdepth = node.prepend_prevdepth +local callback_define + +% \end{macrocode} +% HACK: Do not do this at home! We need to define the engine callback directly, +% so we use the debug library to get the ``real'' \verb+callback.define+: +% \begin{macrocode} +for i=1,5 do + local name, func = debug.getupvalue(luatexbase.disable_callback, i) + if name == 'callback_register' then + callback_define = func + break + end +end +if not callback_define then + error[[Unable to find callback.define]] +end + +local function filtered_append_to_vlist_filter(box, + locationcode, + prevdepth, + mirrored) + local current = call_callback("pre_append_to_vlist_filter", + box, locationcode, prevdepth, + mirrored) + if not current then + flush_node(box) + return + elseif current == true then + current = box + end + return call_callback("append_to_vlist_filter", + box, locationcode, prevdepth, mirrored) +end + +callback_define('append_to_vlist_filter', + filtered_append_to_vlist_filter) +luatexbase.callbacktypes.append_to_vlist_filter = nil +luatexbase.create_callback('append_to_vlist_filter', 'exclusive', + function(n, _, prevdepth) + return prepend_prevdepth(n, prevdepth) + end) +luatexbase.create_callback('pre_append_to_vlist_filter', + 'list', false) +% \end{macrocode} +% \iffalse +%</callback> +%<*gobble> +\end{docstrip-luacode} +%</gobble> +% \fi +% \subsection{Lua module} +% Now we can define our main Lua module: +% \iffalse +%<*gobble> +\begin{docstrip-luacode}{lua-ul} +%</gobble> +%<*luacode> +% \fi +% \begin{macrocode} +local hlist_t = node.id'hlist' +local vlist_t = node.id'vlist' +local kern_t = node.id'kern' +local glue_t = node.id'glue' + +local char_given = token.command_id'char_given' + +local underlineattrs = {} +local underline_types = {} +local saved_values = {} +local function new_underline_type() + for i=1,#underlineattrs do + local attr = underlineattrs[i] + saved_values[i] = tex.attribute[attr] + tex.attribute[attr] = -0x7FFFFFFF + end + local b = token.scan_list() + for i=1,#underlineattrs do + tex.attribute[underlineattrs[i]] = saved_values[i] + end + local lead = b.head + if not lead.leader then + tex.error("Leader required", {"An underline type has to \z + be defined by leader. You should use one of the", "commands \z + \\leaders, \\cleaders, or \\xleader, or \\gleaders here."}) + else + if lead.next then + tex.error("Too many nodes", {"An underline type can only be \z + defined by a single leaders specification,", "not by \z + multiple nodes. Maybe you supplied an additional glue?", + "Anyway, the additional nodes will be ignored"}) + end + table.insert(underline_types, lead) + b.head = lead.next + node.flush_node(b) + end + token.put_next(token.new(#underline_types, char_given)) +end +local function set_underline() + local j + for i=1,#underlineattrs do + local attr = underlineattrs[i] + if tex.attribute[attr] == -0x7FFFFFFF then + j = attr + break + end + end + if not j then + j = luatexbase.new_attribute( + "luaul" .. tostring(#underlineattrs+1)) + underlineattrs[#underlineattrs+1] = j + end + tex.attribute[j] = token.scan_int() +end +local functions = lua.get_functions_table() +local new_underline_type_func = + luatexbase.new_luafunction"luaul.new_underline_type" +local set_underline_func = + luatexbase.new_luafunction"luaul.set_underline_func" +token.set_lua("LuaULNewUnderlineType", new_underline_type_func) +token.set_lua("LuaULSetUnderline", set_underline_func, "protected") +functions[new_underline_type_func] = new_underline_type +functions[set_underline_func] = set_underline + +local add_underline_h +local function add_underline_v(head, attr) + for n in node.traverse(head) do + if head.id == hlist_t then + add_underline_h(n, attr) + elseif head.id == vlist_t then + add_underline_v(n.head, attr) + end + end +end +function add_underline_h(head, attr) + local used = false + node.slide(head.head) + local last_value + local first + for n in node.traverse(head.head) do + local new_value = node.has_attribute(n, attr) + if n.id == hlist_t then + new_value = nil + add_underline_h(n, attr) + elseif n.id == vlist_t then + new_value = nil + add_underline_v(n.head, attr) + elseif n.id == kern_t and n.subtype == 0 then + if n.next and not node.has_attribute(n.next, attr) then + new_value = nil + else + new_value = last_value + end + elseif n.id == glue_t and ( + n.subtype == 8 or + n.subtype == 9 or + n.subtype == 15 or + false) then + new_value = nil + end + if last_value ~= new_value then + if last_value then + local width = node.rangedimensions(head, first, n) + local kern = node.new(kern_t) + kern.kern = -width + local lead = node.copy(underline_types[last_value]) + lead.width = width + head.head = node.insert_before(head.head, first, lead) + node.insert_after(head, lead, kern) + end + if new_value then + first = n + end + last_value = new_value + end + end + if last_value then + local width = node.rangedimensions(head, first) + local kern = node.new(kern_t) + kern.kern = -width + kern.next = node.copy(underline_types[last_value]) + kern.next.width = width + node.tail(head.head).next = kern + end +end +local function filter(b, loc, prev, mirror) + for i = 1, #underlineattrs do + add_underline_v(b, underlineattrs[i]) + end + return true +end +require'pre_append_to_vlist_filter' +luatexbase.add_to_callback('pre_append_to_vlist_filter', + filter, 'add underlines to list') +% \end{macrocode} +% \iffalse +%</luacode> +%<*gobble> +\end{docstrip-luacode} +%</gobble> +% \fi +% \subsection{\TeX\ support package} +% Now only some \LaTeX\ glue code is still needed +% \iffalse +%<*package> +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage + {lua-ul} + [2020/03/12 v0.0.1 Underlining and related functionality for LuaTeX] + +% \fi +% Only \LuaLaTeX{} is supported. +% For other engines we show an error. +% \begin{macrocode} +\ifx\directlua\undefined + \PackageError{lua-ul}{LuaLaTeX required}% + {Lua-UL requires LuaLaTeX. + Maybe you forgot to switch the engine in your editor?} +\fi +\directlua{require'lua-ul'} +\RequirePackage{xparse} +% \end{macrocode} +% We support some options. Especially \verb+minimal+ will disable the predefined commands \verb+\underLine+ and \verb+\strikeThrough+ and allow you to define similar commands with your custom settings instead, \verb+soul+ tries to replicate names of the \verb soul package. +% \begin{macrocode} +\newif\ifluaul@predefined +\newif\ifluaul@soulnames +\luaul@predefinedtrue +\DeclareOption{minimal}{\luaul@predefinedfalse} +\DeclareOption{soul}{\luaul@soulnamestrue} +\ProcessOptions\relax +% \end{macrocode} +% Just one more tiny helper. +% \begin{macrocode} +\protected\def\luaul@maybedefineuse#1#2{% + \unless\ifcsname#1\endcsname + \expandafter\xdef\csname#1\endcsname{#2}% + \fi + \csname#1\endcsname +} + +% \end{macrocode} +% The main macro. +% \begin{macrocode} +\NewDocumentCommand\newunderlinetype{mO{\luaul@defaultcontext}m}{% + \newcommand#1{}% "Reserve" the name + \protected\def#1{% + \expandafter\luaul@maybedefineuse + \expanded{{\csstring#1@@#2}}% + {\LuaULSetUnderline + \LuaULNewUnderlineType\hbox{#3\hskip0pt}% + }}% +} +\ifluaul@predefined + \newcommand\luaul@defaultcontext{% + \number\dimexpr1ex + @\unless\ifx\undefined\LuaCol@Attribute + \the\LuaCol@Attribute + \fi + } + \newunderlinetype\@underLine% + {\leaders\vrule height -.65ex depth .75ex} + \newcommand\underLine[1]{{\@underLine#1}} + \newunderlinetype\@strikeThrough% + {\leaders\vrule height .55ex depth -.45ex} + \newcommand\strikeThrough[1]{{\@strikeThrough#1}} + \newunderlinetype\@highLight[\number\dimexpr1ex]% + {\color{yellow}\leaders\vrule height 1.75ex depth .75ex} + \newcommand\highLight[1]{{\@highLight#1}} + \ifluaul@soulnames + \let\textul\underLine \let\ul\textul + \let\textst\strikeThrough \let\st\textst + \let\texthl\highLight \let\hl\texthl + \fi +\fi +% \end{macrocode} +% \iffalse +%</package> +% \fi +% \Finale |