summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
Diffstat (limited to 'macros')
-rw-r--r--macros/latex/contrib/graphics-def/README.md2
-rw-r--r--macros/latex/contrib/graphics-def/dvisvgm.def12
-rw-r--r--macros/luatex/latex/linebreaker/README.md21
-rw-r--r--macros/luatex/latex/linebreaker/linebreaker-doc.pdfbin0 -> 51388 bytes
-rw-r--r--macros/luatex/latex/linebreaker/linebreaker-doc.tex196
-rw-r--r--macros/luatex/latex/linebreaker/linebreaker.lua453
-rw-r--r--macros/luatex/latex/linebreaker/linebreaker.sty31
-rw-r--r--macros/luatex/latex/luacensor/README12
-rw-r--r--macros/luatex/latex/luacensor/luacensor.pdfbin0 -> 129263 bytes
-rw-r--r--macros/luatex/latex/luacensor/luacensor.sty367
-rw-r--r--macros/luatex/latex/luacensor/luacensor.tex575
-rw-r--r--macros/xetex/latex/xepersian/README4
-rw-r--r--macros/xetex/latex/xepersian/xepersian-doc-intro.ltx2
-rw-r--r--macros/xetex/latex/xepersian/xepersian-doc-title.ltx4
-rw-r--r--macros/xetex/latex/xepersian/xepersian-doc.ltx2
-rw-r--r--macros/xetex/latex/xepersian/xepersian-doc.pdfbin246669 -> 246573 bytes
-rw-r--r--macros/xetex/latex/xepersian/xepersian.dtx222
-rw-r--r--macros/xetex/latex/xepersian/xepersian.pdfbin766214 -> 782344 bytes
18 files changed, 1873 insertions, 30 deletions
diff --git a/macros/latex/contrib/graphics-def/README.md b/macros/latex/contrib/graphics-def/README.md
index 7f21def113..060344b425 100644
--- a/macros/latex/contrib/graphics-def/README.md
+++ b/macros/latex/contrib/graphics-def/README.md
@@ -1,7 +1,7 @@
LaTeX support for color and graphics
====================================
-Release 2022-02-05
+Release 2022-02-19
This bundle contains the 'engine-dependent' part of support for
color and graphics in LaTeX2e (the code here will also function
diff --git a/macros/latex/contrib/graphics-def/dvisvgm.def b/macros/latex/contrib/graphics-def/dvisvgm.def
index 1f2e0577d4..8390ee29b9 100644
--- a/macros/latex/contrib/graphics-def/dvisvgm.def
+++ b/macros/latex/contrib/graphics-def/dvisvgm.def
@@ -16,7 +16,7 @@
%%
%% https://github.com/latex3/graphics-def/issues
%
-\ProvidesFile{dvisvgm.def}[2022/02/05 v1.4 dvisvgm graphics driver for latex]
+\ProvidesFile{dvisvgm.def}[2022/02/19 v1.5 dvisvgm graphics driver for latex]
% The following is copied from dvips.def:
\def\GPT@space{ }
\def\c@lor@arg#1{%
@@ -169,16 +169,16 @@
\dimen@\Gin@svg@real@height@bp pt%
\advance\dimen@ by-\Gin@ury pt%
\edef\Gin@svg@view@base{\strip@pt\dimen@}%
+ \special{dvisvgm:bbox \strip@pt\Gin@req@width pt \strip@pt\Gin@req@height pt transform}%
\Gin@req@width0.99626\Gin@req@width%
\Gin@req@height0.99626\Gin@req@height%
\raise\strip@pt\Gin@req@height bp\hbox{%
\special{dvisvgm: raw
- <g transform="translate({?x},{?y})">
+ <g transform="translate({?x},{?y})">%
<svg overflow="\ifGin@clip hidden\else visible\fi" width="\strip@pt\Gin@req@width" height="\strip@pt\Gin@req@height"
- viewBox="\Gin@llx\GPT@space\Gin@svg@view@base\GPT@space\Gin@svg@view@width\GPT@space\Gin@svg@view@height">
- <image width="\Gin@svg@real@width@bp" height="\Gin@svg@real@height@bp"
- xlink:href="#1"/>
- </svg>
+ viewBox="\Gin@llx\GPT@space\Gin@svg@view@base\GPT@space\Gin@svg@view@width\GPT@space\Gin@svg@view@height">%
+ <image width="\Gin@svg@real@width@bp" height="\Gin@svg@real@height@bp" xlink:href="#1"/>%
+ </svg>%
</g>}%
}%
}
diff --git a/macros/luatex/latex/linebreaker/README.md b/macros/luatex/latex/linebreaker/README.md
new file mode 100644
index 0000000000..5065054fcf
--- /dev/null
+++ b/macros/luatex/latex/linebreaker/README.md
@@ -0,0 +1,21 @@
+#The Linebreaker package, version v0.1 (2022-02-19)
+
+This package tries to prevent overflow lines in paragraphs or boxes.
+It changes the LuaTeX's `linebreak` callback, and it re-typesets the paragraph
+with increased values of `\tolerance` and `\emergencystretch`
+until the overflow doesn't happen. If that doesn't help, it chooses the solution
+with the lowest badness.
+
+
+## Usage
+
+
+ \usepackage{linebreaker}
+
+
+## License
+
+Permission is granted to copy, distribute and/or modify this software
+under the terms of the LaTeX Project Public License, version 1.3.
+
+
diff --git a/macros/luatex/latex/linebreaker/linebreaker-doc.pdf b/macros/luatex/latex/linebreaker/linebreaker-doc.pdf
new file mode 100644
index 0000000000..7f2b426ae3
--- /dev/null
+++ b/macros/luatex/latex/linebreaker/linebreaker-doc.pdf
Binary files differ
diff --git a/macros/luatex/latex/linebreaker/linebreaker-doc.tex b/macros/luatex/latex/linebreaker/linebreaker-doc.tex
new file mode 100644
index 0000000000..729c9e9eaa
--- /dev/null
+++ b/macros/luatex/latex/linebreaker/linebreaker-doc.tex
@@ -0,0 +1,196 @@
+\documentclass{l3doc}
+
+
+% \usepackage[latin,english]{babel}=15pt
+\usepackage{lipsum}
+\usepackage{linebreaker}
+\usepackage{lua-widow-control}
+\linebreakersetup{debug}
+\usepackage{hyperref}
+\newcommand\authormail[1]{\footnote{\textless\url{#1}\textgreater}}
+\ifdefined\HCode
+\renewcommand\authormail[1]{\space\textless\Link[#1]{}{}#1\EndLink\textgreater}
+\fi
+
+\ifdefined\gitdate\else\def\gitdate{Undefined}\fi
+\ifdefined\version\else\def\version{Undefined}\fi
+
+\usepackage{listings}
+\usepackage{fontspec}
+\setmainfont{TeX Gyre Schola}
+% \setmonofont[Scale=MatchLowercase]{Inconsolatazi4}
+\IfFontExistsTF{Noto Sans Mono Regular}{%
+ \setmonofont[Scale=MatchLowercase]{Noto Sans Mono Regular}
+}{\setmonofont{NotoMono-Regular.ttf}}
+\usepackage{upquote}
+
+\usepackage{microtype}
+
+\newcommand\testbox[1]{%
+ \parbox{150pt}{%
+ \parindent=15pt%
+ \tolerance=1%
+ \pretolerance=1%
+ #1
+ }%
+}
+
+\newcommand\printtest[1]{%
+ \linebreakerdisable%
+ \noindent\testbox{%
+ #1
+ \par\medskip\noindent\hfill\textbf{Without Linebreaker}\hfill\null
+ }%
+ \linebreakerenable%
+ \hfill%
+ \testbox{%
+ #1
+ \par\medskip\noindent\hfill\textbf{With Linebreaker}\hfill\null
+ }%
+}
+
+\title{The \texttt{Linebreaker} package}
+\author{Michal Hoftich\authormail{michal.h21@gmail.com}}
+\date{Version \version\\\gitdate}
+\begin{document}
+\maketitle
+\tableofcontents
+
+\section{Introduction}
+
+This package tries to prevent overflow lines in paragraphs or boxes.
+It changes the Lua\TeX's \verb|linebreak| callback, and it re-typesets the paragraph
+with increased values of \cmd{\tolerance} and \cmd{\emergencystretch}
+until the overflow doesn't happen. If that doesn't help, it chooses the solution
+with the lowest badness.
+
+
+The advantage of this approach is that paragraphs that have not overflowed are
+typeset with default parameters. These are changed only for problematic
+paragraphs.
+
+The code is experimental, and you may find bugs or clashes with
+other packages. You can send bug reports to the package's repository on
+Github\footnote{\url{https://github.com/michal-h21/linebreaker}}.
+
+
+
+% \noindent\begin{minipage}{220pt}
+ \def\testtext{%
+The example document given below creates two pages by using Lua code alone. You
+will learn how to access TeX's boxes and counters from the Lua side, shipout a
+page into the PDF file, create horizontal and vertical boxes (hbox and vbox),
+create new nodes and manipulate the nodes links structure. The example covers
+the following node types: rule, whatsit, vlist, hlist and action.
+ }
+
+
+
+\begin{figure}
+\printtest\testtext%
+\caption{Example of Linebreaker's effect}
+\end{figure}
+
+% \end{minipage}
+\newpage
+\section{Usage}
+
+There is only \LaTeX\ package at the moment. Con\TeX t and Plain \TeX\ are not supported.
+You can enable the overflow paragraph handling by loading of the Linebreaker package:
+
+% \begin{lstlisting}{latex}
+% \usepackage[options]{linebreaker}
+\begin{center}
+\cmd{\usepackage}\verb|{linebreaker}|
+\end{center}
+% \end{lstlisting}
+
+\subsection{Enable and disable Linebreaker}
+
+The package owerflow handling is enabled by default. You can disable it and then re-enable using the following commands:
+
+\begin{function}{\linebreakerdisable}
+Disable line-breaking processing. \LaTeX\ will typeset the following paragraphs with the default values for line-breaking.
+\end{function}
+
+\begin{function}{\linebreakerenable}
+Re-enable line-breaking processing after it was disabled by \cmd{\linebreakerdisable}.
+\end{function}
+
+\subsection{Change of Linebreaker parameters}
+
+\begin{function}{\linebreakersetup}
+ Change settings of the line-breaking algorithm.
+ Usage: \cmd{\linebreakersetup}\Arg{options}
+
+
+\end{function}
+
+\subsubsection{Available options for the \cmd{\linebreakersetup} command}
+
+\begin{function}{maxcycles}
+Number of attempts to re-typeset the paragraph.
+\end{function}
+
+\begin{function}{maxemergencystretch}
+Maximal allowed value of \verb|\emergencystretch|.
+\end{function}
+
+\begin{function}{maxtolerance}
+Maximal allowed value of \verb|tolerance|.
+\end{function}
+
+\begin{function}{debug}
+Print debugging info to the terminal output.
+\end{function}
+
+\subsubsection{Example of \cmd{\linebreakersetup} use}
+
+\begin{lstlisting}{latex}
+\linebreakersetup{
+ maxtolerance=90,
+ maxemergencystretch=1em,
+ maxcycles=4
+}
+\end{lstlisting}
+
+
+
+\section{Historical background}
+
+The motivation to create this package was a
+question\footnote{\url{http://tex.stackexchange.com/q/200989/2891}} by Frank Mittelbach on
+TeX.SE. His idea was to rewrite TeX’s paragraph-building algorithm in Lua to
+allow detection of rivers and similar tasks unsupported by the standard TeX
+line-breaking algorithm.
+
+As a complete rewrite of the line-breaking algorithm seemed too complicated,
+I tried a different approach. Lua\TeX\ provides callbacks for working with node lists.
+It calls these callbacks when actions on the node lists happen, such as
+ligaturing, kerning, before line-breaking, after line-breaking, and
+callback that handles the line-breaking process.
+
+There is a \verb|tex.linebreak| function, which takes
+node list and table with \TeX\ parameters (like \verb|lineskip|, \verb|baselineskip|, \verb|tolerance|,
+etc.). It returns a new node list, with lines broken into horizontal boxes.
+
+My idea is to process this returned node list, detect problems and call
+`tex.linebreak` with different parameters if lines overflow. At the
+moment, overflow box detection works in most cases, but river detection is unusable, and it needs further corrections.
+
+\section{License}
+
+Permission is granted to copy, distribute and/or modify this software
+under the terms of the LaTeX Project Public License, version 1.3.
+
+\section{Changes}
+
+\begin{description}
+ \item[v0.1, 2022-02-19]
+ \begin{itemize}
+ \item Initial version
+ \end{itemize}
+\end{description}
+
+\end{document}
+
diff --git a/macros/luatex/latex/linebreaker/linebreaker.lua b/macros/luatex/latex/linebreaker/linebreaker.lua
new file mode 100644
index 0000000000..e6c05d4fdf
--- /dev/null
+++ b/macros/luatex/latex/linebreaker/linebreaker.lua
@@ -0,0 +1,453 @@
+local linebreaker = {}
+
+local hlist_id = node.id "hlist"
+local glyph_id = node.id "glyph"
+local glue_id = node.id "glue"
+local vlist_id = node.id "vlist"
+local maxval = 0x10FFFF
+
+local fonts = fonts or {}
+fonts.hashes = fonts.hashes or {}
+local font_identifiers = fonts.hashes.identifiers or {}
+
+
+-- debugging function, it can be redefined to print debug info if needed
+-- it discards arguments by default
+function linebreaker.debug_print(...)
+ if linebreaker.debug then
+ print(table.concat({...}, "\t"))
+ end
+end
+
+linebreaker.debug = false
+
+-- max allowed value of tolerance
+linebreaker.max_tolerance = 9999
+-- maximal allowed emergencystretch
+linebreaker.max_emergencystretch = tex.sp("3em")
+-- line breaking function is customizable
+linebreaker.breaker = tex.linebreak -- linebreak function
+linebreaker.max_cycles = 30 -- max # of attempts to find best solution
+ -- the number is totally arbitrary
+
+linebreaker.boxsize = 65536 -- it is used in river detection. default box size is 1pt.
+ -- value is in scaled points
+linebreaker.vertical_point = tex.baselineskip.width -- vertical matrix
+linebreaker.previous_points = linebreaker.vertical_point / linebreaker.boxsize
+ -- number of
+ -- points which will be taken into account in
+ -- calculating river value. these points will
+ -- be processed in both directions
+-- factor which will multiply the parindent value to get the minimal allowed width
+-- of a last line in a paragraph
+linebreaker.width_factor = 1.5
+-- will be linebreaker active?
+linebreaker.active = true
+-- return array with default parameters
+function linebreaker.parameters()
+ return {}
+end
+
+-- diagnostic function for traversing nodes returned by linebreaking
+-- function. only top level nodes are processed, not sublists
+function linebreaker.traverse(head)
+ --for n in node.traverse(node.tail(head).head) do
+ for n in node.traverse(head) do
+ linebreaker.debug_print(n.id, n.subtype)
+ if n.id == 10 then
+ local x = n.spec or {}
+ --x.shrink = 111222
+ linebreaker.debug_print("glue", x.shrink,x.stretch)
+ end
+ end
+ linebreaker.debug_print "****************"
+ return head
+end
+
+
+local utfchar = unicode.utf8.char
+
+local getchar = function(n)
+ local t = {}
+ local xchar = font_identifiers[n.font].characters[n.char].unicode
+
+ if type(xchar) == "table" then
+ for k,v in pairs(xchar) do
+ t[#t+1] = utfchar(v)
+ end
+ else
+ -- 8-bit fonts don't contain the unicode value, so just return the char
+ -- value of node. In some cases it will be good, for math it will be mostly wrong
+ return utfchar(xchar or n.char)
+ end
+ return table.concat(t)
+end
+
+-- get text content of node list
+local function get_text(line)
+ local t = {}
+ for n in node.traverse(line) do
+ if n.id == glue_id then t[#t+1] = " "
+ elseif n.id == glyph_id then t[#t+1] = getchar(n)
+ elseif n.id == hlist_id or n.id == vlist_id then t[#t+1] = get_text(n.head)
+ end
+ end
+ return table.concat(t)
+end
+
+-- find badness of a line
+function linebreaker.par_badness(head)
+ local n = 0
+ for line in node.traverse_id(hlist_id, head) do
+ -- print(get_text(line.head), line.glue_order, line.glue_sign, line.glue_set)
+ -- glue_sign: > 0 = normal, 1 = stretch, 2 = shrink
+ -- we count only shrink, but stretch may result in overfull box as well
+ -- I just don't know, how to detect which value of glue_set means error
+ if line.glue_sign == 2 and line.glue_set >= 1 then n = n + 1 end
+ end;
+ return n
+end
+
+-- we have table with guessed param tables. we loop over them and find one with
+-- lowest value of badness. this situation shouldn't happen, as at the moment
+-- tolerance may be as high as 9999 and this should fix all overfulls
+-- this code is remain of older method of guessing right value of tolerance
+local function find_best(params)
+ local min = 10000 -- arbitrary high value
+ local n = params[1] or {}
+ for _, p in ipairs(params) do
+ local badness = p.badness or min
+ if badness <= min then
+ n = p
+ min = badness
+ end
+ end
+ linebreaker.debug_print "best solution"
+ for k,v in pairs(n) do
+ linebreaker.debug_print(k,v)
+ end
+ return n
+end
+
+-- all glue_spec nodes has .width key, but it is the same all the time. real
+-- width depends on line shrink and stretch
+-- this will be used in river detection
+local function glue_calc(n, sign,set)
+ -- function for calculating glue width
+ local size
+ if sign ==2 then
+ size=n.spec.width - n.spec.shrink*set
+ else
+ size=n.spec.width + n.spec.stretch*set
+ end
+ return size
+end
+
+
+-- calculate new tolerance
+-- max_tolerance / max_cycles is added to the current tolerance value
+local function calc_tolerance(previous)
+ local previous = previous or tex.tolerance
+ local max_cycles = linebreaker.max_cycles
+ local max_tolerance = linebreaker.max_tolerance
+ local new = previous + (max_tolerance / max_cycles)-- + math.sqrt(previous * 4)
+ return (new < max_tolerance) and new or max_tolerance
+end
+
+-- river detection -- it doesn't work at the moment, maybe in the future?
+-- idea is following:
+-- 1. count widths of words and spaces
+-- 2. divide widths to segments of some width (1pt?)
+-- 3. assign number to segments: full glyph in the midle of a word:0
+-- full glue: 1
+-- edges of words: fraction depending on glyph dimensions
+-- (it would be nice to incorporate glyph shapes, but it is
+-- unrealistic, we don't have access)
+-- 4. add numbers from previous line, probably sum of segments in some
+-- distance (baselineskip / segments per sp = 45°?)
+-- 5. sum segments for a glue / glue width = river ratio?
+-- 6. find right threshold for telling which value of river ratio is a real
+-- river
+-- 7. calculate river ratio for whole paragraph (sum of over threshold
+-- river ratios?)
+-- I am not a mathematician, so I don't know whether this method is accurate,
+-- correct, or efficient,
+--
+--
+function linebreaker.detect_rivers(head)
+ local lines = {} --
+ local boxsize = linebreaker.boxsize
+ local vertical_point = linebreaker.vertical_point
+ local previous_points = math.ceil(linebreaker.previous_points)
+ -- calculate river for current node `n` and insert values to
+ local calc_river = function(line,n, lines)
+ -- get previous line
+ local previous = lines and lines[#lines] or {}
+ local get_point = function(i)
+ return previous[i] or 0
+ end
+ local x = #line
+ --print("soucasna delka line", x)
+ local sum = 0
+ for i = 1, #n do
+ local v = n[i] or 0
+ if v > 0 then
+ v = v + get_point(i)
+ -- ve add values from previous line
+ --for c = 1, previous_points do
+ local c = previous_points
+ v = v + (get_point(i+x+c)) + (get_point(i+x-c))
+ --print("adding",v)
+ --end
+ end
+ line[i+x] = v
+ sum = sum + v
+ --print("Calculate for", i,v)
+ --print("celkem v", i+x,v)
+ end
+ return line, sum / #n
+ end
+ for n in node.traverse_id(0, head) do
+ local line = {}
+ -- glue parameters
+ local set = n.glue_set
+ local sign = n.glue_sign
+ local order = n.glue_order
+ local first_node = n.head
+ local first_glyph = nil
+ local first = true
+ local word_count = 0
+ local last_glyph = nil
+ local last_glue = n.head
+ local position = 0
+ local remain = 0
+ local get_glyph_black = function(glyph)
+ -- only calculate blackness for glyphs
+ if glyph and glyph.id == glyph_id then
+ local w,h,d = node.dimensions(glyph, glyph.next)
+ -- 1 is maximal white
+ local blackness = 1 - ((h+d) / vertical_point)
+ -- print(char(glyph.char), blackness) return w / boxsize or 0, blackness
+ end
+ return 0,0
+ end
+ -- get width of nodes
+ local get_width = function(start,fin)
+ local w = node.dimensions(set,sign,order,start, fin)
+ return w / boxsize -- get width in pt
+ end
+ local add_word = function(start,fin)
+ word_count = word_count + 1
+ local t = {}
+ local width = get_width(start,fin)
+ -- first and last glyph are taken into account for blackness calculation
+ local w1, f = get_glyph_black(first_glyph)
+ local w2, l = get_glyph_black(last_glyph)
+ w1 = math.ceil(w1 + remain)
+ w2 = math.ceil(w2)
+ if word_count <= 1 then
+ w1 = 0
+ end
+ width = width + remain
+ remain = width - math.floor(width)
+ width = width - remain
+ for i=1, w1 do
+ t[#t+1] = f/i -- add more black at the end of glyph
+ end
+ -- middle of the word
+ for i=1, (width-w1-w2) do
+ t[#t+1] = 0
+ end
+ -- last glyph
+ for i=1, w2 do
+ t[#t+1] = l/(w2-i+1)
+ end
+ line = calc_river(line, t, lines)
+ --print("black", f,l)
+ end
+ add_glue = function(x)
+ local t = {}
+ local r
+ local width = get_width(x,x.next) + remain
+ remain = width - math.floor(width)
+ width = width - remain
+ for i=1, width do
+ t[#t+1] = 1
+ end
+ --print("glue",#t)
+ line, r = calc_river(line,t, lines)
+ if r > 1 then
+ local w = node.new("whatsit","pdf_literal")
+ local color = 1 / r
+ w.data = string.format("q 1 %f 1 rg 0 0 m 0 5 l 5 5 l 5 0 l f Q", color)
+ -- print("color",w.data)
+ node.insert_before(n.head,x,w)
+ end
+ return r
+ end
+ for x in node.traverse(n.head) do
+ if x.id == glue_id and x.subtype == 0 then
+ --print("glue width", get_width(x,x.next))
+ add_word(last_glue, x, first_glyph,last_glyph)
+ local river_value = add_glue(x)
+ -- print("riverness", river_value)
+ first = true
+ last_glue = x.next -- calculate width of next word from here
+ elseif x.id == glyph_id then
+ if first then
+ first_glyph = x
+ end
+ first = false
+ last_glyph = x
+ end
+ end
+ add_word(last_glue, x, first_glyph, last_glyph)
+ --table.insert(lines, calc_river(t,lines))
+ --for k,v in pairs(line) do print(k,v) end
+ table.insert(lines,line)
+ -- print(table.concat(lines[#lines],","))
+ -- local width, h, d = node.dimensions(set, sign, order, n.head, node.tail(n.head))
+ -- print(width,table.concat(t))
+ end
+ return 0
+end
+
+-- End of river detection
+
+
+function linebreaker.last_line_width(head)
+ -- measure length of the last line in a paragraph
+ local w, w1
+ local last = node.tail(head)
+ local n = node.copy(last)
+ -- last node is not node list, return negative number
+ if not n.head then return -1 end
+ n.head = node.remove(n.head, node.tail(n.head))
+ for x in node.traverse(n.head) do
+ if x.id == glue_id then
+ if x.subtype == 15 then
+ n.head = node.remove(n.head, x)
+ end
+ end
+ end
+ -- something went wrong, so discard this solution
+ if not n.head then return 0 end
+ w, _, _ = node.dimensions(n.glue_set, n.glue_sign, n.glue_order, n.head)
+ w1, _, _ = node.dimensions(n.glue_set, n.glue_sign, n.glue_order, n)
+ return w
+end
+
+
+-- try to linebreak current paragraph with increasing tolerance and
+-- emergencystretch
+function linebreaker.best_solution(par, parameters)
+ -- save current node list, because call to the linebreaker modifies it
+ -- and we wouldn't be able to run it multiple times
+ local head = node.copy_list(par)
+ -- this shouldn't happen
+ if #parameters > linebreaker.max_cycles then
+ -- we couldn't find a solution without badness
+ -- break paragraph with the least bad parameters
+ return linebreaker.breaker(head,find_best(parameters))
+ end
+ local params = parameters[#parameters] -- newest parameters are last in the
+ -- table that will be used in recursive invocations of this function
+ -- it holds updated parameters
+ local newparams = {}
+ -- this value is set by hpack_quality callback that is executed by
+ -- tex.linebreak when overflow or underflow happens
+ linebreaker.badness = 0
+ -- break paragraph
+ local newhead, info = linebreaker.breaker(head, params)
+ -- calc badness -- we don't use this anymore, badness of the currently
+ -- processed node list is set by hpack_filter
+ local badness = linebreaker.badness or 0
+ -- don't allow lines shorter than the paragraph indent
+ local last_line_width = linebreaker.last_line_width(newhead)
+ linebreaker.debug_print("last line width", last_line_width, "parindent:", tex.parindent * linebreaker.width_factor)
+ if last_line_width > 0 and last_line_width < tex.parindent * linebreaker.width_factor then
+ linebreaker.debug_print "too short last line"
+ badness = 10000
+ end
+
+ params.badness = badness
+ if badness > 0 then
+ -- calc new value of tolerance
+ local tolerance = calc_tolerance(params.tolerance) -- or 10000
+ -- save tolerance to newparams so this value will be used in next run
+ newparams.tolerance = tolerance
+ newparams.emergencystretch = (params.emergencystretch or 0) + linebreaker.max_emergencystretch / linebreaker.max_cycles
+ table.insert(parameters, newparams)
+ -- run linebreaker again
+ return linebreaker.best_solution(par, parameters)
+ end
+ -- river detection doesn't work, so we don't execute ths code anymore
+ -- detect rivers only for paragraphs without overflow boxes
+ -- local rivers = linebreaker.detect_rivers(newhead)
+ -- print("rivers", rivers)
+ -- print "normal return"
+ --]]
+ return newhead, info
+end
+
+-- this is just reporting function which print lines with glue widths.
+-- this may be useful in river detection
+local function glue_width(head)
+ for n in node.traverse_id(hlist_id, head) do
+ local t = {}
+ local set = n.glue_set
+ local sign = n.glue_sign
+ local order = n.glue_order
+ for x in node.traverse(n.head) do
+ if x.id == glue_id then
+ local g = x.spec
+ local size = glue_calc(x, sign, set)
+ t[#t+1] = ":"..size.."."
+ elseif x.id == glyph_id then
+ t[#t+1] = getchar(x)
+ end
+ end
+ local width, h, d = node.dimensions(set, sign, order, n.head, node.tail(n.head))
+ end
+end
+
+local function fix_nest(info)
+ tex.nest[tex.nest.ptr].prevdepth=info.prevdepth
+ tex.nest[tex.nest.ptr].prevgraf=info.prevgraf
+end
+
+-- test whether the current overfull box message occurs inside our linebreaker function
+local is_inside_linebreaker = false
+function linebreaker.linebreak(head,is_display)
+ -- we can disable linebreaker processing
+ if linebreaker.active == false then
+ local newhead, info = linebreaker.breaker(head)
+ fix_nest(info)
+ return newhead
+ end
+ local parameters = linebreaker.parameters()
+ is_inside_linebreaker = true
+ local newhead, info = linebreaker.best_solution(head, {parameters})
+ is_inside_linebreaker = false
+ fix_nest(info)
+ return newhead
+end
+
+function linebreaker.hpack_quality(incl, detail, head, first, last)
+ if not is_inside_linebreaker then
+ local detail_msg = incl=="overfull" and "overflow" or "badness"
+ linebreaker.debug_print( incl .. " box at lines: " .. first .." -- " .. last ..". " .. detail_msg .. ": " .. detail .."\n text:" .. get_text(head) )
+ end
+ linebreaker.badness = (linebreaker.badness or 0) + detail
+end
+
+-- It seems necessary to call the post_linebreak filter in order to support floats
+-- Even if it does nothing but to return the node list. I don't understand why...
+function linebreaker.post_linebreak(head)
+ return true
+end
+
+linebreaker.get_text = get_text
+
+return linebreaker
+
diff --git a/macros/luatex/latex/linebreaker/linebreaker.sty b/macros/luatex/latex/linebreaker/linebreaker.sty
new file mode 100644
index 0000000000..2804f4bbe6
--- /dev/null
+++ b/macros/luatex/latex/linebreaker/linebreaker.sty
@@ -0,0 +1,31 @@
+\ProvidesPackage{linebreaker}
+
+\RequirePackage{luatexbase}
+\directlua{%
+linebreaker = require "linebreaker"
+luatexbase.add_to_callback("linebreak_filter", linebreaker.linebreak, "new linebreak callback")
+luatexbase.add_to_callback("post_linebreak_filter", linebreaker.post_linebreak, "new linebreak callback")
+luatexbase.add_to_callback("hpack_quality", linebreaker.hpack_quality, "Our overfull box reporting function")
+}
+
+
+% linebreaker setup
+\ExplSyntaxOn
+\keys_define:nn{linebreaker}{
+ debug .code:n = \directlua{linebreaker.debug=true},
+ maxtolerance .code:n = \directlua{linebreaker.max_tolerance = tonumber("#1")},
+ maxemergencystretch .code:n = \directlua{linebreaker.max_emergencystretch = tex.sp("#1")},
+ maxcycles .code:n = \directlua{linebreaker.max_cycles = tonumber("#1")},
+}
+\NewDocumentCommand{\linebreakersetup}{m}{
+\keys_set:nn {linebreaker} {#1}
+}
+\ExplSyntaxOff
+
+% enable/disable linebreaker
+\NewDocumentCommand{\linebreakerenable}{}{\directlua{linebreaker.active=true}}
+\NewDocumentCommand{\linebreakerdisable}{}{\directlua{linebreaker.active=false}}
+
+
+
+\endinput
diff --git a/macros/luatex/latex/luacensor/README b/macros/luatex/latex/luacensor/README
new file mode 100644
index 0000000000..6d737bcdac
--- /dev/null
+++ b/macros/luatex/latex/luacensor/README
@@ -0,0 +1,12 @@
+luacensor - redacting sensitive information with Lua
+
+luacensor is a package that takes advantage of Lua to securely redact documents, both by hiding all characters and by slightly varying the length of strings to prevent jigsaw identification. It also is friendly to screen readers by adding alt-text indicating redacted content.
+
+See the documentation (luacensor.pdf) for examples and instructions for
+installation and usage.
+
+This work may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), version 1.3 or later.
+
+For suggestions, feature requests, and bug reports, please use the project's
+GitHub site: https://github.com/ezgranet/luacensor
diff --git a/macros/luatex/latex/luacensor/luacensor.pdf b/macros/luatex/latex/luacensor/luacensor.pdf
new file mode 100644
index 0000000000..3abd55551d
--- /dev/null
+++ b/macros/luatex/latex/luacensor/luacensor.pdf
Binary files differ
diff --git a/macros/luatex/latex/luacensor/luacensor.sty b/macros/luatex/latex/luacensor/luacensor.sty
new file mode 100644
index 0000000000..614378a56e
--- /dev/null
+++ b/macros/luatex/latex/luacensor/luacensor.sty
@@ -0,0 +1,367 @@
+%luacensor.sty
+\def\luacensorversionnumber{1.0.0}
+\ProvidesPackage{luacensor}
+[2022/02/18 \luacensorversionnumber\
+ Redact sensitive information using Lua]
+% !TeX program = lualatex
+% !TeX encoding = utf8
+% 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 Elijah Z Granet
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% DEPENDENCIES
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\RequirePackage{luacode}
+\RequirePackage{environ}% http://ctan.org/pkg/environ
+
+\RequirePackage{verbatim}
+% ^ for the censoring
+\RequirePackage{accsupp}
+%^for accessibility
+\RequirePackage{fontspec}
+%^for black lines
+%in theory, you could do
+%a lighter version of this
+%package with just asterisks
+%or `[TEXT-REDACTED]'
+%And perhaps that's better for
+%the environment with printing
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% FONTS
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% redacted is prettier and free to download
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Strongly recommended
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\IfFontExistsTF{Redacted}{%
+\newfontface\cnsrfnt[%%%%%%
+%the scale is arbitrary, but kind of works
+Scale=1.1,
+%%the below declarations are to prevent warnings about shapes not being available
+ItalicFont=Redacted,%
+BoldItalicFont=Redacted,%
+BoldFont=Redacted,%
+SmallCapsFont=Redacted]{Redacted}
+\newcommand{\onething}{\cnsrfnt\ • }
+\newcommand{\twothings}{\cnsrfnt\ • • }
+\newcommand{\donothing}{}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%The little spaces let justification happen
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% x chosen as an arbitrary average width
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+}{
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% This option works perfectly
+%fine, it's just less pretty
+%%but a good fallback because
+% Source Sans is in TeX dists by default
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\newfontface\cnsrfnt[Scale=1.01,%To allow for separate use of source sans in text
+WordSpace=0,%To make it all one black line
+%the below declarations are to prevent warnings about shapes not being available
+ItalicFont={Source Sans Pro Black},BoldItalicFont={Source Sans Pro Black},BoldFont={Source Sans Pro Black},SmallCapsFont={Source Sans Pro Black}]{Source Sans Pro Black}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Bit of unicode magic below to make the black line effect
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\newcommand{\onething}{\cnsrfnt ▬ }
+\newcommand{\twothings}{\cnsrfnt ▬ ▬ }
+\newcommand{\donothing}{}
+}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% A neat fallback for disappearing things…
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% FULL CREDIT
+% and FULSOME THANKS
+% TO TEX.SE USER
+% Werner for the code below
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\makeatletter
+\newcommand{\voidenvironment}[1]{%
+ \expandafter\providecommand\csname env@#1@save@env\endcsname{}%
+ \expandafter\providecommand\csname env@#1@process\endcsname{}%
+ \@ifundefined{#1}{}{\RenewEnviron{#1}{}}%
+}
+\makeatother
+\newcommand{\hddn}[1]{%
+\ifcnsr{}\else%
+#1\fi}
+\newenvironment*{hidden}{\begin{@empty}
+}{\end{@empty}}
+\voidenvironment{hidden}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% the CENSOR COMMAND
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\newif\ifcnsr\cnsrfalse
+\newcommand{\cnsr}[1]{%
+ \ifcnsr{%
+
+\voidenvironment{equation*}
+\voidenvironment{equation}
+\voidenvironment{table}
+\voidenvironment{table*}
+\voidenvironment{tabular}
+\voidenvironment{tabular*}
+\voidenvironment{}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% I don't know how many
+% people use TEX native accent commands
+% in LuaTEX given that using Unicode is more
+%people's style. But just in case, because these can lead to stray accent marks floating above censored letters.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\renewcommand{\`}[1]{}
+\renewcommand{\'}[1]{}
+\renewcommand{\^}[1]{}
+\renewcommand{\"}[1]{}
+\renewcommand{\H}[1]{}
+\renewcommand{\~}[1]{}
+\renewcommand{\c}[1]{}
+\renewcommand{\k}[1]{}
+\renewcommand{\l}[1]{}
+\renewcommand{\=}[1]{}
+\renewcommand{\b}[1]{}
+\renewcommand{\.}[1]{}
+\renewcommand{\d}[1]{}
+\renewcommand{\r}[1]{}
+\renewcommand{\u}[1]{}
+\renewcommand{\v}[1]{}
+\renewcommand{\t}[1]{}
+\renewcommand{\o}[1]{}
+\renewcommand{\i}[1]{}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%TEX primitives can break the
+%code because they don't have the good manners
+%to put their arguments in brackets
+%No offence, of course, to the
+%honoured Prof Knuth, who had
+%magnificent genius reasons
+%for coding things that way
+%it's just that i'm an idiot
+%and therefore need things simple
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\renewcommand{\hskip}[1]{}
+\renewcommand{\vskip}[1]{}
+\renewcommand{\raise}[1]{}
+\renewcommand{\lower}[1]{}
+\renewcommand{\kern}[1]{}
+% here we have the accsupp magic
+% this operates by replacing the 'x's
+% or unicode black squares as the case
+% may be with an alt text
+% this serves a dual purpose of both making
+%pdftotext not break with huge strings of meaningless characters
+%but more importantly
+% it means screen readers don't subject
+%. their users to the meaningless reading out of unicode black squares 50 times in a row!
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\BeginAccSupp{method=plain,ActualText={TEXT REDACTED}}%
+\rndstring{#1}
+\EndAccSupp{}%
+}
+ \else%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% if the conditional is off
+% the command does absolutely nothing
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+#1%
+\fi}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% The LUA MAGIC PART
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\begin{luacode}
+--fulsome thanks to TeX.SE users Henri Menke and David Carlisle, without whom none of this would be possible
+local function rndstring()
+
+ local toks = token.scan_toks(s)
+ local on = true
+for n, t in ipairs(toks) do
+ if t.csname == "begin" or t.csname == "end" then
+ on = false
+-- The below is necessary as TeX primitives can break the code otherwise because they do not use brackets
+ end
+
+ if not(on) and t.cmdname == "right_brace" then
+ on = true
+ -- This prevents needless errors about gibberish up commands
+end
+if on and t.csname == "&" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "%" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "$" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "#" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "_" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "{" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "}" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "~" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "^" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+elseif on and t.cmdname == "letter" then
+-- The below is the randomness part of this, which I admit is fairly arbitrary, but will more often artificially shorten strings than lengthen them, as testing found if lengthening was too frequent, it led to really unsightly long strings.
+ local f = math.random (1,20)
+ if f == 1 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+ elseif f == 2 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+ elseif f == 3 then
+ local letter = token.create'donothing' toks[n] = letter
+ elseif f == 4 then
+ local letter = token.create'twothings'
+ toks[n] = letter
+ elseif f == 5 then
+ local letter = token.create'donothing' toks[n] = letter
+
+ else
+ local letter = token.create'onething'
+ toks[n] = letter
+ end
+ elseif
+ on and t.cmdname == "spacer" then
+ local f = math.random (1,20)
+ if f == 2 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+ elseif f == 3 then
+
+ local letter = token.create'donothing' toks[n] = letter
+ elseif f == 4 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+ elseif f == 5 then
+
+
+ local letter = token.create'twothings' toks[n] = letter
+ elseif f == 6 then
+
+
+ local letter = token.create'donothing' toks[n] = letter
+ elseif f == 7 then
+ local letter = token.create'donothing' toks[n] = letter
+
+
+ else
+ local letter = token.create'onething'
+ toks[n] = letter
+
+ end
+
+ elseif
+ on and t.cmdname == "other_char" then
+ local f = math.random (1,20)
+ if f == 2 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+ elseif f == 3 then
+
+ local letter = token.create'donothing' toks[n] = letter
+ elseif f == 4 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+ elseif f == 5 then
+
+
+ local letter = token.create'twothings' toks[n] = letter
+ elseif f == 6 then
+
+
+ local letter = token.create'donothing' toks[n] = letter
+ elseif f == 7 then
+ local letter = token.create'donothing' toks[n] = letter
+
+
+ else
+ local letter = token.create'onething'
+ toks[n] = letter
+
+ end
+ end
+ end
+ --Drop the token in and move on
+ token.put_next(toks)
+end
+local lft = lua.get_functions_table()
+--make a global command
+lft[#lft + 1] = rndstring
+token.set_lua("rndstring", #lft, "global")
+\end{luacode} \ No newline at end of file
diff --git a/macros/luatex/latex/luacensor/luacensor.tex b/macros/luatex/latex/luacensor/luacensor.tex
new file mode 100644
index 0000000000..9e2f1e14a0
--- /dev/null
+++ b/macros/luatex/latex/luacensor/luacensor.tex
@@ -0,0 +1,575 @@
+\documentclass{article}
+\usepackage{xcolor}
+\usepackage[hidelinks]{hyperref}
+\usepackage[british]{babel}
+\usepackage[useregional]{datetime2}
+\DTMlangsetup[en-GB]{ord=omit}
+\definecolor{LightGray}{gray}{0.9}
+\usepackage{mathpazo}
+\usepackage{luacensor}
+\usepackage{fontspec}
+\IfFontExistsTF{Palatine Parliamentary}{\setromanfont[RawFeature={+onum,+pnum}]{Palatine Parliamentary}
+}{\setromanfont[RawFeature={+onum,+pnum}]{TeX Gyre PagellaX}}
+\setmonofont[Scale=.9]{Deja Vu Sans Mono}
+\usepackage{luacode}
+%\usepackage[firstnumber=last]{fancyvrb}
+\usepackage{minted}
+\date{\today}
+\author{Elijah Z Granet\thanks{e-mail: \href{mailto:ezg21@cantab.ac.uk}{ezg21@cantab.ac.uk}}}
+
+\title{\texttt{LuaCensor}:\\A package for redacting sensitive information}
+
+\begin{document}
+\maketitle
+\tableofcontents
+\section{Very Quick Guide}
+\subsection{Purpose}
+This package redacts sensitive information using \texttt{Lua}, and adds accessibility support.
+\subsection{Usage}
+The package is called with:
+
+\begin{minted}{latex}
+\usepackage{luacensor}
+\end{minted}
+
+Sensitive information is enclosed within the command:
+
+\begin{minted}{latex}
+\cnsr{John Smith}
+\end{minted}
+
+When the outputted document is intended for authorised readers who are supposed to see sensitive information, no further action is needed. When the outputted document is for general audiences, who are \textit{not} supposed to see sensitive information, add the following line to the preamble:
+
+\begin{minted}{latex}
+\cnsrtrue
+\end{minted}
+
+This activates the censoring globally.
+\subsection{Requirements}
+This package will \textbf{only} work in Lua\LaTeX. The package works out of the box with a standard \TeX\ distribution, but ideally, I would strongly suggest installing the free (\textit{libre} \& \textit{gratis}) `Redacted' font from Google Fonts, which gives a quite æsthetically pleasing black bar effect.\footnote{Available at: \url{https://fonts.google.com/specimen/Redacted}.}
+\subsection{\color{red}Warning}
+The package is completely effective at censoring text formatted with normal \LaTeX\ commands, by which I mean that it is impossible for someone to ascertain the original text (or even its precise length) from the outputted \MakeLowercase{\textsc{PDF}} when the \verb|\cnsrtrue| option has been activated.
+
+ However, math mode is used, it will censor numbers, but not operators or \TeX\ (as opposed to Unicode) operators. This is probably fine for most instances, but unacceptable where security is of the highest priority, and I would not really recommend using this package to censor highly secret formulæ; the \texttt{censor} package in your \TeX\ distribution will do a better job of that. The package is set to completely disappear the output (as opposed to black bar over) of the \texttt{math}, \texttt{align}, \texttt{equation}, \texttt{tabular}, and a few other environments, as disappearing these environments proved more secure than the piecemeal blacking out I saw. It is probable that there are packages and macros that will break the \texttt{cnsr} macro, and therefore, care should be taken to always examine output before public distribution.
+ \subsection{Demonstration}
+ \begin{quote}
+ \begin{minted}[fontsize=\footnotesize,
+,breaklines]{latex}
+%In the preamble: \usepackage{luacensor}
+\begin{quote}
+\cnsrtrue\footnotesize Whereas recognition of the \cnsr{inherent dignity and of the equal and inalienable rights of all members of the human family} is the foundation of freedom, justice and peace in the world,
+
+\cnsr{Whereas disregard and contempt for human rights have resulted in barbarous acts which have outraged the conscience of mankind, and the advent of a world in which human beings shall enjoy freedom of speech and belief and freedom from fear and want has been proclaimed as the highest aspiration of the common people,}
+ \end{minted}
+ \end{quote}
+ \begin{quote}
+\cnsrtrue\footnotesize Whereas recognition of the \cnsr{inherent dignity and of the equal and inalienable rights of all members of the human family} is the foundation of freedom, justice and peace in the world,
+
+\cnsr{Whereas disregard and contempt for human rights have resulted in barbarous acts which have outraged the conscience of mankind, and the advent of a world in which human beings shall enjoy freedom of speech and belief and freedom from fear and want has been proclaimed as the highest aspiration of the common people,}
+
+ \end{quote}
+
+ \clearpage
+
+
+
+\section{More detailed information}
+\subsection{Purpose}
+This package is a relatively lightweight and aesthetically pleasing censorship solution which includes accessibility features to allow screen readers to be aware that content has been redacted.
+\subsection{The censoring mechanism}
+The package uses Luas's \texttt{toks} filter to replace all \textsc{utf8} characters with a single glyph (• in the case of Redacted, and a Unicode black rectangle in the fallback \TeX\ default font Source Sans Pro). In both font options, these combine visually into a single line (though this can be deconstructed in a text editor).
+
+ However, while changing all characters into a single character is effective in \textit{most} cases, this alone would not be sufficiently secure for reliable usage. This is because knowing the length of a censored name could be combined with other information in, for example, a Family Court judgment, to allow for what lawyers call `jigsaw identification' (\textit{eg}, where there is only one person with an eight letter surname who meets the other details given in the judgment).
+
+Therefore, the package adds an extra layer of security by randomly changing the length of strings during the censorship phase; censored strings can thus be either longer or shorter by a few characters. This means that while the area of the blacked out content will be \textit{approximately} similar to the length of the uncensored string (which means wireframing more or less works), it cannot be used to reverse engineer information about the censored content.
+
+\subsection{The accessibility feature}
+One concern about document redaction is ensuring that visually impaired readers of your document, who use screen reading software to listen to your text, may encounter problems with censored content. If the screen reading software skips over the censored text altogether, it will be a very confusing jump for the visually impaired user. If the screen reading software reads the replacement characters, it will be very annoying for the visually impaired reader to hear, in a censored paragraph, the same character being read out \textit{ad nauseam} (\textit{eg}, `Asterisk, asterisk, asterisk…').
+
+To overcome this limitation, the package uses the \texttt{accsup} package to add an `actual text' feature which will lead screen readers (and utilities like \texttt{pdftotext}) to replace the string of replacement characters with the two words `TEXT REDACTED'. This also will be encountered by naïve users who try to outdo the package by copying and pasting the black blocks from Adobe™ Acrobat or Reader. (However, because other PDF readers, like Apple's Preview, do not implement accessibility features, this is \textbf{not} an additional security feature and is not on its own sufficient to work for redaction; if it were otherwise, the rest of the package would be unnecessary)
+
+
+
+\subsection{Bugs and development}
+All bugs, feature requests, or other technical points should be submitted to the package's official Github page.\footnote{\url{https://github.com/ezgranet/luacensor}}
+
+\subsection{Licensing}
+The software is free and open-sour ce software licensed under the Latex Public Project Licence.\footnote{\url{https://www.latex-project.org/lppl/}}
+
+
+\subsection{Some useful advice}
+This package is really good at some things, but if you find it breaks down on censoring complex \LaTeX\ code, the existing \texttt{censor} package on \textsc{ctan} is excellent (albeit less good with accessibility), and works with non-\texttt{Lua} versions of \TeX. Incidentally, you can use both this package and \texttt{censor} in the same file without trouble; this (\textit{not} a penchant for annoying tech-speak) is why the main command in this package is \texttt{cnsr} without vowels.
+
+\clearpage
+\section{Implementation}
+\begin{minted}[
+frame=lines,
+framesep=2mm,
+baselinestretch=1.2,
+bgcolor=LightGray,
+fontsize=\footnotesize,
+linenos,
+breaklines,
+firstnumber=last
+]
+{latex}
+\ProvidesPackage{luacensor}
+[2022/02/17
+ Redact sensitive information using Lua]
+% 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 Elijah Z Granet
+\end{minted}
+\subsection{Dependencies}
+\begin{minted}[
+frame=lines,
+framesep=2mm,
+baselinestretch=1.2,
+bgcolor=LightGray,
+fontsize=\footnotesize,
+linenos,
+breaklines,
+firstnumber=last
+]{latex}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% DEPENDENCIES
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\RequirePackage{luacode}
+\RequirePackage{environ}
+\RequirePackage{verbatim}
+% ^ for the censoring
+\RequirePackage{accsupp}
+%^for accessibility
+\RequirePackage{fontspec}
+%^for black lines
+%in theory, you could do
+%a lighter version of this
+%package with just asterisks
+%or `[TEXT-REDACTED]'
+%And perhaps that's better for
+%the environment with printing
+\end{minted}
+\subsection{Fonts}
+\begin{minted}[
+frame=lines,
+framesep=2mm,
+baselinestretch=1.2,
+bgcolor=LightGray,
+fontsize=\footnotesize,
+linenos,
+breaklines,
+firstnumber=last
+]{latex}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% FONTS
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% redacted is prettier and free to download
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Strongly recommended
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\IfFontExistsTF{Redacted}{%
+\newfontface\cnsrfnt[%%%%%%
+%the scale is arbitrary, but kind of works
+Scale=1.1,
+%%the below declarations are to prevent warnings about shapes not being available
+ItalicFont=Redacted,%
+BoldItalicFont=Redacted,%
+BoldFont=Redacted,%
+SmallCapsFont=Redacted]{Redacted}
+\newcommand{\onething}{\cnsrfnt\ • }
+\newcommand{\twothings}{\cnsrfnt\ • • }
+\newcommand{\donothing}{}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%The little spaces let justification happen
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% x chosen as an arbitrary average width
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+}{
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% This option works perfectly
+%fine, it's just less pretty
+%%but a good fallback because
+% Source Sans is in TeX dists by default
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\newfontface\cnsrfnt[Scale=1.01,%To allow for separate use of source sans in text
+WordSpace=0,%To make it all one black line
+%the below declarations are to prevent warnings about shapes not being available
+ItalicFont={Source Sans Pro Black},BoldItalicFont={Source Sans Pro Black},BoldFont={Source Sans Pro Black},SmallCapsFont={Source Sans Pro Black}]{Source Sans Pro Black}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Bit of unicode magic below to make the black line effect
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\newcommand{\onething}{\cnsrfnt ▬ }
+\newcommand{\twothings}{\cnsrfnt ▬ ▬ }
+\newcommand{\donothing}{}
+}
+\end{minted}
+
+
+
+\subsection{Eliminate pesky environments}
+\begin{minted}[
+frame=lines,
+framesep=2mm,
+baselinestretch=1.2,
+bgcolor=LightGray,
+fontsize=\footnotesize,
+linenos,
+breaklines,
+firstnumber=last
+]{latex}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% A neat fallback for disappearing things…
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% FULL CREDIT
+% and FULSOME THANKS
+% TO TEX.SE USER
+% Werner for the code below
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\makeatletter
+\newcommand{\voidenvironment}[1]{%
+ \expandafter\providecommand\csname env@#1@save@env\endcsname{}%
+ \expandafter\providecommand\csname env@#1@process\endcsname{}%
+ \@ifundefined{#1}{}{\RenewEnviron{#1}{}}%
+}
+\makeatother
+\newcommand{\hddn}[1]{%
+\ifcnsr{}\else%
+#1\fi}
+\newenvironment*{hidden}{\begin{@empty}
+}{\end{@empty}}
+\voidenvironment{hidden}
+
+\end{minted}
+\subsection{The \texttt{cnsr} command}
+\begin{minted}[
+frame=lines,
+framesep=2mm,
+baselinestretch=1.2,
+bgcolor=LightGray,
+fontsize=\footnotesize,
+linenos,
+breaklines,
+firstnumber=last
+]{latex}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% the CENSOR COMMAND
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\newif\ifcnsr\cnsrfalse
+
+\newcommand{\cnsr}[1]{%
+ \ifcnsr{%
+\voidenvironment{equation*}
+\voidenvironment{equation}
+\voidenvironment{table}
+\voidenvironment{table*}
+\voidenvironment{tabular}
+\voidenvironment{tabular*}
+\voidenvironment{}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% I don't know how many
+% people use TEX native accent commands
+% in LuaTEX given that using Unicode is more
+%people's style. But just in case, because these can lead to stray accent marks floating above censored letters.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\renewcommand{\`}[1]{}
+\renewcommand{\'}[1]{}
+\renewcommand{\^}[1]{}
+\renewcommand{\"}[1]{}
+\renewcommand{\H}[1]{}
+\renewcommand{\~}[1]{}
+\renewcommand{\c}[1]{}
+\renewcommand{\k}[1]{}
+\renewcommand{\l}[1]{}
+\renewcommand{\=}[1]{}
+\renewcommand{\b}[1]{}
+\renewcommand{\.}[1]{}
+\renewcommand{\d}[1]{}
+\renewcommand{\r}[1]{}
+\renewcommand{\u}[1]{}
+\renewcommand{\v}[1]{}
+\renewcommand{\t}[1]{}
+\renewcommand{\o}[1]{}
+\renewcommand{\i}[1]{}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%TEX primitives can break the
+%code because they don't have the good manners
+%to put their arguments in brackets
+%No offence, of course, to the
+%honoured Prof Knuth, who had
+%magnificent genius reasons
+%for coding things that way
+%it's just that i'm an idiot
+%and therefore need things simple
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\renewcommand{\hskip}[1]{}
+\renewcommand{\vskip}[1]{}
+\renewcommand{\raise}[1]{}
+\renewcommand{\lower}[1]{}
+\renewcommand{\kern}[1]{}
+% here we have the accsupp magic
+% this operates by replacing the 'x's
+% or unicode black squares as the case
+% may be with an alt text
+% this serves a dual purpose of both making
+%pdftotext not break with huge strings of meaningless characters
+%but more importantly
+% it means screen readers don't subject
+%. their users to the meaningless reading out of unicode black squares 50 times in a row!
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\BeginAccSupp{method=plain,ActualText={TEXT REDACTED}}%
+\rndstring{#1}
+\EndAccSupp{}%
+}
+ \else%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% if the conditional is off
+% the command does absolutely nothing
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+#1%
+\fi}
+\end{minted}
+\subsection{The Lua Magic}
+\begin{minted}[
+frame=lines,
+framesep=2mm,
+baselinestretch=1.2,
+bgcolor=LightGray,
+fontsize=\footnotesize,
+linenos,
+breaklines,
+firstnumber=last
+]{latex}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% The LUA MAGIC PART
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\begin{luacode}
+\end{minted}
+\begin{minted}[
+frame=lines,
+framesep=2mm,
+baselinestretch=1.2,
+bgcolor=LightGray,
+fontsize=\footnotesize,
+linenos,
+breaklines,
+firstnumber=last
+]{lua}
+--fulsome thanks to TeX.SE users Henri Menke and David Carlisle, without whom none of this would be possible
+local function rndstring()
+
+ local toks = token.scan_toks(s)
+ local on = tfrue
+for n, t in ipairs(toks) do
+ if t.csname == "begin" or t.csname == "end" then
+ on = false
+-- The below is necessary as TeX primitives can break the code otherwise because they do not use brackets
+ end
+
+ if not(on) and t.cmdname == "right_brace" then
+ on = true
+ -- This prevents needless errors about gibberish up commands
+end
+if on and t.csname == "&" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "%" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "$" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "#" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "_" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "{" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "}" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "~" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif on and t.csname == "^" then
+ local letter = token.create'donothing'
+ toks[n] = letter
+elseif on and t.cmdname == "letter" then
+-- The below is the randomness part of this, which I admit is fairly arbitrary,
+--but will more often artificially shorten
+--strings than lengthen them, as testing
+--found if lengthening was too frequent, it
+--led to really unsightly long strings.
+
+ local f = math.random (1,20)
+ if f == 1 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+ elseif f == 2 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+ elseif f == 3 then
+ local letter = token.create'donothing' toks[n] = letter
+ elseif f == 4 then
+ local letter = token.create'twothings'
+ toks[n] = letter
+ elseif f == 5 then
+ local letter = token.create'donothing' toks[n] = letter
+
+ else
+ local letter = token.create'onething'
+ toks[n] = letter
+ end
+ elseif
+ on and t.cmdname == "spacer" then
+ local f = math.random (1,20)
+ if f == 2 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+ elseif f == 3 then
+
+ local letter = token.create'donothing' toks[n] = letter
+ elseif f == 4 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+ elseif f == 5 then
+
+
+ local letter = token.create'twothings' toks[n] = letter
+ elseif f == 6 then
+
+
+ local letter = token.create'donothing' toks[n] = letter
+ elseif f == 7 then
+ local letter = token.create'donothing' toks[n] = letter
+
+
+ else
+ local letter = token.create'onething'
+ toks[n] = letter
+
+ end
+
+elseif on and t.cmdname == "other_char" then
+ local f = math.random (1,20)
+
+if f == 2 then local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif f == 3 then
+local letter = token.create'donothing' toks[n] = letter
+
+elseif f == 4 then
+ local letter = token.create'donothing'
+ toks[n] = letter
+
+elseif f == 5 then
+ local letter = token.create'twothings'
+ toks[n] = letter
+
+ elseif f == 6 then
+local letter = token.create'donothing' toks[n] = letter
+
+elseif f == 7 then
+local letter = token.create'donothing' toks[n] = letter
+
+
+else local letter = token.create'onething'
+ toks[n] = letter
+
+ end
+ end
+ end
+ --Drop the token in and move on
+ token.put_next(toks)
+ end
+ local lft = lua.get_functions_table()
+--make a global command
+lft[#lft + 1] = rndstring
+token.set_lua("rndstring", #lft, "global")
+\end{minted}
+\begin{minted}[
+frame=lines,
+framesep=2mm,
+baselinestretch=1.2,
+bgcolor=LightGray,
+fontsize=\footnotesize,
+linenos,
+breaklines, firstnumber=last
+]{latex}
+\end{luacode}
+\end{minted}
+\clearpage
+\section{Version History}
+\subsection{\texttt{1.00}}
+\ttfamily 18 February 2022: Package creation
+
+
+\end{document} \ No newline at end of file
diff --git a/macros/xetex/latex/xepersian/README b/macros/xetex/latex/xepersian/README
index 9cf2761710..679a271a2c 100644
--- a/macros/xetex/latex/xepersian/README
+++ b/macros/xetex/latex/xepersian/README
@@ -1,6 +1,6 @@
____________________
The XePersian package
-v23.4
+v23.6
The xepersian package provides a convenient interface for
typesetting Persian and English texts in LaTeX, using XeTeX
@@ -25,7 +25,7 @@ The announcements for the new releases of the package will
also appear in the Discussions space under the Announcements
category.
-Current version release date: 2022/02/18
+Current version release date: 2022/02/20
___________________________________________
Vafa Khalighi
tex-persian@vafa.ac
diff --git a/macros/xetex/latex/xepersian/xepersian-doc-intro.ltx b/macros/xetex/latex/xepersian/xepersian-doc-intro.ltx
index 7f50f38b2b..66085aecce 100644
--- a/macros/xetex/latex/xepersian/xepersian-doc-intro.ltx
+++ b/macros/xetex/latex/xepersian/xepersian-doc-intro.ltx
@@ -25,7 +25,7 @@ If you use \textsf{\XePersian} package to write any input \TeX\ document, and th
* Copyright (c) 2008--2022 Vafa Khalighi
* Copyright (c) 2018--2020 bidi-tex GitHub Organization
*
-* v23.4, 2022/02/18
+* v23.6, 2022/02/20
*
* License: LaTeX Project Public License, version
* 1.3c or higher (your choice)
diff --git a/macros/xetex/latex/xepersian/xepersian-doc-title.ltx b/macros/xetex/latex/xepersian/xepersian-doc-title.ltx
index 013d8490a6..878e0abcb1 100644
--- a/macros/xetex/latex/xepersian/xepersian-doc-title.ltx
+++ b/macros/xetex/latex/xepersian/xepersian-doc-title.ltx
@@ -3,7 +3,7 @@
\begin{document}
\title{\textcolor{blue!65!black}{\Huge The \textsf{\XePersian} \textit{Package}}\\
\LARGE Persian for \LaTeXe, using {\XeTeX} engine\\[5pt]
-\large User documentation for \textcolor{blue!65!black}{version 23.4}}
+\large User documentation for \textcolor{blue!65!black}{version 23.6}}
\author{Vafa Khalighi\thanks{Thanks to the bidi-tex GitHub organization consisted of David Carlisle and other members of the {\LaTeX} team to maintain the package in the period 2018--2020.}\\
\url{tex-persian@vafa.ac}}
\setcounter{page}{0}
@@ -14,7 +14,7 @@
\textcolor{blue!65!black}{\Huge The \textsf{\XePersian} \textit{Package}}
\Large
-User documentation for \textcolor{blue!65!black}{version 23.4}}
+User documentation for \textcolor{blue!65!black}{version 23.6}}
\vskip 0pt plus 3fill
\textit{\large to my Master, \textbf{Ferdowsi The Great}}
diff --git a/macros/xetex/latex/xepersian/xepersian-doc.ltx b/macros/xetex/latex/xepersian/xepersian-doc.ltx
index ca23adc820..367860baf5 100644
--- a/macros/xetex/latex/xepersian/xepersian-doc.ltx
+++ b/macros/xetex/latex/xepersian/xepersian-doc.ltx
@@ -1,7 +1,7 @@
\documentclass{ltxdoc}
\input{xepersian-doc-preamble.ltx}
\hypersetup{%
- pdftitle={User documentation for version 23.4 of the XePersian
+ pdftitle={User documentation for version 23.6 of the XePersian
package}%
}
\begin{document}
diff --git a/macros/xetex/latex/xepersian/xepersian-doc.pdf b/macros/xetex/latex/xepersian/xepersian-doc.pdf
index 9b78599bff..0e82459e23 100644
--- a/macros/xetex/latex/xepersian/xepersian-doc.pdf
+++ b/macros/xetex/latex/xepersian/xepersian-doc.pdf
Binary files differ
diff --git a/macros/xetex/latex/xepersian/xepersian.dtx b/macros/xetex/latex/xepersian/xepersian.dtx
index 1142e5a0d7..33dccc8412 100644
--- a/macros/xetex/latex/xepersian/xepersian.dtx
+++ b/macros/xetex/latex/xepersian/xepersian.dtx
@@ -5,7 +5,7 @@
%<*readme>
____________________
The XePersian package
-v23.4
+v23.6
The xepersian package provides a convenient interface for
typesetting Persian and English texts in LaTeX, using XeTeX
@@ -31,7 +31,7 @@ also appear in the Discussions space under the Announcements
category.
-Current version release date: 2022/02/18
+Current version release date: 2022/02/20
___________________________________________
Vafa Khalighi
tex-persian@vafa.ac
@@ -168,7 +168,7 @@ by Vafa Khalighi.
\documentclass{ltxdoc}
\input{xepersian-doc-preamble.ltx}
\hypersetup{%
- pdftitle={Source documentation for version 23.4 of the XePersian
+ pdftitle={Source documentation for version 23.6 of the XePersian
package}%
}
\EnableCrossrefs
@@ -187,7 +187,7 @@ by Vafa Khalighi.
% \GetFileInfo{\jobname.dtx}
% \title{\textcolor{blue!65!black}{\Huge The \textsf{\XePersian} \textit{Package}}\\
%\LARGE Persian for \LaTeXe, using {\XeTeX} engine\\[5pt]
-%\large Source documentation for \textcolor{blue!65!black}{version 23.4}}
+%\large Source documentation for \textcolor{blue!65!black}{version 23.6}}
% \author{Vafa Khalighi\thanks{Thanks to the bidi-tex GitHub organization consisted of David Carlisle and other members of the {\LaTeX} team to maintain the package in the period 2018--2020.}\\
% \url{tex-persian@vafa.ac}}
%\setcounter{page}{0}
@@ -198,7 +198,7 @@ by Vafa Khalighi.
%\textcolor{blue!65!black}{\Huge The \textsf{\XePersian} \textit{Package}}
%
%\Large
-%Source documentation for \textcolor{blue!65!black}{version 23.4}}
+%Source documentation for \textcolor{blue!65!black}{version 23.6}}
%
%\vskip 0pt plus 3fill
%\textit{\large to my Master, \textbf{Ferdowsi The Great}}
@@ -246,7 +246,7 @@ by Vafa Khalighi.
%\iffalse
%<*table>
%\fi
-%% \CheckSum{10978}
+%% \CheckSum{11214}
%% \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
@@ -3477,8 +3477,8 @@ U+003E U+003E <> U+00BB ; >> -> RIGHT POINTING GUILLEMET
% \section{File \texttt{xepersian.sty}}
% \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
-\def\xepersian@version{23.4}
-\def\xepersiandate{2022/02/18}
+\def\xepersian@version{23.6}
+\def\xepersiandate{2022/02/20}
\ProvidesPackage{xepersian}[\xepersiandate\space v\xepersian@version\space
Persian typesetting in XeLaTeX]
\RequirePackage{fontspec}
@@ -3566,6 +3566,194 @@ Persian typesetting in XeLaTeX]
\newfontlanguage{Persian}{FAR}
\ExplSyntaxOn
+\cs_new:Nn \xepersian_set_family:Nnn { \__xepersian_set_family:NnnN #1 {#2} {#3} \tl_set_eq:NN }
+\cs_generate_variant:Nn \xepersian_set_family:Nnn {c}
+
+\cs_new_protected:Nn \__xepersian_select_font_family:nn
+ {
+ \group_begin:
+ \__fontspec_font_suppress_not_found_error:
+ \__fontspec_init:
+
+ \__fontspec_sanitise_fontname:Nn \l_fontspec_fontname_tl {#2}
+ \__fontspec_sanitise_fontname:Nn \l__fontspec_fontname_up_tl {#2}
+ \__fontspec_sanitise_fontname:Nn \l__fontspec_basename_tl {#2}
+
+ \__fontspec_if_detect_external:nT {#2}
+ { \keys_set:nn {fontspec-preparse-external} {Path} }
+
+ \keys_set_known:nn {fontspec-preparse-cfg} {#1}
+
+ \__fontspec_init_ttc:n {#2}
+ \__fontspec_load_external_fontoptions:N \l_fontspec_fontname_tl
+
+ \__fontspec_extract_all_features:n {#1}
+ \tl_set:Nx \l__fontspec_fontid_tl { \tl_to_str:N \l_fontspec_fontname_tl-:-\tl_to_str:N \l__fontspec_all_features_clist }
+
+
+ \__fontspec_preparse_features:
+ \__fontspec_load_font:
+ \__fontspec_set_scriptlang:
+ \__xepersian_get_features:n {}
+ \bool_set_false:N \l__fontspec_firsttime_bool
+
+ \__fontspec_save_family_needed:nTF {#2}
+ {
+ \__xepersian_save_family:nn {#1} {#2}
+ }
+ {
+ }
+ \group_end:
+
+ \tl_set_eq:NN \l_fontspec_family_tl \g__fontspec_nfss_family_tl
+ }
+
+\cs_new:Nn \__xepersian_set_family:NnnN
+ {
+ \tl_set:Nn \l__fontspec_fontface_cs_tl {\l_fontspec_font} % reset
+ \tl_set:Nn \l__fontspec_family_label_tl {#1}
+ \__xepersian_select_font_family:nn {#2} {#3}
+ \__fontspec_tl_new_if_free:N #1
+ #4 #1 \l_fontspec_family_tl
+ \tl_set:Nn \l__fontspec_fontface_cs_tl {\l_fontspec_font} % reset
+ }
+
+\cs_new:Nn \__xepersian_setup_nfss:Nnnn
+ {
+
+ \__xepersian_get_features:n { #2 , #3 , #4 }
+
+ \tl_if_empty:NF \l__fontspec_scale_tl
+ {
+ \tl_set:Nx \l__fontspec_scale_tl { s*[\l__fontspec_scale_tl] }
+ }
+
+ \tl_build_put_right:Nx #1
+ {
+ <\l__fontspec_size_tl> \l__fontspec_scale_tl
+ \__fontspec_construct_font_call:nn { \l_fontspec_fontname_tl }
+ { \l__fontspec_pre_feat_sclist \g__fontspec_rawfeatures_sclist }
+ }
+ }
+
+\cs_new:Nn \__xepersian_setup_single_size:nn
+ {
+ \tl_clear:N \l__fontspec_size_tl
+ \tl_set_eq:NN \l__fontspec_sizedfont_tl \l__fontspec_saved_fontname_tl % in case not spec'ed
+
+ \keys_set_known:nxN {fontspec-sizing} { \exp_after:wN \use:n #2 }
+ \l__fontspec_sizing_leftover_clist
+ \tl_if_empty:NT \l__fontspec_size_tl { \__fontspec_error:n {no-size-info} }
+
+ % "normal"
+ \__fontspec_load_fontname:Nn \l_fontspec_fontname_tl {\l__fontspec_sizedfont_tl}
+ \__xepersian_setup_nfss:Nnnn \l__fontspec_nfss_tl {#1} {\l__fontspec_sizing_leftover_clist} {}
+
+ % small caps
+ \clist_set_eq:NN \l__fontspec_fontfeat_curr_clist \l__fontspec_fontfeat_sc_clist
+
+ \bool_if:NF \l__fontspec_nosc_bool
+ {
+ \tl_if_empty:NTF \l__fontspec_fontname_sc_tl
+ {
+ \__fontspec_make_smallcaps:TF
+ {
+ \clist_put_left:Nn \l__fontspec_fontfeat_curr_clist {Letters=SmallCaps}
+ }
+ {
+ \bool_set_true:N \l__fontspec_nosc_bool
+ }
+ }
+ { \__fontspec_load_fontname:Nn \l_fontspec_fontname_tl {\l__fontspec_fontname_sc_tl} }% local for each size
+ }
+
+ \bool_if:NF \l__fontspec_nosc_bool
+ {
+ \__fontspec_setup_nfss:Nnnn \l__fontspec_nfss_sc_tl
+ {#1} {\l__fontspec_sizing_leftover_clist} {\l__fontspec_fontfeat_curr_clist}
+ }
+ }
+
+\cs_new:Nn \__xepersian_declare_shape:nnnn
+ {
+ \tl_build_begin:N \l__fontspec_nfss_tl
+ \tl_build_begin:N \l__fontspec_nfss_sc_tl
+ \tl_set_eq:NN \l__fontspec_saved_fontname_tl \l_fontspec_fontname_tl
+
+ \exp_args:Nx \clist_map_inline:nn {#4} { \__xepersian_setup_single_size:nn {#3} {##1} }
+
+ \tl_build_end:N \l__fontspec_nfss_tl
+ \tl_build_end:N \l__fontspec_nfss_sc_tl
+
+ \__fontspec_declare_shapes_normal:nn {#1} {#2}
+ \__fontspec_declare_shapes_smcaps:nn {#1} {#2}
+ \__fontspec_declare_shape_slanted:nn {#1} {#2}
+ \__fontspec_declare_shapes_bx:nn {#1} {#2}
+ \__fontspec_declare_shape_loginfo:nn {#1} {#2}
+ }
+\cs_generate_variant:Nn \__xepersian_declare_shape:nnnn {nnxx}
+
+\cs_new:Nn \__xepersian_make_font_shapes:Nnnnn
+ {
+ \group_begin:
+ \__fontspec_keys_set_known:nxN {fontspec-preparse-external} { #4 } \l__fontspec_leftover_clist
+ \__fontspec_load_fontname:Nn \l_fontspec_fontname_tl {#1}
+ \__xepersian_declare_shape:nnxx {#2} {#3} { \l__fontspec_fontopts_clist, \l__fontspec_leftover_clist } {#5}
+ \group_end:
+ }
+
+\cs_new:Nn \__xepersian_set_faces_aux:nnnnn
+ {
+ \fontspec_complete_fontname:Nn \l__fontspec_curr_fontname_tl {#3}
+ \__xepersian_make_font_shapes:Nnnnn \l__fontspec_curr_fontname_tl {#1} {#2} {#4} {#5}
+ }
+
+\cs_new:Nn \__xepersian_set_faces:
+ {
+ \__fontspec_add_nfssfont:nnnn \mddefault \shapedefault \l_fontspec_fontname_tl \l__fontspec_fontfeat_up_clist
+ \__fontspec_add_nfssfont:nnnn \bfdefault \shapedefault \l__fontspec_fontname_bf_tl \l__fontspec_fontfeat_bf_clist
+ \__fontspec_add_nfssfont:nnnn \mddefault \itdefault \l__fontspec_fontname_it_tl \l__fontspec_fontfeat_it_clist
+ \__fontspec_add_nfssfont:nnnn \mddefault \sldefault \l__fontspec_fontname_sl_tl \l__fontspec_fontfeat_sl_clist
+ \__fontspec_add_nfssfont:nnnn \mddefault \swdefault \l__fontspec_fontname_sw_tl \l__fontspec_fontfeat_sw_clist
+ \__fontspec_add_nfssfont:nnnn \bfdefault \itdefault \l__fontspec_fontname_bfit_tl \l__fontspec_fontfeat_bfit_clist
+ \__fontspec_add_nfssfont:nnnn \bfdefault \sldefault \l__fontspec_fontname_bfsl_tl \l__fontspec_fontfeat_bfsl_clist
+ \__fontspec_add_nfssfont:nnnn \bfdefault \swdefault \l__fontspec_fontname_bfsw_tl \l__fontspec_fontfeat_bfsw_clist
+ \prop_map_inline:Nn \l__fontspec_nfssfont_prop { \__xepersian_set_faces_aux:nnnnn ##2 }
+ }
+
+\cs_new:Nn \__xepersian_save_family:nn
+ {
+ \__fontspec_save_fontinfo:n {#2}
+ \__fontspec_find_autofonts:
+ \DeclareFontFamily{\g__fontspec_nfss_enc_tl}{\g__fontspec_nfss_family_tl}{}
+ \__xepersian_set_faces:
+ \__fontspec_info:nxx {defining-font} {#1} {#2}
+ }
+
+\cs_new:Nn \__xepersian_get_features:n
+ {
+ \__fontspec_init_fontface:
+ \__fontspec_keys_set_known:nxN {fontspec-renderer} {\l__fontspec_fontfeat_clist,#1}
+ \l__fontspec_keys_leftover_clist
+ \__fontspec_keys_set_known:nxN {fontspec} {\l__fontspec_keys_leftover_clist} \l__fontspec_keys_leftover_clist
+ \bool_if:NTF \l__fontspec_ot_bool
+ {
+ \keys_set:nV {fontspec-opentype} \l__fontspec_keys_leftover_clist
+ }
+ {
+ \bool_if:nT { \l__fontspec_atsui_bool || \l__fontspec_graphite_bool }
+ { \keys_set:nV {fontspec-aat} \l__fontspec_keys_leftover_clist }
+ }
+ \__fontspec_update_featstr:n { script = arab }
+
+ \tl_if_empty:NF \l__fontspec_mapping_tl
+ { \__fontspec_update_featstr:n { mapping = \l__fontspec_mapping_tl } }
+
+ \str_if_eq:eeF { \l__fontspec_hexcol_tl \l__fontspec_opacity_tl }
+ { \c__fontspec_hexcol_tl \c__fontspec_opacity_tl }
+ { \__fontspec_update_featstr:n { color = \l__fontspec_hexcol_tl\l__fontspec_opacity_tl } }
+ }
+
\keys_define:nn {fontspec-opentype}
{
Ligatures / PersianTeX .code:n = { \tl_set:Nn \l__fontspec_mapping_tl {persian-tex-text} },
@@ -3603,7 +3791,7 @@ Persian typesetting in XeLaTeX]
}
\cs_new:Nn \__xepersian_main_settextfont:nn
{
- \fontspec_set_family:Nnn \l__xepersian_persianfont_family_tl {Ligatures=PersianTeX,#1} {#2}
+ \xepersian_set_family:Nnn \l__xepersian_persianfont_family_tl {Ligatures=PersianTeX,#1} {#2}
\tl_set_eq:NN \rmdefault \l__xepersian_persianfont_family_tl
\use:x { \exp_not:n { \DeclareRobustCommand \persianfont }
{
@@ -3707,7 +3895,7 @@ Persian typesetting in XeLaTeX]
}
\cs_new:Nn \__xepersian_main_defpersianfont:NnnN
{
- \fontspec_set_family:cnn { l__xepersian_ \cs_to_str:N #1 _family_tl } {Ligatures=PersianTeX,#2} {#3}
+ \xepersian_set_family:cnn { l__xepersian_ \cs_to_str:N #1 _family_tl } {Ligatures=PersianTeX,#2} {#3}
\use:x
{
\exp_not:N #4 \exp_not:N #1 {}
@@ -3781,7 +3969,7 @@ Persian typesetting in XeLaTeX]
}
\cs_new:Nn \__xepersian_main_setpersiansansfont:nn
{
- \fontspec_set_family:Nnn \l__xepersian_persiansffamily_family_tl {Ligatures=PersianTeX,#1} {#2}
+ \xepersian_set_family:Nnn \l__xepersian_persiansffamily_family_tl {Ligatures=PersianTeX,#1} {#2}
\tl_set_eq:NN \persiansfdefault \l__xepersian_persiansffamily_family_tl
\use:x { \exp_not:n { \DeclareRobustCommand \persiansffamily }
{
@@ -3805,7 +3993,7 @@ Persian typesetting in XeLaTeX]
}
\cs_new:Nn \__xepersian_main_setpersianmonofont:nn
{
- \fontspec_set_family:Nnn \l__xepersian_persianttfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
+ \xepersian_set_family:Nnn \l__xepersian_persianttfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
\tl_set_eq:NN \persianttdefault \l__xepersian_persianttfamily_family_tl
\use:x { \exp_not:n { \DeclareRobustCommand \persianttfamily }
{
@@ -3829,7 +4017,7 @@ Persian typesetting in XeLaTeX]
}
\cs_new:Nn \__xepersian_main_setnavarfont:nn
{
- \fontspec_set_family:Nnn \l__xepersian_navarfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
+ \xepersian_set_family:Nnn \l__xepersian_navarfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
\tl_set_eq:NN \navardefault \l__xepersian_navarfamily_family_tl
\use:x { \exp_not:n { \DeclareRobustCommand \navarfamily }
{
@@ -3853,7 +4041,7 @@ Persian typesetting in XeLaTeX]
}
\cs_new:Nn \__xepersian_main_setpookfont:nn
{
- \fontspec_set_family:Nnn \l__xepersian_pookfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
+ \xepersian_set_family:Nnn \l__xepersian_pookfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
\tl_set_eq:NN \pookdefault \l__xepersian_pookfamily_family_tl
\use:x { \exp_not:n { \DeclareRobustCommand \pookfamily }
{
@@ -3877,7 +4065,7 @@ Persian typesetting in XeLaTeX]
}
\cs_new:Nn \__xepersian_main_setsayehfont:nn
{
- \fontspec_set_family:Nnn \l__xepersian_sayehfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
+ \xepersian_set_family:Nnn \l__xepersian_sayehfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
\tl_set_eq:NN \sayehdefault \l__xepersian_sayehfamily_family_tl
\use:x { \exp_not:n { \DeclareRobustCommand \sayehfamily }
{
@@ -3901,7 +4089,7 @@ Persian typesetting in XeLaTeX]
}
\cs_new:Nn \__xepersian_main_setiranicfont:nn
{
- \fontspec_set_family:Nnn \l__xepersian_iranicfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
+ \xepersian_set_family:Nnn \l__xepersian_iranicfamily_family_tl {Ligatures=PersianTeX,#1} {#2}
\tl_set_eq:NN \iranicdefault \l__xepersian_iranicfamily_family_tl
\use:x { \exp_not:n { \DeclareRobustCommand \iranicfamily }
{
@@ -4329,7 +4517,7 @@ Persian typesetting in XeLaTeX]
\newcommand*{\@xepersian@pdfcustomproperties}{%
This is the xepersian package, Version \xepersian@version,
- Released February 18, 2022.
+ Released February 20, 2022.
}
\newcommand\twocolumnstableofcontents{%
diff --git a/macros/xetex/latex/xepersian/xepersian.pdf b/macros/xetex/latex/xepersian/xepersian.pdf
index 0d96bb5f82..7dc3715995 100644
--- a/macros/xetex/latex/xepersian/xepersian.pdf
+++ b/macros/xetex/latex/xepersian/xepersian.pdf
Binary files differ