% \iffalse meta-comment % %% File: l3luatex.dtx % % Copyright (C) 2010-2020 The LaTeX3 Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % https://www.latex-project.org/lppl.txt % % This file is part of the "l3kernel bundle" (The Work in LPPL) % and all files in that bundle must be distributed together. % % ----------------------------------------------------------------------- % % The development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full,kernel]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \pkg{l3luatex} package: \LuaTeX-specific functions^^A % } % % \author{^^A % The \LaTeX3 Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released 2020-06-18} % % \maketitle % % \begin{documentation} % % The \LuaTeX{} engine provides access to the \Lua{} programming language, % and with it access to the \enquote{internals} of \TeX{}. In order to use % this within the framework provided here, a family of functions is % available. When used with \pdfTeX{}, \pTeX{}, \upTeX{} or \XeTeX{} these raise an % error: use \cs{sys_if_engine_luatex:T} to avoid this. Details on using % \Lua{} with the \LuaTeX{} engine are given in the \LuaTeX{} manual. % % \section{Breaking out to \Lua{}} % % \begin{function}[EXP, added = 2018-06-18]{\lua_now:n, \lua_now:e} % \begin{syntax} % \cs{lua_now:n} \Arg{token list} % \end{syntax} % The \meta{token list} is first tokenized by \TeX{}, which includes % converting line ends to spaces in the usual \TeX{} manner and which % respects currently-applicable \TeX{} category codes. The resulting % \meta{\Lua{} input} is passed to the \Lua{} interpreter for processing. % Each \cs{lua_now:n} block is treated by \Lua{} as a separate chunk. % The \Lua{} interpreter executes the \meta{\Lua{} input} immediately, % and in an expandable manner. % \begin{texnote} % \cs{lua_now:e} is a macro wrapper around \tn{directlua}: when % \LuaTeX{} is in use two expansions are required to yield the % result of the \Lua{} code. % \end{texnote} % \end{function} % % \begin{function}[added = 2018-06-18]{\lua_shipout_e:n, \lua_shipout:n} % \begin{syntax} % \cs{lua_shipout:n} \Arg{token list} % \end{syntax} % The \meta{token list} is first tokenized by \TeX{}, which includes % converting line ends to spaces in the usual \TeX{} manner and which % respects currently-applicable \TeX{} category codes. The resulting % \meta{\Lua{} input} is passed to the \Lua{} interpreter when the % current page is finalised (\emph{i.e.}~at shipout). Each % \cs{lua_shipout:n} block is treated by \Lua{} as a separate chunk. % The \Lua{} interpreter will execute the \meta{\Lua{} input} during the % page-building routine: no \TeX{} expansion of the \meta{\Lua{} input} % will occur at this stage. % % In the case of the \cs{lua_shipout_e:n} version the input is fully % expanded by \TeX{} in an \texttt{e}-type manner during the shipout % operation. % \begin{texnote} % At a \TeX{} level, the \meta{\Lua{} input} is stored as a % \enquote{whatsit}. % \end{texnote} % \end{function} % % \begin{function}[EXP, added = 2015-06-29]{\lua_escape:n, \lua_escape:e} % \begin{syntax} % \cs{lua_escape:n} \Arg{token list} % \end{syntax} % Converts the \meta{token list} such that it can safely be passed to % \Lua{}: embedded backslashes, double and single quotes, and newlines % and carriage returns are escaped. This is done by prepending an extra % token consisting of a backslash with category code~$12$, and for the line % endings, converting them to |\n| and |\r|, respectively. % \begin{texnote} % \cs{lua_escape:e} is a macro wrapper around \tn{luaescapestring}: % when \LuaTeX{} is in use two expansions are required to yield the % result of the \Lua{} code. % \end{texnote} % \end{function} % % \section{Lua interfaces} % % As well as interfaces for \TeX{}, there are a small number of Lua functions % provided here. % % \begin{function}{l3kernel} % All public interfaces provided by the module are stored within the % |l3kernel| table. % \end{function} % % \begin{function}{l3kernel.charcat} % \begin{syntax} % |l3kernel.charcat(|\meta{charcode}, \meta{catcode}|)| % \end{syntax} % Constructs a character of \meta{charcode} and \meta{catcode} and returns % the result to \TeX{}. % \end{function} % % \begin{function}{l3kernel.elapsedtime} % \begin{syntax} % |l3kernel.elapsedtime()| % \end{syntax} % Returns the CPU time in \meta{scaled seconds} since the start of the % \TeX{} run or since |l3kernel.resettimer| was issued. This only % measures the time used by the CPU, not the real time, e.g., waiting % for user input. % \end{function} % % \begin{function}{l3kernel.filedump} % \begin{syntax} % |l3kernel.filedump(|\meta{file}|,|\meta{offset}|,|\meta{length}|)| % \end{syntax} % Returns the uppercase hexadecimal representation of the content of the % \meta{file} read as bytes. If the \meta{length} is given, only this part % of the file is returned; similarly, one may specify the \meta{offset} from % the start of the file. If the \meta{length} is not given, the entire file % is read starting at the \meta{offset}. % \end{function} % % \begin{function}{l3kernel.filemdfivesum} % \begin{syntax} % |l3kernel.filemdfivesum(|\meta{file}|)| % \end{syntax} % Returns the MD5 sum of the file contents read as bytes; note that % the result will depend on the nature of the line endings used in the file, % in contrast to normal \TeX{} behaviour. If the \meta{file} is not found, % nothing is returned with \emph{no error raised}. % \end{function} % % \begin{function}{l3kernel.filemoddate} % \begin{syntax} % |l3kernel.filemoddate(|\meta{file}|)| % \end{syntax} % Returns the date/time of last modification of the \meta{file} in the % format % \begin{quote} % |D:|\meta{year}\meta{month}\meta{day}\meta{hour}\meta{minute}^^A % \meta{second}\meta{offset} % \end{quote} % where the latter may be |Z| (UTC) or % \meta{plus-minus}\meta{hours}|'|\meta{minutes}|'|. If the \meta{file} is % not found, nothing is returned with \emph{no error raised}. % \end{function} % % \begin{function}{l3kernel.filesize} % \begin{syntax} % |l3kernel.filesize(|\meta{file}|)| % \end{syntax} % Returns the size of the \meta{file} in bytes. If the \meta{file} is not % found, nothing is returned with \emph{no error raised}. % \end{function} % % \begin{function}{l3kernel.resettimer} % \begin{syntax} % |l3kernel.resettimer()| % \end{syntax} % Resets the timer used by |l3kernel.elapsetime|. % \end{function} % % \begin{function}{l3kernel.shellescape} % \begin{syntax} % |l3kernel.shellescape(|\meta{cmd}|)| % \end{syntax} % Executes the \meta{cmd} and prints to the log as for \pdfTeX{}. % \end{function} % % \begin{function}{l3kernel.strcmp} % \begin{syntax} % |l3kernel.strcmp(|\meta{str one}, \meta{str two}|)| % \end{syntax} % Compares the two strings and returns |0| to \TeX{} % if the two are identical. % \end{function} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3luatex} implementation} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \subsection{Breaking out to \Lua{}} % % \begin{macrocode} %<*tex> % \end{macrocode} % % \begin{macrocode} %<@@=lua> % \end{macrocode} % % \begin{macro}[EXP]{\@@_escape:n, \@@_now:n, \@@_shipout:n} % Copies of primitives. % \begin{macrocode} \cs_new_eq:NN \@@_escape:n \tex_luaescapestring:D \cs_new_eq:NN \@@_now:n \tex_directlua:D \cs_new_eq:NN \@@_shipout:n \tex_latelua:D % \end{macrocode} % \end{macro} % % These functions are set up in \pkg{l3str} for bootstrapping: we want to % replace them with a \enquote{proper} version at this stage, so clean up. % \begin{macrocode} \cs_undefine:N \lua_escape:e \cs_undefine:N \lua_now:e % \end{macrocode} % % \begin{macro}[EXP]{\lua_now:n, \lua_now:e} % \begin{macro}{\lua_shipout_e:n, \lua_shipout:n} % \begin{macro}[EXP]{\lua_escape:n, \lua_escape:e} % Wrappers around the primitives. As with engines other than \LuaTeX{} % these have to be macros, we give them the same status in all cases. % When \LuaTeX{} is not in use, simply give an error message/ % \begin{macrocode} \cs_new:Npn \lua_now:e #1 { \@@_now:n {#1} } \cs_new:Npn \lua_now:n #1 { \lua_now:e { \exp_not:n {#1} } } \cs_new_protected:Npn \lua_shipout_e:n #1 { \@@_shipout:n {#1} } \cs_new_protected:Npn \lua_shipout:n #1 { \lua_shipout_e:n { \exp_not:n {#1} } } \cs_new:Npn \lua_escape:e #1 { \@@_escape:n {#1} } \cs_new:Npn \lua_escape:n #1 { \lua_escape:e { \exp_not:n {#1} } } \sys_if_engine_luatex:F { \clist_map_inline:nn { \lua_escape:n , \lua_escape:e , \lua_now:n , \lua_now:e } { \cs_set:Npn #1 ##1 { \__kernel_msg_expandable_error:nnn { kernel } { luatex-required } { #1 } } } \clist_map_inline:nn { \lua_shipout_e:n , \lua_shipout:n } { \cs_set_protected:Npn #1 ##1 { \__kernel_msg_error:nnn { kernel } { luatex-required } { #1 } } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Messages} % % \begin{macrocode} \__kernel_msg_new:nnnn { kernel } { luatex-required } { LuaTeX~engine~not~in~use!~Ignoring~#1. } { The~feature~you~are~using~is~only~available~ with~the~LuaTeX~engine.~LaTeX3~ignored~'#1'. } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\Lua{} functions for internal use} % % \begin{macrocode} %<*lua> % \end{macrocode} % % Most of the emulation of \pdfTeX{} here is based heavily on Heiko Oberdiek's % \pkg{pdftexcmds} package. % % \begin{macro}{l3kernel} % Create a table for the kernel's own use. % \begin{macrocode} l3kernel = l3kernel or { } % \end{macrocode} % \end{macro} % % Local copies of global tables. % \begin{macrocode} local io = io local kpse = kpse local lfs = lfs local math = math local md5 = md5 local os = os local string = string local tex = tex local texio = texio local tonumber = tonumber local unicode = unicode % \end{macrocode} % % Local copies of standard functions. % \begin{macrocode} local abs = math.abs local byte = string.byte local floor = math.floor local format = string.format local gsub = string.gsub local lfs_attr = lfs.attributes local md5_sum = md5.sum local open = io.open local os_clock = os.clock local os_date = os.date local os_exec = os.execute local setcatcode = tex.setcatcode local sprint = tex.sprint local cprint = tex.cprint local write = tex.write local write_nl = texio.write_nl % \end{macrocode} % % Newer Con\TeX{}t releases replace the |unicode| library by |utf| and % since Lua 5.3 we can even use the Lua standard |utf8| library. % \begin{macrocode} local utf8_char = (utf8 and utf8.char) or (utf and utf.char) or unicode.utf8.char % \end{macrocode} % % Deal with Con\TeX{}t: doesn't use |kpse| library. % \begin{macrocode} local kpse_find = (resolvers and resolvers.findfile) or kpse.find_file % \end{macrocode} % % \begin{macro}[int]{escapehex} % An internal auxiliary to convert a string to the matching hex escape. % This works on a byte basis: extension to handled UTF-8 input is % covered in \pkg{pdftexcmds} but is not currently required here. % \begin{macrocode} local function escapehex(str) write((gsub(str, ".", function (ch) return format("%02X", byte(ch)) end))) end % \end{macrocode} % \end{macro} % % \begin{macro}{l3kernel.charcat} % Creating arbitrary chars using |tex.cprint|. % \begin{macrocode} local charcat function charcat(charcode, catcode) cprint(catcode, utf8_char(charcode)) end l3kernel.charcat = charcat % \end{macrocode} % \end{macro} % % \begin{macro}{l3kernel.elapsedtime, l3kernel.resettimer} % Simple timing set up: give the result from the system clock in scaled % seconds. % \begin{macrocode} local base_time = 0 local function elapsedtime() local val = (os_clock() - base_time) * 65536 + 0.5 if val > 2147483647 then val = 2147483647 end write(format("%d",floor(val))) end l3kernel.elapsedtime = elapsedtime local function resettimer() base_time = os_clock() end l3kernel.resettimer = resettimer % \end{macrocode} % \end{macro} % % \begin{macro}{l3kernel.filedump} % Similar comments here to the next function: read the file in binary mode % to avoid any line-end weirdness. % \begin{macrocode} local function filedump(name,offset,length) local file = kpse_find(name,"tex",true) if file then local length = tonumber(length) or lfs_attr(file,"size") local offset = tonumber(offset) or 0 local f = open(file,"rb") if f then if offset > 0 then f:seek("set",offset) end local data = f:read(length) escapehex(data) f:close() end end end l3kernel.filedump = filedump % \end{macrocode} % \end{macro} % % \begin{macro}{l3kernel.filemdfivesum} % Read an entire file and hash it: the hash function itself is a built-in. % As Lua is byte-based there is no work needed here in terms of UTF-8 % (see \pkg{pdftexcmds} and how it handles strings that have passed through % \LuaTeX{}). The file is read in binary mode so that no line ending % normalisation occurs. % \begin{macrocode} local function filemdfivesum(name) local file = kpse_find(name, "tex", true) if file then local f = open(file, "rb") if f then local data = f:read("*a") escapehex(md5_sum(data)) f:close() end end end l3kernel.filemdfivesum = filemdfivesum % \end{macrocode} % \end{macro} % % \begin{macro}{l3kernel.filemoddate} % See procedure \texttt{makepdftime} in \texttt{utils.c} of % \pdfTeX{}. % \begin{macrocode} local function filemoddate(name) local file = kpse_find(name, "tex", true) if file then local date = lfs_attr(file, "modification") if date then local d = os_date("*t", date) if d.sec >= 60 then d.sec = 59 end local u = os_date("!*t", date) local off = 60 * (d.hour - u.hour) + d.min - u.min if d.year ~= u.year then if d.year > u.year then off = off + 1440 else off = off - 1440 end elseif d.yday ~= u.yday then if d.yday > u.yday then off = off + 1440 else off = off - 1440 end end local timezone if off == 0 then timezone = "Z" else local hours = floor(off / 60) local mins = abs(off - hours * 60) timezone = format("%+03d", hours) .. "'" .. format("%02d", mins) .. "'" end write("D:" .. format("%04d", d.year) .. format("%02d", d.month) .. format("%02d", d.day) .. format("%02d", d.hour) .. format("%02d", d.min) .. format("%02d", d.sec) .. timezone) end end end l3kernel.filemoddate = filemoddate % \end{macrocode} % \end{macro} % % \begin{macro}{l3kernel.filesize} % A simple disk lookup. % \begin{macrocode} local function filesize(name) local file = kpse_find(name, "tex", true) if file then local size = lfs_attr(file, "size") if size then write(size) end end end l3kernel.filesize = filesize % \end{macrocode} % \end{macro} % % \begin{macro}{l3kernel.strcmp} % String comparison which gives the same results as \pdfTeX{}'s % \tn{pdfstrcmp}, although the ordering should likely not be relied upon! % \begin{macrocode} local function strcmp(A, B) if A == B then write("0") elseif A < B then write("-1") else write("1") end end l3kernel.strcmp = strcmp % \end{macrocode} % \end{macro} % % \begin{macro}{l3kernel.shellescape} % Replicating the \pdfTeX{} log interaction for shell escape. % \begin{macrocode} local function shellescape(cmd) local status,msg = os_exec(cmd) if status == nil then write_nl("log","runsystem(" .. cmd .. ")...(" .. msg .. ")\n") elseif status == 0 then write_nl("log","runsystem(" .. cmd .. ")...executed\n") else write_nl("log","runsystem(" .. cmd .. ")...failed " .. (msg or "") .. "\n") end end l3kernel.shellescape = shellescape % \end{macrocode} % \end{macro} % % \subsection{Generic \Lua{} and font support} % % \begin{macrocode} %<*initex> % \end{macrocode} % % \begin{macrocode} %<@@=alloc> % \end{macrocode} % % A small amount of generic code is used by almost all \LuaTeX{} material so % needs to be loaded by the format. % \begin{macrocode} attribute_count_name = "g_@@_attribute_int" bytecode_count_name = "g_@@_bytecode_int" chunkname_count_name = "g_@@_chunkname_int" whatsit_count_name = "g_@@_whatsit_int" require("ltluatex") % \end{macrocode} % % With the above available the font loader code used by plain \TeX{} and % \LaTeXe{} when used with \LuaTeX{} can be loaded here. This is thus being % treated more-or-less as part of the engine itself. % \begin{macrocode} require("luaotfload-main") local _void = luaotfload.main() % \end{macrocode} % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % %\end{implementation} % %\PrintIndex