OpTeX

OpTeX - tips, tricks, howto

This page includes solutions of various tasks when OpTeX is used. Each solution would be ``small'', it means you can see whole solution at once in your www browser. The constellation of each solution is similar: user description followed by macro code followed by explanation of the macro code. User can copy and paste the macro code to his/her document simply by the mouse.

If there exists any tip from you, OpTeX user, don't hesitate please and send me it. I'll welcome it and save it here (with the author of the solution mentioned).

OPmac tricks can be used too but re-implementation of the code is probably needed because OPmac uses different internal macros than OpTeX. The important OPmac tricks will be re-implemented here soon.

Note that several OPmac tricks are implemented directly into OpTeX:

  • \colordef does colors mixing.
  • \morecolors reads color names from xcolor package.
  • \transformbox applies linear transformations.
  • \oval, \circle create colored oval or circle around text.
  • \clipinoval, \clipincircle declare clipping path for images.
  • \hisyntax performs syntax highlighting for C, Python, TeX, html and XML languages.
  • \draft and \showlabels can be used for printing labels in draft mode.
  • Options in key=value dictionaries.
  • \lipsum prints the text Lorem ipsum dolor sit.
  • \mathbox creates a \hbox in math list with size-context.
  • \numberedpar helps with creating numbered Theorems, Definitions etc.
  • \inspic syntax allows alternative \inspic{filename.ext} without space separator.
  • \inkinspic inludes pictures with labels generated by Inkscape.
  • \crlp is implemented for tables.
  • Tables to given width.
  • Variations of paragraphs with p declarator.
  • Vertical centered text in more rows in tables.
  • Eqbox: equal width of boxes across whole document.
  • \slides style allows to create presentations.
  • \usebib reads directly .bib databases without any external program.
  • Index allows hyperlinked pages and alternative page lists.

Contents



Fonts

Micro-typography: font expanding, hanging punctuation

LuaTeX supports "microtypography features": slight font expanding when the paragraph line is stretched and hanging punctuation. You can enable this using primitives \pdffontexpand+\adjustspacing and \rpcode,\lpcode+\protrudechars. Example:

\pdffontexpand\_tenrm 30 20 5   % font expanding configured for \_tenrm
\adjustspacing=2                % font expandnig activated

\chardef\hyph=\hyphenchar\_tenrm
\rpcode\_tenrm\hyph=310  \rpcode\_tenrm`\.=200 \rpcode\_tenrm`\,=200
\lpcode\_tenrm`\“=400 \rpcode\_tenrm`\”=400  % hanging punctuation configured for \_tenrm
\protrudechars=2                             % hanging puctuation activated

See TeX in a Nutshell, page 23 for more information about parameters of used primitives.

You can start from this example and modify it for your needs.

Note that the example above has its limitations, see this comment.

You can use mte.opm package where the data for microtypographic extensions are already prepared and ready for use.

(0058) -- P. O. 2020-04-16


Text size changed to the desired width

We prepare the macro \scaleto size {text} which prints “text” in the current font, but rescaled so that the width of “text” is the given size.

\def\scaleto#1#{\def\tmp{#1}\scaletoA}
\def\scaletoA#1{\setbox0=\hbox{{#1}}%
   \edef\tmp{\expr{\bp{\tmp}/\bp{\wd0}}}%
   \transformbox{\pdfscale{\tmp}{\tmp}}{#1}}

\scaleto 7cm {text} % the "text" has 7cm width 

The desired dimension is saved to the \tmp macro and \scaletoA does the real work. It saves the text to \hbox 0 and calculate the coefficient of scaling as \tmp/\wd0. This coefficient is saved to \tmp again and used in the \transformbox{\pdfscale}{} macro.

You can compare the solution of similar task in OPmac trick 027. You can see that we have more powerful tools for calculation than in OPmac: \bp converts the dimension to Adobe points and \expr calculates the ratio.

If you are using a font family with optical sizes, then we need to scale to right optical size. Then we can use \scaletof size {text}.

\def\scaletof#1#{\def\tmp{#1}\scaletofA}
\def\scaletofA#1{\setbox0=\hbox{{#1}}%
   \edef\tmpa{\expr{\bp{\tmp}/\bp{\wd0}}}%
   \scaletoA{\setfontsize{mag\tmpa}\currvar#1}}

The \setfontsize{mag factor} is calculated, where the factor is the scale ratio calculated and saved in the \tmpa. When the font is changed then the result is not exactly the desired width, because new font have a little different proportions. So previous \scaletoA is called again to reach the exact accuracy.

(0011) -- P. O. 2020-05-06


More than 1300 icons from Awesome5 fonts

The TeX distribitions include OTF fonts FontAwesome5Free-Solid-900, FontAwesome5Brands-Regular-400 and FontAwesome5Free-Regular-400 with icons (dingbats) like keyboard, house, faces etc. See the documentation from fontawesome5 LaTeX package where all these dingbats are listed. We want to use them. The following macro code does it:

\initunifonts  % we need to load OTF fonts
\font\tenfafree      =[FontAwesome5Free-Solid-900]
\font\tenfabran      =[FontAwesome5Brands-Regular-400]
\font\tenfafreeregul =[FontAwesome5Free-Regular-400]
\protected\def\faregul#1{{\let\tenfafree=\tenfafreeregul #1}}
\protected\def\fafree{\tenfafree\resizethefont}
\protected\def\fabran{\tenfabran\resizethefont}
\def\__fontawesome_def_icon:nnnnn#1#2#3#4#5{%
   \ifx^#1^\else
      \getfourtokens\tmp#3\relax 
      \protected\edef#1{{\csname fa\tmp\endcsname \char#5}}%
   \fi
}
\def\getfourtokens#1#2#3#4#5#6\relax{\def#1{#2#3#4#5}}
\input fontawesome5-mapping.def
\__fontawesome_def_icon:nnnnn{\faWifi}{wifi}{free3}{213}{"F1EB}

Now, you can use all control sequences listed in the fontawesome5 documentation. For example \faHouseUser, \faAngry, \faApple, etc. The alternative syntax like \faIcon{Angry} is not supported. A few icons are in its "regular" variants. They are listed as \faAngry[regular] in the fontawesone5 documentation. These icons are available by the \faregul prefix in uour macro, i.e. \faregul\faAngry.

Notice to the implementation. The three fonts with dingbats are loaded as \tenfafree, \tenfabran and \tenfafreeregul. The third one includes the "regular" variants. The macros \fafree amd \fabran select these fonts at current size used by Font Selection System from OpTeX.

We need to read the mapping from names to the font codes. It is saved in the fontawesome5-mapping.def. The set of \__fontawesome_def macros are used here. We define such macro in order to read this mapping and then we do \input fontawesome5-mapping.def.

Of course, if Marcel Krüger make changes in the fontawesome5-mapping.def file syntax, we must to redeclare our macros. I hope that this syntax is more or less stable.

You can compare the complexity of macros in the LaTeX implemenation with these 16 lines of plain TeX macros. Only TeX primitives are used here (and two OpTeX macros \initunifonts and \resizethefont). This is one of reasons why I like plain TeX.

(0012) -- P. O. 2020-05-08


Emoji characters used directly in text

We want to write:

\fontfam[lm]

Is it OK? 👍 Yes.

and the current font is used for the text, but emoji font is used for the emoticon. The following code implements this.

\initunifonts
\addto\_fontfeatures{fallback=emoji;}\_reloading
\directlua{luaotfload.add_fallback ("emoji", {"TwemojiMozilla:+colr;"} )}
\rm

The "fallback" font feature is used and set globally for all fonts. This feature loads and uses fonts from given font list (declared by the luaotfload.add_fallback Lua function) if the character is missing in the current font. The falback font is loaded at the same size as the current font.

Note: we need not to load emoji package.

(0062) -- P. O. 2021-04-25


Fonts with non-Unicode encoding

OpTeX does not prefer such fonts but sometimes there are a reason to use them. For example the font slabikar (with hand written letters continuously binded one with other) was created in 1997 in pre-Unicode epoch. We can use such font if the following file is prepared:

\def\charenc #1 #2 {\catcode`#1=13 \bgroup \lccode`\~=`#1 \lowercase{\egroup \chardef~=#2}}

\def\csfenc{%
   \charenc á 225 % a-acute
   \charenc Á 193 % A-acute
   \charenc ä 228 % a-diaeresis
   \charenc Ä 196 % A-diaeresis
   \charenc č 232 % c-caron
   \charenc Č 200 % C-caron
   ...
   etc.
}

See csf-enc.tex for full version of such file. Then you can use it:

\input csf-enc
\pdfmapline{=slabikar slabikar ≤slabikar.pfb}
\font\s=slabikar at20pt

{\s\csfenc Tady píšu fontem slabikář česky.}
\bye

The letters are set as active but they can be used in \edef or \write without expanding them because their meanig is not macro but chardef constant.

We don't want to fall to the encodings hell. So, such encoding file is here only as an example. It can be a part of an old non-Unicode encoded fonts, but such files will be never the part of OpTeX package. The hyphenation patterns cannot work properly when such fonts are used.

(0018) -- P. O. 2020-05-25


Marking parts of text

We can use standard marking by {\it italic}, {\bf bold}, {\bi bold italic} or {\em emphasized text}. If you want to do something more special, then the following OPmac macros work without any change in OpTeX:

  • \ul{text} for underlining text (splittable to more lines) like in the soul LaTeX package, see OPmac trick 0063
  • Overlining can be done by the same macro when \uline macro is redefined.
  • Leterspacing referred in OPmac trick 0063 need not be done by this macro because we have more robust letterspacing implemented as font feature, se section 2.13.10 in the OpTeX documentation.
  • \hyphenprocess from OPmac trick 0065 can be used to implement hyphenation feature to the \ul macro.
  • Background colored (splitabble to more lines) text by OPmac trick 0085 can be printed. Use \coltext\ColorA\ColorB{text}.
  • See also the following trick (OpTeX trick 0064) for information about achieving font effects with LuaTeX attributes.

(0044) -- P. O. 2021-02-08


Font effects using attributes

You can set printing outlines of characters directly and simply by

\pdfliteral{1 Tr .3 w}TEXT\pdfliteral{0 Tr 0 w}

but it cannot span more pages and it does not respect TeX grouping. There is another possibility shown in this trick.

Setting of color is based on attributes in OpTeX v1.04+. We can use similar mechanism to achieve other font/rule effects, with different PDF graphics operators. For example we can achieve transparency or font outlines this way:

Normal, {\transparency{.5} half transparent,
\Red{\transparency{.2}red and more transparent,} back to half,} back to normal.

Normal, {\outlinefont{.15}outlined}, {\outlinefont{.3}more outlined}.

which produces:

Text with transparency and outlines

To achieve these effects we must first introduce a Lua mechanism:

\directlua{
local node_id  = node.id
local glyph_id = node_id("glyph")
local rule_id  = node_id("rule")
local glue_id  = node_id("glue")
local hlist_id = node_id("hlist")
local vlist_id = node_id("vlist")
local disc_id  = node_id("disc")

local direct       = node.direct
local todirect     = direct.todirect
local tonode       = direct.tonode
local getfield     = direct.getfield
local setfield     = direct.setfield
local getlist      = direct.getlist
local setlist      = direct.setlist
local getleader    = direct.getleader
local getattribute = direct.get_attribute
local insertbefore = direct.insert_before
local copy         = direct.copy
local traverse     = direct.traverse

local token_getmacro = token.get_macro

local pdfliteral = optex.directpdfliteral

function register_pre_shipout_injector(name, attribute_name, namespace, default)
    local current
    local default = default or 0
    local attribute = assert(registernumber(attribute_name))
    local function injector(head)
        for n, id, subtype in traverse(head) do
            if id == hlist_id or id == vlist_id then
                % nested list, just recurse
                setlist(n, injector(getlist(n)))
            elseif id == disc_id then
                % only replace part is interesting at this point
                local replace = getfield(n, "replace")
                if replace then
                    setfield(n, "replace", injector(replace))
                end
            elseif id == glyph_id or id == rule_id
                    or (id == glue_id and getleader(n)) then
                local new = getattribute(n, attribute) or 0
                if new ~= current then
                    local literal = token_getmacro(namespace..new)
                    head = insertbefore(head, n, pdfliteral(literal))
                    current = new
                end
            end
        end
        return head
    end

    callback.add_to_callback("pre_shipout_filter", function(list)
        current = default
        return tonode(injector(todirect(list)))
    end, name)
end
}

Then we can define the font effects as we please:

Transparency:

\newattribute \transpattr
\newcount \transpcnt \transpcnt=1 % allocations start at 1
\def\transparency#1{\inittransparency \transpattr=
   \ifcsname transp::#1\endcsname \lastnamedcs\relax \else
      \transpcnt
      \sxdef{transp::#1}{\the\transpcnt}%
      \sxdef{transp:\the\transpcnt}{/Tr\the\transpcnt\space gs}%
      \addextgstate{/Tr\the\transpcnt <</ca #1 /CA #1>>}%
      \incr \transpcnt
   \fi
}
\addto\_resetcolor{\transpattr=-"7FFFFFFF }
% Transparency of "1" is the default
\sdef{transp::1}{0}
\sdef{transp:0}{/Tr0 gs}
\def\inittransparency{%
   \addextgstate{/Tr0 <</ca 1 /CA 1>>}%
   \glet\inittransparency=\relax
}
\directlua{
register_pre_shipout_injector("transp", "transpattr", "transp:")
}

Font outline:

\newattribute \fntoutattr
\newcount \fntoutcnt \fntoutcnt=1 % allocations start at 1
\def\outlinefont#1{\fntoutattr=
   \ifcsname fntout::#1\endcsname \lastnamedcs\relax \else
      \fntoutcnt
      \sxdef{fntout::#1}{\the\fntoutcnt}%
      \sxdef{fntout:\the\fntoutcnt}{#1 w 1 Tr}%
      \incr \fntoutcnt
   \fi
}
\addto\_resetcolor{\fntoutattr=-"7FFFFFFF }
\sdef{fntout:0}{0 w 0 Tr}
\directlua{
register_pre_shipout_injector("fntout", "fntoutattr", "fntout:")
}

The core idea is the same as with colors (see the OpTeX documentation) – we use attributes to mark typesetting material and just before shipout we postprocess, injecting PDF literals where necessary. But unlike with colors, which are specialized (e.g. stroke vs non-stroke), we define a generic Lua function generator that can inject any PDF literals we want.

Each distinct transparency / font outline width maps to a single number that is used as the attribute value. Similarly, the inverse mapping maps the attribute values to PDF literals. Special attribute value of “0” designates the default effect – this is e.g. what returns the graphics state to normal after TeX group ends. Other effects are allocated starting at number 1.

The TeX user interface consists of \outlinefont and \transparency, they essentially just set the attribute to the right attribute value (allocating new one if necessary). \transparency has a little more work to do, because it has to initialize a new graphics state for each transparency – even the default one (this happens the first time transparency is actually used, apart from other things it allows detection of collision with TikZ).

We also add to \_resetcolor which is what the default OpTeX output routine uses to achieve clean slate for headers / footers.

Name of the effect, the attribute name and the macro prefix for PDF literals are passed to the Lua side in order to generate the associated PDF literal injector.

The literals for transparency activate the prepared graphics states (which are named “/Tr⟨number”. Literals for font outlines activate the outline style (“1 Tr”) and set the outline width with “number⟩ w”.

If you want to use other default value of an effect, you need to change the mapping of attribute value 0 and also need to force inject a PDF literal on the start of each page (this is normally not done, because it would be wasteful). The first can be easily done by using a couple of \sdefs. The second can be achieved by passing an invalid attribute value (e.g. -1) as the initial (which forces the initial injection no matter what).

For example to achieve half transparency for whole document, we change a few definitions:

% Transparency of ".5" is the default
\sdef{transp::.5}{0}
\def\inittransparency{%
   \addextgstate{/Tr0 <</ca .5 /CA .5>>}%
   \glet\inittransparency=\relax
}
\directlua{
% -1 forces initial setting on each page
register_pre_shipout_injector("transp", "transpattr", "transp:", -1)
}

Sadly outline fonts don't work with colors.

(0064) -- M. Vlasák 2021-07-14


Lists

List items multi-numbered at arbitrary level

We declare the \style m (multi-numbered) of the list. The items are numbered similarly as sections, i.e. 1., 2. 2.1, 2.2, 2.2.1, 2.2.2, 2.2.3 etc. We create macro \keepstyle which propagates the current style to all sub-levels. So:

\begitems \style m \keepstyle  % prints:
* First                        % 1. First
* Second                       % 2. Second
  \begitems
  * Second-A                   %   2.1 Second-A
  * Second-B                   %   2.2 Second-B
  \enditems
* Third                        % 3. Third
\enditems

The implementation:

\def\iprefix#1{}
\addto\_setlistskip{\ifnum\ilevel>1 \edef\iprefix{\iprefix.\the\itemnum}\fi}
\sdef{_item:m}{\iprefix.\the\itemnum. }
\def\keepstyle{\_defaultitem=\_printitem}

The \iprefix is saved at the start of \begitems (in the \_setlistskip macro) and it is used in deeper levels of lists. There can be arbitrary nesting levels.

(0047) -- P. O. 2021-02-17


Depth of the list given by number of *

We daclere the macro \easylist which enables to give the list level simply by the number of * used as a prefix. We need not to specify nesting \begitems...\enditems. So, if we use the \style m and \keepstyle from previous OpTeX trick 0047, then the following input:

\begitems \easylist \style m \keepstyle
* First proposition.
** Interesting comment.
*** A note on the comment.
*** Another note.
**** By the way...
***** This is a subsub...-proposition.
* Let’s start something new...
\enditems

gives exactly the same output as documented at the page 2 of the LaTeX package easylist (see texdoc easylist).

The implementation:

\def\countlist{\tmpnum=1 \countlistA}
\def\countlistA{\futurelet\next\countlistB}
\def\countlistB{\ifx\next\aast \ea\countlistC\else \ea\countlistD \fi}
\def\countlistC#1{\incr\tmpnum \countlistA}
\def\countlistD{%
   \ifnum\tmpnum>\ilevel \fornum \ilevel..\tmpnum-1 \do{\_begitems\easylist}\else
   \ifnum\tmpnum<\ilevel \fornum \tmpnum..\ilevel-1 \do{\_enditems}\fi\fi
   \_startitem}
\def\enditems{\fornum 1..\ilevel \do{\_enditems}}

Another application. We set given style for each used level:

\everylist={\ifcase\ilevel\or \style X \or \style x \else \style - \fi}
\begitems \easylist
* Main one
* Main two
** sub-item
*** sub-sub-item
\enditems

(0048) -- P. O. 2021-02-17


Verbatim

Code blocks like in Markdown

Code blocks are frequently used in Markdown. They are displayed as an auto-sized, shaded, rounded-corner box around a word. We can redefine the \_printinverbatim macro in order to do it:

\def\_printinverbatim#1{%
   \ovalparams{\lwidth=0pt \lcolor=\LightGrey \fcolor=\LightGrey}%
   \inoval{\setbox0=\hbox{#1}\ht0=1.35ex \dp0=.15ex \box0}}
\verbchar`

This `text` is printed as a `code& {block}`.

Code blocks

The \inoval macro is used for shaded rounded-corner box. The \vphantom{ly} gives a strut inside the text. The height and depth of the text is set to the constant values in order all ovals have the same height plus depth.

(0009) -- P. O. 2020-05-02


Inline verbatim in macro parameters

We know that inline verbatim in maro parameters does not work:

\def\p#1{print: "#1"}
\verbchar`

\p{test: `\relax x~&` fin.} % error (misplaced align tab)

The reason of this error: the parameter text is tokenized when the parameter is read. The first ` is run after this parameter is completely read. It changes catcodes but nothing is read directly from file at this time, so such catcode setting is ineffective.

We know that the usage of \code{...} is 100% working, but we must escape each TeX sensitive character: \p{test: \code{\\relax x\~\&} fin.}.

There is a method for "protecting" the macro parameter without escaping the TeX sensitive characters. Use \verbi as a prefix before usage of the macro:

\def\verbi#1{\def\tmp{#1}\begingroup \verbC \verbG}
\def\verbii#1#2#{\def\tmp{#1#2}\verbiiA}
\def\verbiiA#1{\addto\tmp{{#1}}\begingroup \verbC \verbG}
\def\verbC{\catcode`\\=12 \catcode`\#=12 \catcode`\%=12 }
\def\verbG#1{\endgroup \tmp{\scantextokens{#1}}}

\verbchar`
\def\p#1{print: "#1"}

\verbi\p{aha `\relax!@#$%^& uff~` mluff}

\verbii\table{cc}{ a & `\table` \cr b & `&` }

The \verbii can be used before \table macro or similar macros where the second parameter is important.

Implementaton note: We read the parameter first with category codes set by \verbC. The \endgroup in the \verbG restores the original category codes and the macro parameter is represented by \scantextokens{#1}. When this parameter is processed then the current category codes are used again. The \verbii macro read parameters like \verbii\table pxto10cm{cc}{...}. This is the reason of the existence of the \verbiiA auxiliary macro.

This solution doesn't work in special cases when there are unpaired braces {} in the verbatim parameter and with macros where the scanned parameter is pre-processed. Only \code{...} is 100% working.

Compare the complexity of the cprotect.sty LaTeX package with this 5-line solution.

(0017) -- P. O. 2020-05-22


Graphics

Color gradients

We can use \input tikz and color gradients support from this package. But there is another approach: create a color gradient in an interactive vector editor (Inkscape, for example) and use it. You can create arbitrary rectangle with color gradient in it and save such image to (say) my-gradient.pdf.

Then you can draw a rule with 1pt thickness of this gradient by

\hbox{\picwidth=\hsize \picheight=1pt \inspic{my-gradient.pdf}}

If you want to use gradients to another graphic objects, then it may be more complicated. For example gradiented oval boundary with text in it:

\newdimen\ovalw  \newdimen\ovalh
\ovalw=8cm \ovalh=15cm

\def\gradientoval{\clipinoval \dimexpr\ovalw/2 \dimexpr\ovalh/2 \ovalw \ovalh
   {\rlap{\picwidth=\ovalw \picheight=\ovalh \inspic{my-gradient.pdf}}%
   \raise5mm\hbox{\kern1mm
   \inoval[\roundness=4mm \fcolor=\White \lcolor=\White]
   {\raise\dimexpr\ovalh-1cm\hbox to\dimexpr\ovalw-1cm{}}}}}

\def\textingradientoval#1{\hbox{\rlap{\gradientoval}\kern5mm
   \raise\dimexpr\ovalh-1cm\vtop{\hsize=\dimexpr\ovalw-1cm\noindent #1}}}

\textingradientoval{\lorem[1]}

The \clipinoval is used first with my-gradient.pdf. Then a slightly smaller white oval is drawn over it. Only the boundary remains visible. Finally, the text is printed in it.

(0024) -- P. O. 2020-06-05


Curved arrows

We prepare the macro

\arrowcc x0 y0 {cx0 cy0 cx1 cy1} x1 y1 (dx1 dy1) {Text}

which draws the curve from x0 y0 to x1 y1 ended by arrow spike. The part {cx0 cy0 cx1 cy1} can be empty, i. e. {}. The line is drawn in such case. The numbers {cx0 cy0 cx1 cy1} give the control points of a Bézier curve. At the end of the arrow is printed the "Text" shifted by dx1 dy1. The numbers cx0 cy0 cx1 cy1 x1 y1 are relative to the origin x0 y0. This origin is relative to the current typesetting point. All numbers are in bp units by default. The macro \arrowccparams can include the additional settings (color, line width, etc.). The macro \arrowccspike includes the drawing of the arrow spike and you can redefine it.

\vglue5cm
\def\arrowccparams{1 0 0 rg 1 0 0 RG}  % red color is initialized

Pokus \arrowcc 0 10 {-30 20 -30 50} 20 50 (3 -3) {Text 1}
dále \arrowcc 0 -3 {} 40 -30 (3 -3) {Text 2}
a ještě \arrowcc 0 2 {10 20 20 30} 40 40 (3 -2) {Text 3}.

creates:

Code blocks

The implementation is exacltly the same as in OPmac trick 0062 but the calculation of direction of the spikes is re-implemented using more simple and straightforward lua code.

\def\arrowccspike{2 0 m -5 2 l -5 -2 l h f}
\def\arrowcc #1 #2 #3 #4 #5 (#6)#7{%
   \if^#3^\preparerotdata(0 0) (#4 #5)\else \preparedirection #3 (#4 #5)\fi
   % x0 y0 {cx1 cy1 cx2 cy2} x1 y1 (dx1 dy1) {Text}
   \pdfsave\rlap{\pdfliteral{%
   .7 w \arrowccparams\space 1 0 0 1 #1 #2 cm 0 0 m
   \if ^#3^#4 #5 l \else #3 #4 #5 c \fi S
   1 0 0 1 #4 #5 cm  q \rotdata\space 0 0 cm \arrowccspike\space Q}%
   \if^#7^\else\pdfliteral{1 0 0 1 #6 cm}\hbox{#7}\fi}\pdfrestore
}
\def\preparedirection #1 #2 #3 #4 {\preparerotdata(#3 #4) }
\def\arrowccparams{}

\def\preparerotdata (#1 #2) (#3 #4){\edef\rotdata{\rotcm (#1 #2) (#3 #4)}}
\def\rotcm (#1 #2) (#3 #4){\directlua{%
   local x=(#3)-(#1)
   local y=(#4)-(#2)
   local norm = math.sqrt(x*x+y*y)
   if norm==0 then tex.print('1 0 0 1')
   else tex.print(string.format('\_pcent.3f \_pcent.3f \_pcent.3f \_pcent.3f',%
                                 x/norm, y/norm, -y/norm, x/norm))
   end
}}

The macro \rotcm takes vector (#3,#4)-(#1,#2) and prepares cm matrix data for rotation in the direction of given vector. The data is stored in \rotdata macro using \preparerotdata (for lines) or \preparedirection (for Bézier curves). Then \rotdata is used inside \pdfliteral parameter.

(0037) -- P. O. 2021-02-05


Text around a circle

We create a macro \circletext {radius}{angle}{TEXT}{correction} which prints TEXT around a circle with given radius. First letter of the TEXT starts at given angle. The fourth parameter declares a correction between letter pairs because the standard kerning table is deactivated when printing around circle. The TEXT runs clockwise when the radius is positive (the center of the circle is below the letters). When the radius parameter is negative then TEXT runs anticlockwise and the center of the circle is above the letters. The macro creates a typesetting material with zero dimensions, the center of the circle is at the current typesetting point. For example

Code blocks

\hbox{%
\circletext {1.7cm} {212}  {{$\bullet$} UNIVERSITAS CAROLINA PRAGENSIS {$\bullet$}}
                           {\spaceskip=.5em \kpcirc TA{-.1}\kpcirc NA{-.05}}
\circletext {-1.7cm} {237} {Facultas MFF}
                           {\kpcirc Fa{-.1}}
}

The fourth parameter correction can include the declaration of word space (using \spaceskip=...) and the space corrections between declared pairs of letters using \kpcirc AB{num}: \kern num (in em units) is inserted between each pair of letters AB. Note that the example includes a "composed object" – $\bullet$. Such object must be enclosed by braces.

Letterspacing can be set by \def\circletextS{\kern value}. No letterspacing is set by default.

The \circletext macro can be defined by

\def\circletext#1#2#3#4{\hbox\bgroup
    \edef\tmpc{\expr{-57.295779/\bp{#1}}}% -(Pi/180)/R
    \setbox0=\hbox{\ifdim#1<0pt X\fi}% lap letters by X height if R<0
    \def\l{0}\baselineskip=#1 \advance\baselineskip by-\ht0 \lineskiplimit=-\maxdimen
    \edef\tmpa{\expr{\ifdim#1<0pt \else-\fi90+#2}}%
    \def\tmpb{{}#3}\replstring\tmpb{ }{{ }}#4% spaces => {spaces}
    \pdfsave \pdfrotate{\tmpa}%
    \expandafter\circletextA\tmpb\relax
}
\def\circletextA#1{\ifx#1\relax\pdfrestore\egroup\ignorespaces\else
    \ifx^#1^\else \setbox0=\hbox{#1\circletextS}%
       \ismacro\l{0}\iffalse
          \edef\l{\expr{\l+\bp{.5\wd0}}}%
          \pdfrotate{\expr{\tmpc*\l}}%
       \fi
       \edef\l{\bp{.5\wd0}}%
       \vbox to0pt{\vss\hbox to0pt{\hss#1\hss}\null}%
    \fi
    \expandafter\circletextA\fi
}
\def\kpcirc#1#2#3{\replstring\tmpb{#1#2}{#1{\kern#3em}#2}}
\def\circletextS{}

The code from OPmac trick 0109 is re-implemeted here. The calculation is done by \expr and \bp macros. The \l macro means the lenght of typeset text and \tmpc is coefficinet which converts such length to the angular measure in degrees.

(0038) -- P. O. 2021-02-05


Ignoring pictures when \inspic is processed

Pictures may take more size in the resulting PDF. We may want to disable their loading when a draft of our document is created. Or we may want to sent the source code to a colleague for experimenting, but without picture files. Both cases can be solve by \ignoreinspic macro. When it is used at the beginning of the document then all pictures loaded by \inspic have following features:

  • If the given picture file exists then \inspic loads it only in order to get its natural sizes (height and width) but the picture is not printed. The gray rectangle with the same sizes (calculated from \picwidth, \picheight values and from the natural sizes) is printed instead and the name of the picture file is appended. The real picture is not embedded to the PDF file, so the file keeps its small size.
  • If the given picture file does not exist then the virtual natural sizes of a non-existent image are taken from \nopicw and \nopicratio macros. The \nopicw gives the natural width and the \nopicratio gives the ratio width/height. The real sizes are calculated from \picwidth, \picheight and the virtual natural sizes (as \inspic normally does it) and a gray rectangle is printed. The text "NONE: file-name" is appended, where the file-name is the non existent picture file name given by the argument of \inspic.

Implementation:

\def\nopicw{5cm}
\def\nopicratio{1.7}

\let\inspicBori=\_inspicB
\def\inspicBnew#1{%
   \isfile{#1}\iftrue
      \edef\filename{\the\picdir#1}%
      \setbox0=\hbox\bgroup\inspicBori{#1}% \egroup is in \inspicBori
   \else
      \edef\filename{NONE: \the\picdir#1}%
      \ifdim\picheight=0pt \picheight=\expr{1/\nopicratio}\picwidth \fi
      \ifdim\picwidth=0pt \picwidth=\nopicratio\picheight \fi
      \ifdim\picwidth=0pt \picwidth=\nopicw\relax \picheight=\expr{1/\nopicratio}\picwidth \fi
      \setbox0=\hbox to\picwidth{\vbox to\picheight{}\hss}%
   \fi
   {\LightGrey \vrule height\ht0 width\wd0\Black\raise1ex\llap{\filename\ }}\egroup
}
\def\ignoreinspic{\let\_inspicB=\inspicBnew}

The internal \_inspicB macro is re-defined when \ingnoreinspic is used. It loads the image to box0 (using the orginal \inspic macro) and uses only its dimensions. The contents of box0 is thrown away when the group is left. Note, that \inspic starts with \hbox\bgroup, so the final \egroup leaves its group. When the file does not exists then more calculatons are done and box0 is constructed with calculated dimensions.

(0053) -- P. O. 2021-04-04


Math

\bbchar from AMS fonts when Unicode math font is used

When \fontfam[] is used and \noloadmath is not declared then a relevant math font from Unicode math fonts is loaded too. The single font includes more math alphabets together: math italics, roman, calligraphic, fractur, greek letters, bbchars etc.

Maybe, your opinion about ideal black board letters (\bbchar's) is influneced by the traditional AMS fonts and it is different than the opinion of font designer of the used Unicode math font (Latin Modern math font is typical example of this case). Then you can return to the AMS \bbchars using the following code after the first \fontfam[] command loads the Unicode math font.

\addto\_normalmath{\_loadmathfamily 5 msbm }
\addto\_boldmath{\_loadmathfamily 5 msbm }
\def\bbchar{\fam5 \_rmvariables}

We need to add \_loadmathfamily 5 to \_normalmath and \_boldmath in order to load 8 bit version of math fonts as family 5 (5 is the first unused math family when Unicode math font is loaded). Then we re-define \bbchar macro as family5 selector and mathcodes of letters must be "normal" (not Unicoded) when this 8bit font is used (this is done by the \_rmvariables macro).

(0001) -- P. O. 2020-04-09


More comfortable writing of matrices

It is quite strenuous to put the characters & between every item of a matrix. In particular, when we are writing a huge amount of matrices. But we can simplify our writing (and reading of the source text too) using the \replstring macro. We create the \x macro which can be used as a prefix before macros such as \matrix and \pmatrix. If it is used then user can write spaces instead of ampersands.

\def\x#1#2{\def\tmpb{#2}\replstring\tmpb{ }{&}\_ea#1\_ea{\tmpb}}

$$
  \x\pmatrix{x_1 x_2 x_3\cr 12 11 10} = \left[\x\matrix{12 11 10\cr y_1 y_2 y_3}\right]
$$

(0010) -- P. O. 2020-04-09


Bigger outer parentheses automatically set

Sometimes we need to typeset something like f(b+((c+d)(e+f))\cdot g) and we want to set outer parentheses bigger than the inner ones because this is a traditional typographical rule. We can write $f\Bigl(b+\bigl((c+d)(e+f)\bigr)\cdot g\Bigl)$ in our TeX source, but this makes the source less clear. The macro \biglr defined here is able to do such a task automatically. You can write:

$\biglr f(b+((c+d)(e+f))\cdot g) \biglr$

All material between the \biglr pair is scanned by TeX and the appropriate \bigl, \bigr, \Bigl etc. are added before parentheses () automatically.

\def\biglr#1\biglr{%
   \def\biglrL{}\tmpnum=0 \def\biglrM{0}%
   \def\tmpb{#1}\replstring\tmpb{(}{\biglrC(}\replstring\tmpb{)}{\biglrC)}%
   \ea\biglrA\tmpb\biglrC.%
}
\def\biglrA#1\biglrC#2{\addto\biglrL{#1}%
   \ifx.#2\tmpnum=\biglrM\relax \biglrL
   \else \addto\biglrL{\biglrC#2}%
         \ifx(#2\advance\tmpnum by1 \ifnum\biglrM<\tmpnum \edef\biglrM{\the\tmpnum}\fi\fi
         \ifx)#2\advance\tmpnum by-1 \fi
         \ea \biglrA\fi}
\def\biglrC#1{\ifx(#1\advance\tmpnum by-1 \biglrD(\fi
              \ifx)#1\biglrE)\advance\tmpnum by1 \fi}
\def\biglrD{\ifcase\tmpnum \or\ea\bigl\or \ea\Bigl\or \ea\biggl \else \ea\Biggl\fi}
\def\biglrE{\ifcase\tmpnum \or\ea\bigr\or \ea\Bigr\or \ea\biggr \else \ea\Biggr\fi}

Note that the \biglr scanner is active only at outer level of braces {}. And it is not usable when you are using fractions or other big objects inside parentheses in display style. Use \left, \right primitives in such situations.

(0026) -- P. O. 2020-06-30


Text fonts in math mode

When you write 13 or $13$ then the digits are (typically) rendered from different fonts: from text font in first case and from math font in second one. Sometimes you want to use only text fonts in both cases. Suppose that you have a text font without visually compatible math font. Then you may want to use a-z, A-Z, 0-9 and some other characters from text font in math mode instead from math font. The following trick does this (compare this with the mathastext.sty package used in LaTeX).

We assume that the Unicode math font is loaded already and now, we want to typeset characters mentioned above from text font in math mode. Try this:

\loadmath{texgyretermes-math} % Math font is loaded: texgyretermes-math, just for testing
\fontfam[Heros]               % Text font is loaded: texgyreheros, just for testing

% Test:
Heros in text, 13, but $Termes-math, 13$. It is visually incompatible.

\_def\_xsetmathfamily#1 #2 #3{\_resizefont{#2}#3\_setmathfamily #1 #3}

\_addto\_normalmath{% This must be declared after Unicode math font is loaded
    \_xsetmathfamily    5 rm \_tenrm
    \_xsetmathfamily    6 it \_tenit
}%
\_addto\_boldmath{%
    \_xsetmathfamily    5 bf \_tenbf
    \_xsetmathfamily    6 bi \_tenbi
}
\def\_rmdigits    {\_umathrange{0-9}75\_digitrmO}
\def\_rmvariables {\_umathrange{A-Z}75\_ncharrmA \_umathrange{a-z}75\_ncharrma}
\def\_itdigits    {\_umathrange{0-9}76\_digitrmO}
\def\_itvariables {\_umathrange{A-Z}76\_ncharrmA \_umathrange{a-z}76\_ncharrma}

% Default settings:
\_normalmath              % reloads math fonts incuding families 5, 6.
\_rmdigits \_itvariables  % Upright digits, slanted variables

\_def\textcharsinmath #1#2 {\_ifx\_end#2\_else
   \_Umathcode `#1=#2 5 `#1\_relax \_ea\textcharsinmath\_fi}

% Characters !?*,.:;+-=()[]/<>| are printed from math family 5, i.e. normal text font
\textcharsinmath !5 ?5 *2 ,6 .0 :6 ;6 +2 −2 =3 (4 )5 [4 ]5 /0 <3 >3 |0 .\end
\Umathcode `- = 2 5 "2212   % Minus in math mode created by the hyphen character
\Umathcode `\{ = 4 5 `\{    % \{ and \} must have mathcode from fam 5
\Umathcode `\} = 5 5 `\}
\Udelcode  `\{ = 1 `\{      % and delcode from fam 1 (math font)
\Udelcode  `\} = 1 `\}
\edef\{{\csstring\{}  \edef\}{\csstring\}}

% Test:
Heros in text and in math: $Heros-math: 13, M = -(3!) + y_5$.

The code above reloads the same Unicode math font but moreover, it loads the currently used \rm and \it text fonts as math family 5 and 6. The digits are rendered from family 5 and variables from family 6 instead of from the math fonts. The characters specified by \textcharsinmath, i.e. ! ? * , . : + - = ( ) [ ] < > | \{ and \}, are printed from family 5 too. Other math symbols are printed from original Unicode math font.

If you have loaded a special text font by \font primitive, i.e. \font\foo=something, then you can set this font as family 5 by

\_addto\_normalmath{%
    \_setmathfamily    5 \foo
}

Note that \_setmathfamily (no \_xsetmathfamily) is used with only two parameters: family number and the font switch.

(0027) -- P. O. 2020-07-09


Equation marks in atypical cases

We want to put equation marks \eqmark in more lines in display mode when we are using macros not designed for such case. For example in the lines of \case macro:

$$ f(x) = \cases{0 & for $x<0$\toright\eqmark \cr
                 1 & otherwise\toright\eqmark } $$

This puts the equation marks to the right margin in each line generated by the \case macro. The \toright\eqmark is used here. Analogically, \toleft\eqmark puts the equation mark to the left margin. The \toright, \toleft macrs are based on the OpTeX trick 0020 where the \setpos and \posx macros are defined.

\newcount\tomarginno
\def\toright#1{\_incr\tomarginno {\setpos[tr:\the\tomarginno]%
   \rlap{\kern-\posx[tr:\the\tomarginno]\kern\hoffset\kern\hsize\llap{#1}}}}
\def\toleft#1{\_incr\tomarginno {\setpos[tr:\the\tomarginno]%
   \rlap{\kern-\posx[tr:\the\tomarginno]\kern\hoffset\rlap{#1}}}}

The parameter #1 is shifted to the right/left margin in the second run of TeX because we need to know the absolute position of the current point and we shift it to the right margin.

(0028) -- P. O. 2020-07-10


Loading additional Unicode math fonts

Unicode math font should include all math characters needed in the document. But it is not generally true. Try to load a Unicode math font using \loadmath{math-font} followed by \input print-unimath.opm and you can see empty slots. They are not supported by loaded math font.

Maybe, another Unicode math font supports another slots. You can combine more than one math font in single document. For example, you have loaded Garamond-Math font and you have found that \triangleq isn't supported. Then you can load additional Unicode math font by \addUmathfont and you can re-declare \triangleq in order this additional font is used for such character. Example:

\fontfam [EBGaramond]  % Garamond family including Garamond-Math is loaded
\addUmathfont \stix {[STIXMath-Regular]}{} {}{} {} % additional font STIX loaded
\resetUmathchar \stix \triangleq  % \triangleq will use STIX

Test: $a \triangleq b$. % All is in Garamond only \triangleq in STIX.

If you want to set whole math alphabet to be used from additional font, you can modify \cal, \frak, \bbchar etc. macros:

\addto\cal{\fam\stix} % calligraphic will be used from STIX

You can load more than one additional Unicode math font (use more than one \addUmathfont command). Each additional font declares a new math-font family (its name is given in the first parameter) and you can use this name for re-declaring math characters. You can use \resetUmathchars (instead of \resetUmathchar) in order to re-declare more than one math character. The list of character sequences must be terminated by colon in such case. For example:

\resetUmathchars \stix \stareq \triangleq \veeeq \wedgeq ;

The implementation of \addUmathfont, \resetUmathchar and \resetUmathchars macros follows:

\def\resetUmathchar #1#2{% #1: fam, #2 \mathsequence
   \tmpnum=#2 \divide\tmpnum by16777216 \multiply\tmpnum by16777216
   \tmpnum=\numexpr #2-\tmpnum \relax
   \global\Umathcharnumdef #2=\numexpr 16777216*#1 + \tmpnum\relax
   \setUmathcode#2
}
\def\setUmathcode#1{% #1 \mathsequence
   \tmpnum=#1 \divide\tmpnum by1048576 \multiply\tmpnum by1048576
   \global\Umathcodenum\numexpr #1-\tmpnum =#1\relax
}
\def\resetUmathchars #1{\def\tmp{\resetUmathchar#1}\xargs\tmp}

\def\addUmathfont #1#2#3#4#5#6{% #1: fam (will be set), #2#3: normal font, #4#5: bold font
   \ifx\_ncharrmA\undefined \errmessage{basic Unicode math font must be loaded first}\fi
   \global\advance \famaddress by1
   \global\chardef #1=\famaddress
   \global\addto\_normalmath{\_corrmsize#6 \_loadumathfamily #1 {#2}{#3} }%
   \ifx\relax#4\relax
      \global\addto\_boldmath{\_corrmsize#6 \_loadumathfamily #1 {#2}{embolden=1.7;} }%
   \else
      \global\addto\_boldmath{\_corrmsize#6 \_loadumathfamily #1 {#4}{#5} }%
   \fi
   \_normalmath
   \wterm{MATH-FONT: added #1=\the#1, normal: "#2", \ifx"#4"\else bold: "#4"\fi}
}
\newcount\famaddress  \famaddress=100

The \addUmathfont has six parameters: family-name {normal-font}{features} {bold-font}{features} {factor}. If "bold-font" is empty then faked bold constructed from normal-font is used. The factor is decimal number for size corrections. If it is empty then factor=1. The math families are allocated from 101, 102 etc., i.e. macro programmer can use the numbers 16..99 for direct addressing (note that 0..4 are reserved by TeX, 5..15 are reserved by OpTeX).

The \resetUmathchar re-sets the appropriate Umathcode too (using \setUmathcode macro).

This trick was inspired by a question at StackExchange, but I recommend to use my answer (wipet's answer) in this case becuase combination of basic equal sign from Garamond with other equal signs from STIX doesn't look good in single formula.

(0030) -- P. O. 2020-12-02


Intelligent \dots like in AMSTeX

AMSTeX provides \dots macro which works by the context. If it is surrounded by symbols like + - = then it works like \cdots, if it is surrounded by comma or similar symbols then it works like \ldots. The OPmac trick 0084 shows one implementation of this feature, but it works only in non-Unicode math. So, we show another solution.

\def\declbasemathchars#1{\foreach#1\do{\sxdef{dots:\meaning##1}{}}}
\def\dots{\relax \ifmmode \ea\specdots \else \_dots \fi}
\def\specdots{\futurelet\next\specdotsA}
\def\specdotsA{\ifcsname dots:\meaning\next\endcsname \ldots
   \else \ischaracter\next \iftrue \cdots
   \else \ea\specdotsB\scantextokens\ea{\meaning\next}\sepU\Umathchar\sepU\end \fi\fi}
\def\specdotsB#1\Umathchar#2\sepU#3\end{\ifx^#1^\specdotsC#2\sepU \else \ldots \fi}
\def\specdotsC"#1"#2"#3\sepU{%
   \ifcase#1 \ldots\or \cdots\or \cdots\or \cdots\or \cdots\or \cdots\else \ldots \fi}
\def\ischaracter#1\iftrue{\ea\ischaracterA\scantextokens\ea{\meaning#1}character {}\end}
\def\ischaracterA#1character #2#3\end{%
   \ifx\relax#2\relax \cs{iffalse\ea}\else \cs{iftrue\ea}\fi}

\declbasemathchars{, . ; :}

% Test:
$a_1,\dots,a_n$\quad $a_1+\dots+a_n$
\bye

The macro \dots is re-defined here in math mode. It reads the following token and does \cdots or \ldots. If the following token is a single letter then it uses \ldots. If the following token is a single character not listed in the \declbasemathchars parameter then it uses \cdots. If it is listed in \declbasemathchars paremater then it uses \ldots. If the next token is a control sequence declared by \Umathchardef then it uses \cdots for classes Op, Bin, Rel, Open, Close and \ldots for other classes. In other cases, it uses \ldots.

The \declbasemathchars can be used more than once, the list of declared characters are appended when second and more \declbasemathchars is used.

(0045) -- P. O. 2021-02-12


Vertical bars and more math shortcuts

Writing $|x|$ in the source code works but $|-1|$ or $|\sin x|$ gives bad spaces, because | is Ord and it should be Open at the left and Close at the right side. But typing something more complicated in the math source is annoying. We define | as math active character which gives right spacing. Moreover, we can type $||x||$ for norm with righ spacing.

\mathcode`|="8000
\def\autovert{\isnextchar|{\autoVertA}{\autovertA}}
\def\autoVertA|#1||{\mathopen{}\mathclose{\left\Vert #1\right\Vert}}
\def\autovertA#1|{\mathopen{}\mathclose{\left\vert #1\right\vert}}
{\catcode`|=13 \global\let|=\autovert}

You can try to write $|-1|$ or $||x-y||$. Moreover, the vertical bars are resizable because the |#1| is defined as

\mathopen{}\mathclose{\left\vert#1\right\vert}.

Do you want more shortcuts? For example, if you write <= then it should be automatically transformed to Unicode character ≤ in your text editor. Then OpTeX is able to read it and your math source keeps clear. But, if such a feature is not available in your text editor then you can implement this intelligence at macro level. You write

$$ a <= b <= c ==> a <= c $$

and you get:

Code blocks

You can use macos from OPmac trick 0149, they are working in OpTeX too.

(0061) -- P. O. 2021-04-19


Tables

Colored cells in the table

We create the macro \colortab which can be used in \thistable declaration. When this macro is applied then tha background of each cell can be cloered by chosen color. If the first item in the cell data is color switcher then this color is used for the background of the cell. For example:

   ... & \Blue Text &        ... % blue background and black text
   ... & \Green \Red  Text & ... % green background and red text
   ... & \relax \Blue Text & ... % blue text and default background

The color of the background will stretch as spaces in the common cells in normal table, i.e. C declarator means that colored space will stretch from both sides, L declarator means right stretching and R declarator means left stretching. The spaces from \tabiteml and \tabitemr are colored too.

If \cellcolor sequence is set by \let to the color (example \let\cellcolor=\Yellow) then this color is used as default background. If the \cellcolor is undefined then default background is transparent.

You can insert the white space between columns by nonzero \tabskip and you can insert the white space between lines by \noalign{\kern...}. Example:

\thistable={\colortab              % colored cells activated
            \tabstrut={\lower4pt\vbox to15pt{}}        % lines dimensions
            \tabskip=2pt \everycr={\noalign{\kern2pt}} % spaces between cells
            \let\cellcolor=\Yellow % default background color
           }
\table{clr}{first item      & second & \Blue \White third item \cr
            next long item  & \Red X & \Green       YES }
creates the table:

Table example

Implementation:

\def\tabdC{\futurelet\next\setcellcolor##\end\hfil\hfil}
\def\tabdL{\futurelet\next\setcellcolor##\end\relax\hfil}
\def\tabdR{\futurelet\next\setcellcolor##\end\hfil\relax}
\def\tabdP#1{\futurelet\next\setcellcolor##\end\vtop
    {\hsize=#1\relax \baselineskip=\normalbaselineskip
     \lineskiplimit=0pt \noindent\tmp\_unsskip\lower\dp\_tstrutbox\hbox{}}}
\def\colortab{\tablinespace=0pt \let\_paramtabdeclarep=\tabdP
   \let\_tabdeclarec=\tabdC \let\_tabdeclarel=\tabdL \_let\_tabdeclarer=\tabdR}
\def\setcellcolor{%
   \ifx\next\_setcolor \expandafter\setcellcolorC\else \expandafter\setcellcolorD\fi}
\def\setcellcolorC\_setcolor#1#2#3#4\end#5#6{%
   \ifx#5\vtop \def\tmp{#4}%
      \setbox0=\hbox{\the\tabiteml\vtop{\localcolor#6}\the\tabitemr}%
      \the\tabstrut {\localcolor\_setcolor{#1}{#2}{#3}\vrule width\wd0}\kern-\wd0 \box0
   \else
      \setbox0=\hbox{\the\tabiteml\localcolor#4\_unsskip\the\tabitemr}%
      {\localcolor\_setcolor{#1}{#2}{#3}%
       \the\tabstrut\leaders\vrule\hskip\wd0 \ifx#5\hfil plus1fil\fi}%
      \kern-\wd0 \box0
      \ifx#6\hfil
      {\kern-.3bp \localcolor\_setcolor{#1}{#2}{#3}\leaders\vrule\hskip.3bp plus1fil}\fi
   \fi
}
\def\setcellcolorD{\ifx\cellcolor\undefined \let\next=\setcellcolorN
   \else \def\next{\_ea\_ea\_ea\setcellcolorC\cellcolor}%
   \fi \next
}
\def\setcellcolorN#1\end#2#3{#2\the\tabiteml{\localcolor#1\unskip}\the\tabitemr#3}

\def\multispanc#1#2#3{\multispan{#1}%
   \_ea\_ea\_ea\cellcolexp#2#3\end\hfil\hfil\ignorespaces}
\def\cellcolexp{\futurelet\next\setcellcolor}

The \setcellcolor macro scans the first token from the cell data. The macro works in declaration part of the \halign, so the first token is scanned as the first unexpandable token after expansion. This token is \_setcolor when color macro is presented here. If this is true then the \setcellcolorC macro is executed, #1, #2 and #3 are the \_setcolor parameters #4 is the text of the item, #5 and #6 decide the align style. This macro measures the cell text in the box0 and does the colored background using \leaders. If the first token isn't \_setcolor then the macro \setcellcolorD decides if \cellcolor is defined. If it is true then \setcellcolorC is execuded (with partially expanded \cellcolor as parameter). Else \setcellcolorN is executed. This macro doesn't print any background.

The \multispanc Num \Color {text} can be used for multispan cells, see OPmac trick 103.

(0004) -- P. O. 2020-04-10


Colored lines in the table

We can see the trend to use colored tables like this:

Table example

The table above can be created by the \table macro from OpTeX:

\thistable={\tabiteml={\quad}\tabitemr={\quad}}
\frame{\table{llllll}{\crx
  aa & bb & cc & dd & ee & ff \crx
  gg & hh & ii & jj & kk & ll \crx
  mm & nn & oo & pp & qq & rr \crx
  ss & tt & uu & vv & ww & xx \crx
  ab & cd & ef & gh & ij & kl \crx
  mn & op & qr & st & uv & wx}}

We need to define the \crx macro which inserts the grey rule before each odd line into the vertical list:

\newcount\tabline  \tabline=1 
\def\crx{\crcr \ifodd\tabline \colortabline \else\noalign{\hrule height0pt}\fi 
         \global\advance\tabline by1 } 
\def\colortabline{\noalign{\localcolor\LightGrey 
   \hrule height\ht\strutbox depth\dimexpr\dp\strutbox +2\tablinespace\relax 
   \kern-\dimexpr\ht\strutbox+\dp\strutbox +2\tablinespace}} 

The \tabline counter counts the lines in the table and inserts the grey backgroud for each odd \tabline. The last line must not be terminated by \crx macro.

(0005) -- P. O. 2020-04-10


Tables like in booktabs package

The orthodox advocates of LaTeX booktabs package don't like the table formats from the example above and they point out that I am loser because I don't know that the vertical rules in the table are out. Only horizontal rules are allowed but not double rules. And less rules is better than more. They have the following example in the documentation:

examle of table

we can create such table by OpTeX and without booktabs package:

\thistable={\tabiteml={}\tabitemr={}\rulewidth=.2pt}
\table{l(\quad)lr}{                           \crtop 
  \mspan2[c]{Item}         &                  \crlp{1-2} 
   Animal    & Description & \quad Price (\$) \crmid 
   Gnat      & per gram    &      13.65       \cr 
             & each        &       0.01       \cr 
   Gnu       & stuffed     &      92.50       \cr 
   Emu       & stuffed     &      33.33       \cr 
   Armadillo & frozen      &       8.99       \crbot} 

We only need to define \crtop, \crmid, \crbot macros:

\def\crtop{\crcr \noalign{\hrule height.6pt \kern2.5pt}} 
\def\crbot{\crcr \noalign{\kern2.5pt\hrule height.6pt}} 
\def\crmid{\crcr \noalign{\kern1pt\hrule height.4pt\kern1pt}} 

We need to set default rulewidth for this table to .2pt (it is generated by \crlp{1-2}). Other rules have explicitly declared thickness. We have to remove the spaces left in the first cell and right in the last cell because the rules have to be aligned with the text. This is a reason why the \tabitemr and \tabiteml are empty. The space between first and second column is created by (\quad) in the declaration. The same space before third column is caused by \quad before "Price".

(0007) -- P. O. 2020-04-10


Decimal digits aligned by the decimal point

We declare new column letter N with numeric parameter: it gives the maximal number of the digits used after the decimal point in the column. Table items can be in the form "xxx.xx" or ".xxx" or "xxx.". All these variants are aligned by the decimal point. The deciaml point itself is printed by the \decimalpoint macro, which can be defined as period or as comma (comma is used in Czech traditional typography for decimal numbers). The latest form "xxx." is aligned too but the decimal point is not printed. If the table item does not have any decimal point (for example it is the title of the column) then the text is centered.

\def\decimalpoint{.}
\def\_paramtabdeclareN#1{\the\tabiteml\hfil\adigit{#1}##.\relax\the\tabitemr} 
\def\adigit#1#2.#3{\ifx\relax#3\hfil#2\_unsskip\hfil \else
    \tmpdim=.5em \tmpdim=#1\tmpdim $#2$%
    \_ea \ifx.#3\relax\phantom\decimalpoint \else \decimalpoint \fi
    \_ea \adigitA\fi #3%
}
\def\adigitA#1.\relax{\hbox to\tmpdim{$#1$\hss}} 

% test:

\table{r N3 l}{
       & results & \crl
   aha &   1.24  & uff \cr
   uha &    .123 & mm  \cr
    nn &  24.42  & rr  \cr
     p &   2.    & r   \cr
     q &    .24  & s   }

The N declararor runs \adigit macro. This macro reads the item data until decimal point is found. The hidden decimal point is in the right part of declaration, so we are sure that the scanning of the parameter finishes. If this compensatory decimal point is scanned (i.e. no decimal point is in the item data, #3 is \relax) then we write the text centered. Else we write left part of the number, \decimalpoint (or \phantom\decimalpoint if no digits follows) and the rest is printed in the box with fixed width given by the number of digits declared in N parameter.

If you don't want to specify the maximal number of digits, you can declare D column declarator (without parameter) and use the \eqbox macro from OpTeX, see section 2.30.4. You have to run TeX two times.

\def\decimalpoint{.}
\def\_tabdeclareD{\the\tabiteml\hfil\adigitD##.\relax\the\tabitemr} 
\def\adigitD#1.#2{\ifx\relax#2\hfil#1\_unsskip\hfil \else
    $#1$%
    \_ea \ifx.#2\relax\phantom\decimalpoint \else \decimalpoint \fi
    \adigitAD\fi #2%
}
\def\adigitAD#1.\relax{\eqbox l[D:\the\tabnum/\the\colnum]{$#1$}} 

\newcount\tabnum
\everytable{\global\advance\tabnum by1}

We must to specify the label for \eqbox uniquely in whole document. So we have to set numbers to each table and use the label in the form D:tablenum/colnum. Note that the column number \colnum is calculated by the \table macro and it is available for each column used in this macro.

(0015) -- P. O. 2020-05-20


Long table across multiple pages

The \table macro runs \halign primitive inside \vbox. This is reason why it is unbreakable to multiple pages. But you can deactivate this \vbox. Suppose that you have a lot of lines like this:

\def\ltable{\bgroup \let\_tablebox=\egroup \table} % the \vbox is deactivated

\ltable{|c|c|}{data & data \cr 
               data & data \cr 
      ... much more & data ... \cr} 

Now, the \halign can work in main vertical mode and its lines are breakable to more pages. But you can see more problems. If the \table is in main vertical mode, then all lines are at the left margin of the page box. Maybe, you want to see them in the center. This can be done by:

\thistable{tabskipl=0pt plus1fil \tabskipr=\tabskipl}
\ltable to\hsize {|c|c|}{data & data \cr 
                         data & data \cr 
                ... much more & data ... \cr} 

OK, the table is in the center. But \crl does not do what we want. We can use \crli instead it. If there is \crli at each line, then we see next problem. The horizontal rule is at the bottom of the page but not at the top of next page. You can re-define \crli like this:

\thistable{\tabskipl=0pt plus1fil \tabskipr=\tabskipl
           \def\crli{\_crli \noalign{\penalty0\kern-.4pt}\_crli\noalign{\nobreak}}}
\ltable to\hsize{|c|c|}{\crli
                  data & data \crli
                  data & data \crli 
         ... much more & data ... \crli} 

The trick is based on the fact, that there are two horizontal rules (one over another) between each lines. If a page break occurs (at \penalty0) then one horizontal rule is at the bottom and second one at the top of next page.

The tricks above are only very simple examples of long tables. If you need something more smart then you can use fixed witdh of table items and each line is one \hbox. You need not to use \halign. Or you can use \eqbox from OpTeX. Or you can resample the boxes created by \halign. This last approach allows to repeat a table header at the top of each page again:

\def\longtable#1#2{\goodbreak \bgroup \tablinespace=0pt \let\_zerotabrule=\empty 
   \setbox0=\table{#1}{\strutT\relax #2} 
   \setbox1=\vbox{\unvbox0 \setbox2=\vbox{}\revertbox} 
   \setbox2=\vbox{\unvbox2 \global\setbox4=\lastbox\unskip \global\setbox5=\lastbox\unskip}
   \whatfree4 \advance\tmpdim by-.8pt \ifdim\tmpdim≤\baselineskip \vfil\break \fi 
   \offinterlineskip \crule\center4\crule\center5 \printboxes 
   \center5\crule \egroup \goodbreak 
}
\def\whatfree#1{\tmpdim=\vsize \advance\tmpdim by-\pagetotal 
   \advance\tmpdim by-\prevdepth \advance\tmpdim by-.4pt 
   \advance\tmpdim by-\ht#1 \advance\tmpdim by-\dp#1 \advance\tmpdim by-\ht5 } 
\def\revertbox {\setbox0=\lastbox\unskip 
   \ifvoid0 \else \global\setbox2=\vbox{\unvbox2\box0} \ea\revertbox\fi } 
\def\printboxes{\setbox2=\vbox{\unvbox2 \global\setbox0=\lastbox\unskip} 
   \ifvoid0 \else \whatfree0 
      \ifdim\tmpdim≤0pt \center5\crule\vfil\break \crule\center4\crule\center5 \fi 
      \center0 \nobreak \printboxes \fi } 
\def\crule{\centerline{\hbox to\wd4{\hrulefill}}\nobreak} 
\def\center#1{\centerline{\copy#1}\nobreak} 

\def\strutT {\vrule height1.8em depth.8em width0pt} % strut for the title

\longtable {|c|c|c|}{ head1 & head2 & head22 \cr % title 
                                             \tskip5pt
                      data2 & data2 & data2 \cr
                      data3 & data3 & data3 \cr
                      data4 & data4 & data4 \cr 
                       data & data  & data \cr 
              ... much more & data  & data ...} 

The head1 , head2 and head22 is repeated with rules above and below this head at the top of each page. The \tskip is mandatory here and it separates the bottom rule of the head from the data. This solution is copied from OPmac trick 0024.

(0016) -- P. O. 2020-05-20


Vertically centered paragraphs in table

The p declarator in the \table macro creates paragraphs in \vtop. It means that first line of all paragraphs in one table row is vertically aligned. Maybe, you want to set vertically alignment to center or to the last line. The following testing code shows one approach:

\def\vbot{\let\_tableparbox=\_vbox}
\def\vcent{\toksapp\tabiteml{$}\tokspre\tabitemr{$}\let\_tableparbox=\_vcenter}

\table{|p{1.5cm}|p{4cm}|}{ % top alignment (default)
 \crl
 d f g fd s f h d d s r df g h jj f d sd f d f g g t f di ejb  &
 ff h hjs hjs chjds js sjd fgh fshs gf sfhs fh ss shfs fs
 \crl
}

\table{|p{1.5cm\vcent}|p{4cm\vcent}|}{ % vertically centered paragraphs
 \crl
 d f g fd s f h d d s r df g h jj f d sd f d f g g t f di ejb  &
 ff h hjs hjs chjds js sjd fgh fshs gf sfhs fh ss shfs fs
 \crl
}

You can combine \fL, \fR, \fC and \fX with \vbot and \vcent. For example, p{7cm\fC\vcent} creates horizontaly and verticaly centered paragraph.

Internals: the macros \vbot and \vcent redefine the OpTeX sequence \_tableparbox, which is used in "p" table items and is declared as \vtop by default.

The alternative is:

\thistable{\vcent} \table{|p{1.5cm}|p{4cm}|}{...}

(0023) -- P. O. 2020-06-01


The decimal point aligned in the table

We create table column declarator Nn which can be used for decimal numbers aligned by decimal point.

\table{|c|Nn|}{\crl
   first:   & 23433.1       \cr
   second:  &    55.131415  \crl
}

In fact, there are two clumns N and n with following declarations:

\def\_tabdeclareN{\the\tabiteml \hfil \scannum ##\_unsskip}
\def\_tabdeclaren{##\_unsskip \hfil \the\tabitemr}

\def\scannum#1 {\isinlist{#1}.\iftrue \ea\scannumA \else \ea\scannumB\fi #1\relax}
\def\scannumA#1.#2\relax{#1&\decimaldot#2}
\def\scannumB#1\relax{#1&}
\def\decimaldot{.}

First column N is right aligned and second colum n is left aligned. The first column runs the scanner \scannum which reads the number separated by space. It means that we must write space after numbers and before & or \cr.

There exists more powerfull macro \num which can be used in tables too, see OpTeX trick 0040.

(0039) -- P. O. 2021-02-06


Table notes

We create the macro \tnote{text} which can be used in tables. Each occurrence prints only a mark in the form ^a, ^b, etc. and saves {text} into the memory. When the table is printed then user write \tnoteprint and the marks are repeated again with the appropriate texts. This is similar as footnotes, but works locally for each table.

\newcount\tnotenum
\def\tnotelist{}
\def\tnote#1{\incr\tnotenum $\Red^{\rm\_athe\tnotenum}$\global\addto\tnotelist{{#1}}}
\def\tnoteprint{\par\noindent \tnotenum=0
   \ea\foreach\tnotelist \do{\advance\tnotenum by1 $\Red^{\rm\_athe\tnotenum}$##1 }\par
   \global\tnotenum=0 \gdef\tnotelist{}%
}

%test:
\table{|c|c|}{\crl
  m\tnote{meters}      & s\tnote{seconds} \crl
  kg\tnote{kilograms}  & A\tnote{ampers} \crl
}
\tnoteprint

The format of \tnoteprint is single paragraph with \noindent and with note marks in \Red. You can create a different \tnoteprint macro with different formating, of course.

(0046) -- P. O. 2021-02-14


Positioning in the table

We craete macro \tabnodes which creates nodes in the table when \thistable{\tabnodes} is declared. You can put graphics or text at these nodes before or after the table using \tablebefore or \tableafter macros. Example:

\tablebefore
   \pdfliteral{q 1 0 0 RG \icc[1,1] m \icc[2,3] l S \icc[2,1] m \icc[1,3] l S Q}%

\thistable{\tabnodes \tablinespace=0pt}
\table{ccc}{
   first & second & third \cr
   A     & B      & C
}

This example connects the center of "first" with the center of "C" and the center of "third" with the center of "A" by red line. The macro is based on absolute positions on the page OpTeX trick 0020, so the material from \tablebefore and \tableafter is placed at given positions after second run of TeX.

The \tablebefore or \tableafter set the position of the current point as origin of coodinate system and following \pdfliteral's can use the macro \icc (and others). They give the positions of table items in bp unit and in the current coordinate system. The \tablebefore must be used before \table (the graphics act as background of the real table) and \tableafter should be used after \table with the same meaning, but the graphics owerwrite the table. You can use both of them or only one of them at the same page where the \table is.

When \tabnodes is used then \tablinespace=0pt is recommended in order to correct vertical positioning of the nodes.

There are macros \itl, \icl, \ibl, \itc, \icc, \ibc, \itr, \icr, \ibr. Their syntax is \itl[row,column] and they expand to the coordinates (separated by space) of the table item given by [row,column]. These macros expand to top-left, center-left, bottom-left, top-center, center, bottom-center, top-right, center-right, bottom-right coordinates of the item respectively. Moreover the macros \iht[row,column] or \iwd[row,column] expand to the total height or width of the item respectively.

\ishift\itl[row,col][right,up] expands to the \itl coordinates of the point shifted by given amount right and up, for example \ishift\itl[2,3][1pt,1pt]. The same can be applied to other macros \icl, \ibl, \itc, \icc, \ibc, \itr, \icr, \ibr.

You can use resuls from \icc (etc.) macros as argument of \puttext and \putpic macros too, but you have to re-define \expru for this case:

\tablebefore
   ...
   {\def\expru#1{\expr{#1}bp}\puttext\ibl[2,2]{HELLO}}

The implementation follows. Copy macros fom the OpTeX trick 0020 and add following macros:

\def\tablebefore{\ifvmode \nointerlineskip\null \fi
   \incr\tndnum \setpos[t\the\tndnum:ori:b]\ea\tbpxpy\ea{\the\tndnum}{b}\decr\tndnum
}
\def\tableafter {\setpos[t\the\tndnum:ori:e]\ea\tbpxpy\ea{\the\tndnum}{e}}
\def\tbpxpy#1#2{%
   \def\tbpx[##1]{\expr{\bp{\posx[t#1:##1]}-\bp{\posx[t#1:ori:#2]}}}%
   \def\tbpy[##1]{\expr{\bp{\posy[t#1:##1]}-\bp{\posy[t#1:ori:#2]}}}%
}
\newcount\tndnum \newcount\tablerownum
\def\putnodeh#1{\omit\relax
   \setpos[t\the\tndnum:l#1]\hskip0pt plus1fill\relax \setpos[t\the\tndnum:r#1]}
\def\putnodelr{\fornum 1..\colnum-1\do{\putnodeh{##1}&}\putnodeh{\the\colnum}%
   \gdef\putnode{\noalign{\putnodev}}\cr}
\def\putnodev{\setpos[t\the\tndnum:v\the\tablerownum]\incr\tablerownum}
\def\tabnodes{\incr\tndnum \tablerownum=0
   \def\_tablepxpreset{\everycr{}}\let\putnode=\putnodelr \everycr={\putnode}}

\def\itl[#1,#2]{\expru{\tbpx[l\enum{#2}]} \expru{\tbpy[v\enum{#1-1}]}}
\def\icl[#1,#2]{\expru{\tbpx[l\enum{#2}]}
                \expru{(\tbpy[v\enum{#1-1}]+(\tbpy[v\enum{#1}]))/2}}
\def\ibl[#1,#2]{\expru{\tbpx[l\enum{#2}]} \expru{\tbpy[v\enum{#1}]}}
\def\itc[#1,#2]{\expru{(\tbpx[l\enum{#2}]+(\tbpx[r\enum{#2}]))/2}
                \expru{\tbpy[v\enum{#1-1}]}}
\def\icc[#1,#2]{\expru{(\tbpx[l\enum{#2}]+(\tbpx[r\enum{#2}]))/2}
                \expru{(\tbpy[v\enum{#1-1}]+(\tbpy[v\enum{#1}]))/2}}
\def\ibc[#1,#2]{\expru{(\tbpx[l\enum{#2}]+(\tbpx[r\enum{#2}]))/2}
                \expru{\tbpy[v\enum{#1}]}}
\def\itr[#1,#2]{\expru{\tbpx[r\enum{#2}]} \expru{\tbpy[v\enum{#1-1}]}}
\def\icr[#1,#2]{\expru{\tbpx[r\enum{#2}]}
                \expru{(\tbpy[v\enum{#1-1}]+(\tbpy[v\enum{#1}]))/2}}
\def\ibr[#1,#2]{\expru{\tbpx[r\enum{#2}]} \expru{\tbpy[v\enum{#1}]}}
\def\iwd[#1,#2]{\expru{\tbpx[r\enum{#2}]-(\tbpx[l\enum{#2}])}}
\def\iht[#1,#2]{\expru{\tbpy[v\enum{#1-1}]-(\tbpy[v\enum{#1}])}}

\def\enum#1{\number\numexpr#1\relax}
\def\ishift #1{\ea\ishifta#1}  %  \ishift[2,3][1pt,2.5pt]
\def\ishifta \expru#1 \expru#2#3#4,#5]{\expru{#1+(\bp{#4})} \expru{#2+(\bp{#5})}}
\let\expru=\expr

The nodes t(tab-num):v(row-num) are created for vertical positioning, t(tab-num):l(con-num) and t(tab-num):r(col-num) are created for horizontal positioning (left side and right side of given column). The t(tab-num):ori:b and t(tab-num):ori:e are nodes for origins at \tablebefore and \taleafter respectively. See .ref file for real example. Note that t(tab-num):l(col-num) can differ from t(tab-num):r(col-num -1) if \tabskip is non-zero.

(0050) -- P. O. 2021-03-09


Text blocks

Text blocks with grey background splittable to more pages

We implement another design of \begblock...\endblock than the default from OpTeX. The text blocks will have a grey background. They can split into more pages. The grey background continues to the next pages in this case. The main issue of the implementation is this splitting problem:

\newcount\blocklevel  % nesting level of blocks
\def\begblock{\par\bgroup
   \advance\blocklevel by1 \advance\leftskip by\iindent \rightskip=\leftskip
   \medskip
   \pdfsavepos \ea\_wref\ea\Xblock\ea{\ea{\the\blocklevel}B{\the\pdflastypos}}
   \nobreak \medskip
}
\def\endblock{\par\nobreak\medskip
   \pdfsavepos \ea\_wref\ea\Xblock\ea{\ea{\the\blocklevel}E{\the\pdflastypos}}
   \medskip \egroup
}
\refdecl{%
   \def\Xblock#1#2#3{\ifnum#1=1 \edef\tmp{frm:\ea\ignoresecond\_currpage}^^J
      \unless\ifcsname \tmp \endcsname \sxdef{\tmp}{}\fi^^J
      \sxdef{\tmp}{\cs{\tmp}#2{#3}}\fi}
}
\newdimen\frtop \newdimen\frbottom % positions of top and bottom text on the pages
\def\frcolor{.8 g } % light grey -- color of blocks.
\pgbackground={%
   \slet{tmp}{frm:\the\pageno}
   \ifx\tmp\undefined \def\tmp{}\fi
   \frtop=\dimexpr \pdfpageheight-\voffset+\smallskipamount\relax
   \frbottom=\dimexpr\pdfpageheight-\voffset-\vsize-\medskipamount\relax
   \ifx\frnext y \edef\tmp{B{\number\frtop}\tmp}\global\let\frnext n\fi
   \ea\printframes \tmp B{0}E{\number\frbottom}
   \ifx\frameslist\empty \else
   \pdfliteral{q \frcolor 1 0 0 1 0 \bp{-\pdfpageheight} cm \frameslist Q}\fi
}
\def\printframes B#1#2E#3{\ifnum#1=0 \else
   \printframe {\hoffset}{#3sp}{\_xhsize}{\ifnum#1=-1 \number\frtop\else#1\fi sp-#3sp}
   \ifx^#2^\else \global\let\frnext=y \let\printframes=\relax \fi
   \expandafter\printframes\fi
}
\def\frameslist{}
\def\printframe #1#2#3#4{\edef\frameslist{\frameslist
    \bp{#1} \bp{#2} \bp{#3} \bp{#4} re f }%
}

The main idea of the implementation. The \begblock writes its vertical position to the .ref file in the format \Xblock{level}B{position} using \pdfsavepos and \pdflastypos. The \endblock writes analogical information in the format \Xblock{level}E{position}. We restore this information (only first nesting level) from the .ref file like

\def\frm:gpageno{B{pos}E{pos}B{pos}E{pos}...B{pos}E{pos}}

for each page number gpageno. The grey rectangles are drawn in the \pgbackground when output routine is processed in the second TeX run. This is done by

\printframes B{pos}E{pos}...B{pos}E{pos}B{0}E{bottom-pos}

This macro reads B{}E{} in pairs in a loop and creates the frame for each pair. When B{0}E{bootom-pos} is read, the loop is finished and the last frame is not created. This is "normal" behavior when all blocks begin and end on the same page. If we have an opened block at a page (but not closed), then the data looks like

\printframes B{pos}E{pos}...B{pos}B{0}E{bottom-pos}

The last pair B{pos}#2E{bottom-pos} is processed now (#2 is ignored) and the status about the continued block is saved as \frnext=y. Then the data of the next page looks like B{top-pos}E{pos}...B{0}E{pos}.

You can test it:

\lipsum[1]
\begblock
  \lipsum[2-3]
\endblock
\lipsum[4-5]

and run OpTeX twice.

(0031) -- P. O., 2021-01-13


Text blocks in ovals

The trick 0031 above (text blocks splitting to more pages) should be modified with respect to various different designs. The example below puts these blocks to yellow ovals with red borders.

We have to modify the \frcolor macro:

\def\frcolor{1 1 0 rg 1 0 0 RG } % yellow background, red borders

and the \printframe macro in order to print the oval with desired sizes. The diameter or rounded corners is set to 10bp.

\def\printframe #1#2#3#4{\edef\frameslist{\frameslist
    q 1 0 0 1 \bp{\hoffset+10bp} \bp{#2+10bp} cm    % origin shifted to the left-bottom
    \_oval{\bp{\_xhsize-20bp}}{\bp{#4-20bp}}{10} B Q }%
}

(0034) -- P. O., 2021-01-14


Macro tricks

Structured conditionals

The usage of \if* conditionals is very uncomfortable if we need to use some structured logical expression in our macros with brackets () and logical AND, OR. This is not implemented at TeX primitive level, unfortunately. But we can use the following trick:

\def\cond[#1:#2]{#1 1\else0\fi}

%%        IF      (           a=a     AND          c=c      ) OR          e=f       then 
\ifnum 0<\numexpr ( \cond[\ifx aa:\fi] * \cond[\ifx cc:\fi] ) + \cond[\ifx ef:\fi] \relax
     YES \else NO \fi

The \cond macro has syntax: \cond[primitive conditional:\fi] where primitive conditional is \ifx, \if, \ifnum, \ifvoid, \ifmmode, \ifdim, etc. followed by elementary conditional expression which syntax is dependent on used \if primitive. The \fi prameter is mandatory here because we need to have the whole construction \if-skipable.

If you want to use AND logical conjunction write *. If you want OR write +. Use brackets () as usual. The whole \if construction is fully expandable.

You can add another logical operators if you want:

\def\IMPL#1{\IMPLa#1\relax}
\def\IMPLa#1=>#2\relax{\ifnum #1>#2 0\else 1\fi} 

\ifnum 0<\numexpr 
%        (         a=a      =>           c=d     ) AND          2=2        then
   \IMPL{ \cond[\if aa:\fi] => \cond[\if cd:\fi] } * \cond[\ifnum2=2:\fi] \relax 
   Yes \else No \fi

(0019) -- P. O. 2020-05-27


Absolute positions on the page

You can write \setpos[label] somewhere and the position of such \setpos[label] can be referenced by \posx[label], \posy[label] and \pospg[label]. The first two macros expand to x and y position measured from left-bottom corner of the page (dimen values) and \pospg[label] expands to the page number in the document (counted from one at beginning of the document).

These values are available in second (and more) TeX run, because the information is saved to ref file and restored from it at the beginning of TeX job. If these vaules are not known then mentioned macros expand to 0sp, 0sp and 0.

\refdecl{
   \def\Xpos#1#2#3{\sxdef{pos:#1}{{#2}{#3}\_currpage}}
}
\def\setpos[#1]{\openref\pdfsavepos
   \_ewref\Xpos{{#1}\unexpanded{{\the\pdflastxpos}{\the\pdflastypos}}}}

\def\posx [#1]{\_ea \posi   \romannumeral-`\.\trycs{pos:#1}{{0}{0}{0}{0}}sp}
\def\posy [#1]{\_ea \posii  \romannumeral-`\.\trycs{pos:#1}{{0}{0}{0}{0}}sp}
\def\pospg[#1]{\_ea \posiii \romannumeral-`\.\trycs{pos:#1}{{0}{0}{0}{0}}}

\def\posi   #1#2#3#4{#1}
\def\posii  #1#2#3#4{#2}
\def\posiii #1#2#3#4{#3}

The coordinates are saved to the .ref file in the format \Xpos{label}{x-pos}{y-pos}. The \Xpos macro defines \pos:label as {x-pos}{y-pos}{total-pg}{rel-pg}. We need to read only given parameter by \posi, \posii or \posiii auxiliary macros. The \romannumeral-`\. trick does (empty) expansion of \trycs until first { occurs.

This example shows the usage of \refdecl macro from OpTeX and \pdfsavepos, \pdflastxpos, \pdflastypos pdfTeX primitives.

Unusable example: the following code draws the line from current position to the left-bottom corner of the page.

Text text\setpos[A]\pdfliteral{q 0 0 m -\bp{\posx[A]} -\bp{\posy[A]} l S Q}

(0020) -- P. O. 2020-05-27


Drawing to given nodes

We show the application of the previous OpTeX trick 0020. We can connect two (or more) points at the single page by a line (more lines, curves). These points are declared by the position of the current point. For example the last word of the first paragraph is connected to the last word of the second paragraph by a line:

This is first paragraph.\setorigin\pdfliteral{0 0 m \bpx[end1] \bpy[end1] l S}

This is second paragraph connected by a line.\setpos[end1]

One position must be set by \setorigin. Other positions at the same page can be set by \setpos[label]. Then you can draw something at origin position by \pdfliteral primitive. The coordinates of the origin is 0,0, the coordinates of a point with label [label] (in big points) is given by \bpx[label],\bpy[label].

Use macros from previous OpTeX trick 0020 and these macros:

\newcount\posorigin
\def\setorigin{\incr\posorigin\xdef\origin{ori:\the\posorigin}\setpos[ori:\the\posorigin]}
\def\bpx[#1]{\expr{\bp{\posx[#1]}-\bp{\posx[\origin]}}}
\def\bpy[#1]{\expr{\bp{\posy[#1]}-\bp{\posy[\origin]}}}

We can add arrows using macro \arrowccspike and \rotcm from OpTeX trick 0037:

This is first paragraph.\setorigin
\pdfliteral{q 0 0 m 1 0 0 RG 1 0 0 rg
   100 40 \expr{\bpx[end1]+50} \expr{\bpy[end1]+30} \bpx[end1] \bpy[end1] c S
   q \rotcm (0 0) (-100 -40) 0 0 cm \arrowccspike\space Q
   q \rotcm (0 0) (-50 -30) \bpx[end1] \bpy[end1] cm \arrowccspike\space Q
   Q}

This is second paragraph connected by a line.\setpos[end1]

Code blocks

(0041) -- P. O. 2021-02-06


Tabbing macros

Plain TeX provides tabbing macros but OpTeX does not, bacuase old typewriter style for alignment is not preferred. Use \table instead this. Or you can set such typewriter TABs by following macros. Moreower, these macros are more intelligent than the Plain TeX equivalent.

You can say \settabs 4\column. Then the \hsize is divided to four equal-size parts, each of them begins by a TAB position and the last one ends by a TAB position too. I.e. there are five equi-distant TAB positions. Then you can type

\tabs first text & second text & third text & fourth text & other text \cr

The left boundary of the first text is aligned with first TAB position. The next text is aligned to next TAB position. But it needs not to be exactly the second one: all TAB positions which are left to the actual text position are ignored and the first non-ignored TAB position is used. This means that the text is never overlapped. This is real behavior of old typewriters. Go to a Museum of Technology and try it. This is the significant difference from plain TeX macros.

You can set the TAB positions by invisible sample line too:

\settabs\tabs &textA&textB&textC\cr

Each & or \cr gives one TAB position. If you did not use first & immediately after \tabs then first TAB position is shifted from left boundary of lines.

The implementation looks like:

\def\tabs #1\cr{\setbox0=\hbox\bgroup \tabA #1&\cr&}
\def\tabA #1&{\ifx\cr#1\egroup\box0 \else 
   \egroup 
      \tmpdim=\maxdimen \ea\tabB \tabsdata {}\relax
      \ifdim \tmpdim=\maxdimen \tmpdim=0pt
      \else \advance\tmpdim by-\wd0 \fi
   \setbox0=\hbox\bgroup \unhbox0 \kern\tmpdim \ignorespaces#1\unskip\kern1sp
   \_ea\tabA \fi
}
\def\tabB #1{\ifx^#1^\else \unless\ifdim \wd0>#1 \tmpdim=#1 \tabC \fi \ea\tabB \fi} 
\def\tabC #1\relax {\fi\fi}

\def\settabs #1{\def\tabsdata{}\ifx\tabs#1\_ea\settabstext \else\_ea\settabscols \fi #1}
\def\settabscols #1\columns{\tmpdim=\hsize \divide\tmpdim by#1\relax
   \fornum 0..#1 \do {\edef\tabsdata{\tabsdata{\the\dimexpr##1\tmpdim\relax}}}}

\def\settabstext #1#2\cr{\setbox0=\hbox\bgroup \settabA #2&\cr&}
\def\settabA #1&{\ifx\cr#1\egroup \edef\tabsdata{\tabsdata{\the\wd0}}\else
    #1\egroup \edef\tabsdata{\tabsdata{\the\wd0}}%
    \setbox0=\hbox\bgroup \unhbox0
    \_ea\settabA \fi
}
\def\tabsdata{}  % default: no tabulators set

The \tabsdata macro includes TAB positions in the format {0pt}{100pt}{200pt}. Of course, you can define directly TAB positions by \def\tabsdata{{0cm}{2cm}{4cm}{6cm}{8cm}}, for example.

The \tabs macro opens \setbox0=\hbox\bgroup, sets the appropriate text to it, closes it, measures \wd0, sets appropriate kern, opens \setbox0=\hbox\bgroup \unhbox0 again ad does this in a loop.

(0021) -- P. O. 2020-05-27


Macro with variable number of arguments in braces

You can create a macro by \vardef\mymacro{...} which can be used with arbitrary number of parameters enclosed in braces. For example \mymacro{first}{second} or \mymacro{one}{two}{three}. If the \mymacro is declared by \vardef, then you can use \NARGS in the macro body (this expands to the number of parameters just read) and \ARG{num} which expands to the num-th parameter.

This code is published at Stack exchange too. The discussion and more detail description is there.

\newcount\vardefnum
\newtoks\vardefinition

\def\vardef#1#2{%
\def#1{%
    \vardefnum=0
    \vardefinition{#2}%
    \grab}}

\def\grab{\futurelet\next\grabA}

\def\grabA{\ifx\next\bgroup \expandafter\storearg \else
  \edef\NARGS{\the\vardefnum}%
  \def\ARG##1{\csname vararg:##1\endcsname}%
  \the\vardefinition
  \let\NARGS\undefined \let\ARG=\undefined
  \fi}

\def\storearg#1{\advance\vardefnum by 1
  \ea\def\csname vararg:\the\vardefnum\endcsname{#1}%
  \grab}

The body #2 of a user defined macro is stored in the \vardefinition token string. Then the opening brace { is tested by the \futurelet primitive and possible parameters are read to \vararg:num macro. Next, the \vardefinition is processed.

You can test it by following code:

\vardef\mymacro{%
  The number of arguments passed to {\tt\string\mymacro} is \NARGS.
  \ifnum\NARGS=0\else
     They are:
     \tmpnum=1 \loop \ARG{\the\tmpnum}%
        \ifnum\NARGS>\tmpnum, \advance\tmpnum by 1 \repeat.\fi
}

\mymacro{a}{b}{c}

\mymacro{1}{2}{3}{4}{5}

\mymacro{x}

\mymacro{}{}{}{}

\mymacro123  % You can't omit brackets!

(0025) -- Matteo Caoduro. 2020-06-05


The import package

We implement the LaTeX import package (see texdoc import) by three lines of the code.

\def\import#1#2{\def\tmp{\gtoksapp\picdir{#1/}\input{#2}}%
   \ea\tmp\ea\global\ea\picdir\ea{\the\picdir}}
\def\input#1{\_input{\the\picdir#1}}

The \input is redefined as primitive input applied to "\the\picdir filename". The \import macro redeclares \picdir and does the \input. When input is finished, then \picdir returns to its previous value.

Unlike the import LaTeX package, the \import macro can be used recursively in imported files. So, we need not to define \subimport.

(0035) -- P. O., 2021-01-14


Numbers printed in three-digits groups

The long number should be printed with spaces (or another symbol depending on used language) with three-digits groups. For example:

  2 365 121
     10 115.16
         25.145 17

We can write \num 2365121 or \num 10115.1 or \num 25.14517 and the grouping will be calculated by the macro.

\def\num#1 {\isinlist{#1}{.}\iftrue \ea\numG \else \ea\numH\fi#1\relax}
\def\numG#1.#2\relax{\numH#1\relax\decimaldot\numC#2\relax\relax\relax\relax\relax}
\def\numH#1\relax{\tmpnum=0 \numA #1\relax \numB#1\relax\relax\relax\relax\relax}
\def\numA#1{\ifx\relax#1\empty \else \incr\tmpnum \ea\numA \fi}
\def\numB{\edef\tmp{\the\tmpnum}\divide\tmpnum by3 \multiply\tmpnum by3
   \ifcase \numexpr\tmp-\tmpnum\relax \ea\numC \or \ea\numD \or \ea\numE\fi}
\def\numC#1#2#3{#1#2#3\ifx\relax#3\empty\else\ea\numF\fi}
\def\numD#1{#1\numF}
\def\numE#1#2{#1#2\numF}
\def\numF#1#2#3{\ifx\relax#1\empty \else \numsep #1#2#3\ea\numF\fi}
\def\numsep{\,}
\def\decimaldot{.}

The macro \num reads the number separated by space. If it incudes period then the front part is processed by \numH and the decimal part by \numC. The front part is processed in two steps. First, the macro counts the number of digits by \numA and then it inserts spaces by \numB,...\numF.

We can use this macro in tables with Nn declarator (see OpTeX trick 0039), but we must add the & symbol at the end of the \numH macro:

\def\numH#1\relax{\tmpnum=0 \numA #1\relax \numB#1\relax\relax\relax\relax\relax&}

\def\_tabdeclareN{\the\tabiteml \hfil \num ##\_unsskip}
\def\_tabdeclaren{##\_unsskip\hfil\the\tabitemr}

\table{|c|Nn|}{\crl
   first:   & 23433.1       \cr
   second:  &    55.131415  \crl
}

(0040) -- P. O., 2021-02-06


Expandable token-per-token scanner

We define \def\dotoken#1{do something with #1} and we can use \scantokenlist{token list}. The macro \scantokenlist calls \dotoken repeatedly to each token from the given token list. All tokens are prepared "as is" in the #1 parameter (including category code), only { and } are given as tokens with the same ASCI code but category 12.

The implementation follows:

\def\scantokenlist #1{%
   \immediateassignment\edef\tmp{\scantokenlistA #1{\final_token} }%
   \immediateassignment\edef\tmp{\ea\scantokenlistC \tmp \final_token}%
   \ea\scantokenlistD\tmp \final_token
}
\def\scantokenlistA #1#{\unexpanded{#1}\scantokenlistB}
\def\scantokenlistB #1{\ifx\final_token#1\empty\else
   \string{\scantokenlistA#1{\final_token}\string}\ea\scantokenlistA\fi}
\def\scantokenlistC #1 #2{\ifx\final_token#2\empty\unexpanded{#1}\else
   \unexpanded{#1{ }#2}\ea\scantokenlistC\fi}
\def\scantokenlistD #1{\ifx\final_token#1\else \dotoken{#1}\ea\scantokenlistD\fi}
\def\final_token{\final\stop\mark}

%% Test:
\def\dotoken#1{^^Jtoken: \string#1}
\message{\scantokenlist{a{h}#a {\test u{f}f muf}f.}}

First, \scantokenlistA converts { and } to category 12. It is used in \edef with \immediateassignment which makes \edef expandable. This is LuaTeX feature. Second, \scantokenlistC converts spaces to spaces surrounded in { }. Third, \scantokenlistD applies \dotoken to each token in the preprocessed token list.

If you want to return converted token list to the the original token list ({,} have catcodes 1,2) then you can define

\def\dotoken#1{\ea\ifx\string{#1{\else\ea\ifx\string}#1}\else\noexpand#1\fi\fi}

and run \scantokenlist in \edef, for example

\edef\mylist{\ea\scantokenlist\ea{\mylist}}

(0042) -- P. O., 2021-02-06


Nested brackets of another type than {}

TeX checks the nested brackets only for one type of brackets: {}. OpTeX uses macros sometimes with the parameters surrounded by [] brackets, but they cannot be simply nested. If you write (for example) \label[a[b]c] then you get the label a[b and the text c] is printed. You can check the pairs and nesting of another type of brackets than {} by the \ensurebalanced macro:

% \ensurebalanced left-bracket right-bracket macro-to-run {first-attempt}
\def\macro[#1]{\ensurebalanced[]\macroA{#1}}
\def\macroA#1{the parameter "#1" has balanced brackets [].}
for example:
\macro[a[b]c] prints: the parameter "a[b]c" has balanced brackets [].

The \label macro can be redefined in order to balanced [] brackets can be nested:

\def\tmp{\def\labelA##1}\ea\tmp\ea{\label[#1]}
\def\label[#1]{\ensurebalanced[]\labelA{#1}}

The \ensurebalanced macro is defined by:

\def\ensurebalanced#1#2#3{\immediateassigned{%
   \def\balopen{#1}\def\balclose{#2}\let\balaction=#3%
   \def\readnextbal##1##2#2{\ensurebalancedA{##1#2##2}}}%
   \ensurebalancedA}
\def\ensurebalancedA#1{\isbalanced#1%
   \iftrue\afterfi{\balaction{#1}}\else\afterfi{\readnextbal{#1}}\fi}
\def\isbalanced#1\iftrue{\immediateassignment\tmpnum=0 \isbalancedA#1{\isbalanced}}
\def\isbalancedA#1#{\countbalanced#1\isbalanced \isbalancedB}
\def\isbalancedB#1{%
   \ifx\isbalanced#1\afterfi{\cs{ifnum}\tmpnum=0 }\else\ea\isbalancedA\fi}
\def\countbalanced#1{\ea\ifx\balopen #1\immediateassignment\incr\tmpnum\fi
                     \ea\ifx\balclose#1\immediateassignment\decr\tmpnum\fi
                     \ifx\isbalanced#1\else\ea\countbalanced\fi}

The macro \ensurebalanced is fully expandable using LuaTeX primitives \immediateassigned and \immediateassignment.

If the parameter includes {...} then balancing is not counted inside this pair. It. means that balancing of {...} has higher priority.

The heart of this macro is \ensurebalancedA and \readnextbal. They say (roughly speaking):

\ensurebalancedA #1 -> \isbalanced #1\iftrue \balaction{#1}\else \readnextbal{#1}
\readnextbal #1#2] -> \ensurebalancedA{#1]#2}

The \readnextbal reads next part of the text until next ] occurs.

(0043) -- P. O., 2021-02-06


Breaking URL at any point

Breaking URLs to more lines can be customized by \_urlxskip (inserted between normal characters), \_urlbskip (inserted after :// / . ? = - &), \_urlskip (inserted before these special characters) and \_urlgskip (inserted by \|). By default, URL is breakable at any point (since OpTeX version May 2021) because \_urlxskip is defined by \penalty9990 and there is a small amount of stretchability.

If we dislike such stretchability between characters, we can declare breaking possibiity with ragged right, when URL is broken:

\def\_urlxskip{\nobreak\hfil\penalty9900 \hfilneg}
\def\_urlbskip{\nobreak\hfil\penalty100 \hfilneg}

Suppose, we want to allow breaking URLs only after / . ? = & and breaking using \|, moreower small stretching between all characters in URL are allowed. Then we can use following settings:

\slet{_ur:-}{undefined} % disallow breaking after -
\_def\_urlxskip{\_penalty10000\_hskip0pt plus0.03em\_relax}

If we want to break long URLs only manually using \| inside URL text then we can define:

\let\_urlxskip=\nobreak
\let\_urlbskip=\nobreak
\let\_urlskip=\nobreak
\def\_urlgskip{\hfil\break}

You can try to show, where glues are added to the URL text by following experiment:

\def\_urlxskip{@}
\def\_urlbskip{*}
\def\_urlskip{§}
\def\_urlgskip{!}
\url{http://petr.olsak.net/op\|tex}

You get: h*t*t*p§:§/§/@p*e*t*r§.@o*l*s*a*k§.@n*e*t§/@o*p!t*e*x but a hyperlink to real http://petr.olsak.net/optex is created (if \hyperlinks is declared).

(0052) -- P. O., 2021-05-12


PerPage package features

We implement the functionality from LaTeX perpage.sty package. User can declare \newcount\mycounter and then the \mycounter can be used by following macros:

  • \incrpp\mycounter increments \mycounter by one and resets (or increments) the internal counter counted from initial value (usually from one) per each page.
  • \thepp\mycounter prints (expands to) the value of the internal counter prepared by \incrpp.
  • \truepp expands to the true page number if it is used immediatelly after \incrpp\mycounter.
  • \thepplast\mycounter{pageno} expands to the last value of the internal counter at given page {pageno}. This can be used in the \output routine if you want to count the number of objects at given page.

Typically, the \incrpp\mycounter \thepp\mycounter are used in this couple. Or use: \incrpp\mycounter \truepp/\thepp\mycounter.

You must run TeX twice to get references right.

The internal counter is reset to 1 at begin of each page. If you like other value, use \sdef{resetpp:mycounter}{2} for example. Note that "mycounter" is the name you our counter without backslash.

Example:

\newcount\mycounter
\headline={The number of objects: \thepplast\mycounter{\the\pageno}\hss}

xx: \incrpp\mycounter \thepp\mycounter,
yy: \incrpp\mycounter \thepp\mycounter.
\vfil\break
zz: \incrpp\mycounter \thepp\mycounter,
uu: \incrpp\mycounter \thepp\mycounter,
vv: \incrpp\mycounter \thepp\mycounter.
\bye

Implementation:

\openref
\def\thepp#1{\trycs{pp:\csstring#1}{?\the#1}}
\def\thepplast#1#2{\trycs{lastpp:\csstring#1:#2}{?\the#1}}
\def\truepp{?\the\pageno}
\def\incrpp#1{\incr#1%
   \isdefined{_pgref:pp:\csstring#1:\the#1}\iftrue
      \edef\tmp{\cs{_pgref:pp:\csstring#1:\the#1}}%
      \ea\ifx\csname currpp:\csstring#1\endcsname\tmp
         \sxdef{pp:\csstring#1}{\the\numexpr\cs{pp:\csstring#1}+1\relax}%
      \else \sxdef{pp:\csstring#1}{\trycs{resetpp:\csstring#1}{1}}%
         \sxdef{currpp:\csstring#1}{\tmp}\fi
      \xdef\truepp{\ea\ea\ea\extractpp \csname currpp:\csstring#1\endcsname}%
      \sxdef{lastpp:\csstring#1:\truepp}{\cs{pp:\csstring#1}}%
   \else \incr\_unresolvedrefs \openref \fi
   \_ewref \_Xlabel {{pp:\csstring#1:\the#1}{}}%
}
\def\extractpp[#1]#2{#2}

The \incrpp does whole work. It increments the counter and saves the label in the form "pp:counter-name:counter-value" using \_ewref to the ref file. The effect is the same as when the normal \label[...] is saved to the ref file. The \incrpp knows the actual page (where the labes was saved) in the second TeX run using \cs{_pgref:label}. The effect is the same as when \pgref is used. If the current page is new then \incrpp sets the internal macro \cs{pp:counter-name} to the initial value, else it increments the value of the internal macro. The \cs{currpp:counter-name} includes the page where last \incrpp\countername was used. The \cs{lastpp:counter-name:pageno} includes the last internal number from \cs{pp:counter-name}. The \truepp is defined by \incrpp too as expanded \cs{currpp:counter-name} where the real pageno is extracted, because \cs{currpp:counter-name} has the format [pg:gpageno]{pageno} (see section 2.22, sequence \_Xlabel, in the OpTeX manual).

The \thepp expands to the \cs{pp:counter-name} and \thepplast expands to the \cs{lastpp:counter-name:pageno}.

(0051) -- P. O., 2021-04-01


Filecontents package features

We implement something similar to filecontents.sty LaTeX package. A user can write:

\begfile {filename.tex}
% file generated from source
\macros \macros \macros ...
   Text text text.
\endfile

and the filename.tex is written to the current directory with given contents.

Implementation:

\newwrite\outfile
\def\begfile #1 {\immediate\openout\outfile={#1}%
   \begingroup \_setverb \endlinechar=`\^^J \begfileA
}
\ea\def\ea\begfileA\ea#\ea1\ea^^J\csstring\\endfile#2^^J{\endgroup
   \immediate\write\outfile{#1}\immediate\closeout\outfile
}

The \_setverb sets the reading of the argument to verbatim mode. And the \endlinechar is set. Then the parameter is read and written to the given file. Elementary my dear Watson.

(0057) -- P. O., 2021-04-10


Lua matters

Processing lua code with normal characters

The \directlua primitive processes its argument with current category codes of characters. It means that you probably cannot directly use characters \, %, and you cannot use -- as prefix of lua comments because the whole parameter of \directlua is interpreted as only single line.

Of course, you can create a *.lua file and use it by "loadfile" or "require" functions. But sometimes, it can be practical to have a native lua code in a TeX macro file. We implement the pair \beglua ...\endlua. The lua code between them can include % and \ as normal characters, more consecutive spaces are really more spaces and the lines are interpreted by lua interpreter, so lua comments prefixed by -- are possible.

The \beglua...\endlua pair is equivalent to \begin{luacode*}...\end{luacode*} from the LaTeX package luacode. The \begLUA...\endLUA pair is equivalent to \begin{luacode}...\end{luacode}, i.e. the macros \foo can be inside the lua code and they are fully expanded before lua interpreter reads the code.

Moreover, you can use \logginglua at the begining of your document to see the processed lua codes in the log file.

The \beglua...\endlua, \begLUA...\endLUA are based on changing category codes. They cannot be used in macro bodies. Typical usage is:

\beglua
function myfunction (...) -- lua comments are possible
  ...
  tex.print(string.format("%04X", num)) -- normal % can be used here
  ...                                   -- TeX comments by % are not allowed here
  end
\endlua

\def\mymacro{...\directlua{myfunction(parameters)}...}

Implementation:

\newtoks\luamacros
\luamacros={\let\\=\nbb \edef\%{\csstring\%}\edef\#{\csstring\#}\let\n=\relax}
\def\beglua{\savelineno\bgroup \_setverb \endlinechar=`\^^J \begluaA}
\ea\def\ea\begluaA\ea#\ea1\csstring\\endlua^^J{\egroup\debuglua{#1}\directlua{#1}}
\def\begLUA{\savelineno\bgroup \_setverb \endlinechar=`\^^J \catcode`\\=0 \begLUAA}
\def\begLUAA#1\endLUA^^J{\the\luamacros\debuglua{#1}\directlua{#1}\egroup}
\def\savelineno{\edef\tmp{\the\numexpr\inputlineno+1}}
\let\debuglua=\ignoreit
\def\printloglua#1{\wlog{lua code processed (l.\tmp):^^J#1-------------}}
\def\logginglua{\let\debuglua=\printloglua}

The parameter separated by \endlua is read in verbatim mode (initialized by \_setverb) and when \endlinechar=`\^^J. Then this parameter is processed by the \directlua primitive. The \luamacros token list is used by \begLUA...\endLUA pair, you can add more macros here.

(0054) -- P. O., 2021-04-04


Printing time, date

The current date and time are saved in the TeX primitive registers \year (current year), \month (current month), \day (the day of the the month) and \time (minutes from the last midnight) when TeX is started. That is all. The following macro code declares non-primitive registers \hours, \minutes, \seconds and \weekday. The values of these registers can be initialized by:

\sethours   % sets current \hours and \munutes from \time
\setminutes % equivalent to \sethours
\setseconds % sets \seconds from OS time
\setweekday % sets \weekday as current day in the week (0=Sun...6=Sat)
            % from \day, \month, \year

Moreover, the macro \Othe is provided: it behaves like \the but it keeps two-digits format. So, \the\minutes can expand to 7, but \Othe\minutes expands to 07. Examples:

\sethours \setseconds
Current time is \the\hours:\Othe\minutes:\Othe\seconds.

\setweekday
Current day in the week is
\ifcase\weekday Sun\or Mon\or Tue\or Wed\or Thu\or Fri\or Sat\fi.

Today is
\ifcase\month\or Jan\or Feb\or Mar\or Apr\or May\or Jun\or Jul\or
   Aug\or Sep\or Nov\or Dec\fi, \the\day, \the\year
\ or in another format: \the\year/\Othe\month/\Othe\day.

Of course, you don't have to use just such abbreviations and such a format as shown in the example above. You are free to create your macros with arbitrary format and/or language. You can use \_mtext if you want to create a multilingual document or multilingual macros. You can get inspiration from section 2.37.3 of OpTeX documentation where the multilingual \today macro is defined.

You can set \year, \month, \day to arbitrary values (not necessarily current) and then you can process your macros for printing or use \setweekday to get the day in the week in given date.

Implementation is based on calculations at lua level:

\newcount\hours \newcount\minutes \newcount\seconds \newcount\weekday

\def\setminutes{%
   \minutes=\directlua{tex.sprint(\the\time\pcent 60)}\relax
   \hours=\directlua{tex.sprint(math.floor(\the\time/60))}\relax
}
\let\sethours=\setminutes
\def\setseconds{\ea\setsecondsA\pdffeedback creationdate\relax}
\def\setsecondsA#1#2#3#4#5#6#7#8#9{\setsecondsB}
\def\setsecondsB#1#2#3#4#5#6#7#8\relax{\seconds=#6#7\relax}
\def\setweekday{\weekday=\directlua{% Zeller's algorithm:
      local m, y, K, J
      m = \the\month \ifnum\month<3 +12 \fi
      y = \the\year  \ifnum\month<3 -1 \fi
      K = y \pcent 100
      J = math.floor(y/100)
      tex.sprint((\the\day + math.floor((13*(m+1))/5) + K +
         math.floor(K/4) + math.floor(J/4) - 2*J + 6)\pcent 7)
   }\relax
}
\def\Othe#1{\ifnum#1<10 0\fi\the#1}

(0055) -- P. O., 2021-04-06


Listings of all nodes in selected pages

We create macro "\showpglists list-of-pages" which prints the list of all nodes in selected pages to the log and to the terminal for debugging purposes. For example

\showpglists 3,4,7

prints all nodes of pages 3, 4, and 7.

This OpTeX trick is a simple example of usage of the pre_shipout_filter callback declared in OpTeX from version 1.04. The package nodetree is required for the listings of nodes. The implemetation:

\def\addshowpglists{\directlua{
   local nodetree = require("nodetree")
   callback.add_to_callback("pre_shipout_filter", function(head)
         nodetree.print(head)
         return head
      end, "showpglists") }}
\gdef\removeshowpglists{\directlua{
   callback.remove_from_callback("pre_shipout_filter", "showpglists") }}

\def\showpglists #1 {\addto\showpgs{#1,}}
\def\showpgs{,}

\addto\_begoutput{%
   \ea\isinlist\ea\showpgs\ea{\ea,\the\pageno,}\iftrue
      \addshowpglists
      \addto\_endoutput{\removeshowpglists}%
   \fi
}

The callback is registered in \_begoutput only if \the\pageno is in the page list \showpgs. Then it used in \_preshipout and unregistered in \_endoutput.

(0063) -- P. O., 2021-07-03


Sections

New level of sections

OpTeX defines \chap, \sec and \secc. A new level \seccc is not defined, but we can do it as an exercise of sections programming in OpTeX. The \chap ahs level 1, \sec has level 2, \secc has level 3. So, we will declare the 4th level of sections.

Note that OpTeX declares \secl4, \secl5, etc. but all these levels of sections are unnumbered, without copying to table of contents and with an only very simple design.

We can define \seccc analogical like \secc already defined in OpTeX:

\newcount \_secccnum
\def \_thesecccnum {\_othe\_chapnum.\_the\_secnum.\_the\_seccnum.\_the\_secccnum}

\_optdef\_seccc[]{\_trylabel \_scantoeol\_inseccc}

\_def\_inseccc #1{\_par \_sectionlevel=4
   \_def \_savedtitle {#1}% saved to .ref file
   \_ifnonum \_else {\_globaldefs=1 \_incr\_secccnum \_secccx}\_fi
   \_edef \_therefnum {\_ifnonum \_space \_else \_thesecccnum \_fi}%
   \_printseccc{\_scantextokens{#1}}%
   \_resetnonumnotoc
}
\public \seccc ;

We must implement resetting new counter at \secc level. All concept of reseting counters are copied here for greater clarity.

\_def \_chapx {\_secx   \_secnum=0   \_lfnotenum=0 }
\_def \_secx  {\_seccx  \_seccnum=0  \_tnum=0 \_fnum=0 \_dnum=0 \_resetABCDE }
\_def \_seccx {\_secccx \_secccnum=0 }
\_def \_secccx {}

Finally, we must declare a design of the \seccc:

\_def\_printseccc#1{\_par
   \_abovetitle{\_penalty-100}{\_medskip}
   {\_bf \_noindent \_raggedright \_printrefnum[@\_quad]#1\_nbpar}%
   \_nobreak \_belowtitle{\_smallskip}%
   \_firstnoindent
}

If we want to see \seccc in the table of contents, then we must declare the printing rule of the 4th level of sections:

\_sdef{_tocl:4}#1#2#3{\_advance\_leftskip by2\_iindent \_cs{_tocl:2}{#1}{#2}{#3}}

Now, \seccc works in the table of contents and in PDF outlines too:

\hyperlinks \Green \Green
\outlines0

\maketoc

\chap  My chapter
\sec   Special section
\secc  This is subsection
\seccc New level of sub-subsection
Text.
\seccc Next text
Text.

\end

(0033) -- P. O., 2021-01-14


Adding \part, the highest level of sections

OpTeX supports \chap, \sec, \secc, but not \part, i.e. the outermost level of them. We suppose that user can define it including required design if it is needed. The example of \part macro follows:

\newcount\partnum
\eoldef\part#1{\vfil\break
   \incr\partnum \_chapnum=0 \_chapx % reset counters
   \vglue100pt
   \incr\tocrefnum \dest[toc:\the\tocrefnum] % destination from TOC and outlines
   \centerline{\typosize[20/]\bf Part \the\partnum:\quad #1} % Title
   \_ewref\_Xtoc{{0}{part}{}{}#1} % TOC line, \part has level 0
   {\nopagenumbers \vfil\break}   % single page without pageno
}

You can try it:

\hyperlinks \Green \Green
\outlines0
\maketoc

\part Book One

\chap My chapter
\sec  Special section
\secc This is subsection
\secc Next subsection
Text.

\chap Next Chapter
\sec  next sec

\part Book Two

\bye

Note, that table of contents is created properly and correct tree structure of outlines is prepared too.

(0059) -- P. O., 2021-06-01


References

Flexible reference texts

In OpTeX, \ref[label] prints an active text in fixed format which is only a number of the section or the figure or whatever. Maybe, we want to print more active text (for example, with the name of linked object). This OpTeX trick creates more flexible \ref macro. If \ref[label] is not followed by {text} immediatelly then it behaves as standard OpTeX's \ref. But \ref[label]{text} prints active "text". This "text" should include the @ character which is replaced by the number of the referenced object. For example:

\hyperlinks\Blue\Blue

See section \ref[label] or see \ref[label]{section @}.

creates: See section 65 or see section 65.

The implementation follows:

\def\ref[#1]{\def\tmp{#1}\futurelet\next\refA}
\def\refA{\ifx\next\bgroup\ea\refB\else\refB{@}\fi}
\def\refB#1{\def\tmpb{#1}%
   \isdefined{_lab:\tmp}\iftrue
      \replstring\tmpb{@}{\cs{_lab:\tmp}}\_ilink[ref:\tmp]{\tmpb}%
   \else
      \replstring\tmpb{@}{??}\tmpb
      \_opwarning{label [\tmp] unknown. Try to TeX me again}%
      \_incr\_unresolvedrefs \_openref
   \fi
}

The original \ref macro is redefined. The existence of opening brace "{" is tested by \futurelet and the @ is replaced by \replstring. The \cs{_lab:label} expands to the number of the referenced object.

(0065) -- P. O., 2021-08-20


Slides

Navigation bar for selecting pages

The following code creates a clickable navigation bar for selecting pages/slides in \slides style. You can insert it to the file op-slides.tex (between lines \backgroundpic{op-slides-bg.png} and \verbchar` and try it. You must process the document by OpTeX twice.

\pgbackground={\vbox to0pt{\_copy\_bgbox\_vss}
     \ifnum\_slidelayer=0 \_slidelayer=1 \fi
     \ifnum\pageno>\thispage
        \xdef\thispage{\the\pageno}\xdef\maxlayer{\_cs{_p:\thispage}}\fi
     \immediate\_wref\_sdef{{_p:\_the\_pageno}{\_the\_slidelayer}}
     \_sxdef{_p:\_the\_pageno}{\_the\_slidelayer}
     \nointerlineskip
     \hbox to\pdfpagewidth{\hss\vbox{\null\slideslist}\kern5pt}
}
\def\prepslidepages{\openref\def\slideslist{}%
  \if?\lastpage \else
     \tmpnum=0
     \fornum 1..\lastpage \do{%
         \advance\tmpnum by0\_cs{_p:##1}\edef\tmp{\the\tmpnum}%
         \addto\slideslist{\slidepage{##1}}%
         \ea\addto\ea\slideslist\ea{\ea{\tmp}}%
         }%
  \fi
}
\def\thispage{0}
\prepslidepages

\def\slidepage#1#2{%
  \hbox{\Grey \ifnum\pageno=#1 \printpagenum \Blue \fi \pagerectangle{#2}}
}
\def\printpagenum{%
  \llap{\bf\the\pageno \ifnum\_slidelayer=0\maxlayer\else+\fi\ }%
}
\def\pagerectangle#1{\let\Blue=\relax \ilink[pg:#1]{\vrule height.8ex width.8ex}}%

\def\_endslides{\vfil\break \_decr\pageno \edef\lastpage{\folio}
   \let\slideslistA=\slideslist \prepslidepages
   \ifx\slideslistA\slideslist \else \advance \_unresolvedrefs by1 \fi
   \_byehook \_end
}

The \pgbackground iserts a background picture declared by \backgroundpic, then it writes to the .ref file

\sdef{_p:pageno}{slidelayer}

so, we have saved the number of last layer of each page after the .ref file is read again. Finally, \pgbackgroud prints the clickable bar as a \vbox with list of \hbox'es generated by \slideslist macro. This macro is prepared by \prepslidepages as a list of \slidepage{pageno}{globalpage}. The globalpage represents the given page with maximal layer and it is used as clickabe link to the page.

The \_endslides is redefined. It does checking of consistency of the navigation bar: The \slideslit is created again and compared with previous \slideslistA. If they are differ then \_unresovedrefs are advanced by 1 and \_byehook will write the "WARNING: Rerun to get references right". (You need the version of OpTeX bigger than 0.17, where \_endslides macro is used).

(0029) -- P. O., 2020-11-19


Languages

The (n)german.sty implementation

Old TeX german documents use shorthands with active " character for

  • dieresis: "a is ä, "U is Ü, etc. (comes from old days where ancient 7bit fonts were used),
  • double es: "s is ß, "S is SS, "z is ẞ, etc.,
  • quoutation marks: "`text"' is „text“,
  • special hyphenations: Zu"cker is Zucker or Zuk-/ker, Ro"ller is Roller or Roll-/ler,
  • special marks for hyphenations: "-, "|, "", "~ and "=.

See `texdoc ngerman` for more information.

The first three types seem to be obsolette today (but maybe you want to process an old TeX document or you have an unconfigured keyboard). The last two types can be usable. You can do this markup with following definitions:

\def\germanTeX   {\deolang \activeqq} % starts old German hyphens 1901 + "shorthands
\def\ngermanTeX  {\delang  \activeqq} % starts new German hyphens 1996 + "shorthands
\def\originalTeX {\enlang  \catcode`"=12 } % English hyphens + deactivates "shorthands

\def\activeqq{\adef"##1{\ifcsname qq:"\string##1\endcsname \cs{qq:"\string##1\_ea}\else
                        \opwarning{"\string##1 not declared}\string##1\fi}}
\def\qqdef"#1{\sdef{qq:"\string#1}}
\qqdef "a{ä} \qqdef "o{ö} \qqdef "u{ü} \qqdef "s{ß}  \qqdef "z{ẞ}  \qqdef "e{ë} \qqdef "i{ï}
\qqdef "A{Ä} \qqdef "O{Ö} \qqdef "U{Ü} \qqdef "S{SS} \qqdef "Z{SZ} \qqdef "E{Ë} \qqdef "I{Ï}

\qqdef "ck{\nobreak\discretionary{k-}{}{c}\allowhyphens k}
\qqdef "CK{\nobreak\discretionary{K-}{}{C}\allowhyphens K}
\qqdef "ll{\specdiscr l} \qqdef "mm{\specdiscr m} \qqdef "nn{\specdiscr n}
\qqdef "LL{\specdiscr L} \qqdef "MM{\specdiscr M} \qqdef "NN{\specdiscr N}
\qqdef "pp{\specdiscr p} \qqdef "rr{\specdiscr r} \qqdef "tt{\specdiscr t}
\qqdef "PP{\specdiscr P} \qqdef "RR{\specdiscr R} \qqdef "TT{\specdiscr T}
\qqdef "ff{\specdiscr f}
\qqdef "FF{\specdiscr F}
\qqdef "`{„} \qqdef "'{“} \qqdef "<{«} \qqdef ">{»}

\qqdef "-{\nobreak\-\allowhyphens}
\qqdef "|{\nobreak\discretionary{-}{}{\kern.03em}\allowhyphens}
\qqdef ""{\hskip0pt\relax}
\qqdef "~{\leavevmode\hbox{-}}
\qqdef "={\nobreak-\hskip0pt\relax}

\def\specdiscr#1{\nobreak\discretionary{#1#1-}{}{#1}\allowhyphens#1}
\def\allowhyphens{\nobreak \hskip0pt\relax}

The German environment is opened by \germanTeX or \ngermanTeX (as described in german.sty and ngerman.sty doc). You have to load Unicode font (\fontfam[lmfonts], for example) because preloaded EC fonts have bad encoding for ß, for example. Now, you can try (text from gerdoc.tex):

\fontfam[lmfonts]
\ngermanTeX

Der beim 6.~Treffen der deutschen \TeX-Interessenten in M"unster
festgelegte Befehlssatz wurde nach"-tr"aglich um einige Befehle
erweitert.
\bye

Note that usage of these macros are incompatible with \dequotes, i.e. with quotation marks used by \"text" and preferred in OpTeX.

(0036) -- P. O., 2021-01-22


Features of PDF viewer

Page labels shown in PDF viewer

OpTeX uses \gpageno as a counter which counts pages globally from one to the last page in the document. Moreover, plain TeX (and OpTeX too) declares \pageno and use it as page counter printed in footlines or headlines of the pages. There can be more separate sections in the document, each such a section has its own \pageno range and they can be printed in various format. For example, setting \pageno=-1 starts a page range printed in roman numerals: i, ii, iii, iv, etc.

If none is specified then PDF viewer knows nothing about \pageno ranges and their format. PDF viewer shows only \gpageno in its status bar. But you can do specification of \pageno ranges using \pdfcatalog:

\pdfcatalog{/PageLabels << /Nums [ index <<spec>> index <<spec>> ...etc. ] >>}

where index is \gpageno-1 and spec is specification of the format of the \pageno range. It informs the PDF viewer that given ranges start at index+1 global page and have given format. This format is used in the PDF viewer status bar. The spec can be:

/S/r   ... lowercase roman numerals
/S/R   ... uppercase roman numerals
/S/D   ... decimal arabic numerals
/S/A   ... letters A..Z
/S/a   ... letters a..z

Additional specification can be

/P (string) ... optional prefix used before the counter in given queue
/St number  ... starting number in given \pageno range (default is 1)

The following example is taken from PDF manual:

\pdfcatalog{/Pagelabels << /Nums [
      0 << /S/r >>
      4 << /S/D >>
      7 << /S/D /P (A-) /St 8 >>
   ] >>
}

which gives page labels: i, ii, iii, iv, 1, 2, 3, A-8, A-9, ...

You can accumulate your page specifications in your macro \pagespecs and use it at the end of your document (or when the last page range is started). For example:

\def\pagespecs{}
\pageno=-1 % Front matter
\edef\pagespecs{\pagepsecs \the\numexpr\gpageno-1 <</S/r>>}
...
\vfil\break
\pageno=1 % Main matter
\edef\pagespecs{\pagepsecs \the\numexpr\gpageno-1 <</S/D>>}
...
\vfil\break
\def\_folio{App-\the\pageno}  % Appendices
\edef\pagespecs{\pagepsecs
   \the\numexpr\gpageno-1 <</S/D /P (App-) /St \the\pageno >>}
\pdfcatalog {/PageLabels << /Nums [ \pagespecs ] >>}

(0056) -- P. O., 2021-04-09


Others

Version number of the format

The \optexversion macro expands to "x.yz Mon.Year" or "x.yz+ Mon.Year". The first one means official released version (uploaded to the CTAN) and the second one denotes versions of the format patched and uploaded to github.com but not released. When I decide to release next version, I add one to the minor number yz, upgrade the date information Mon.Year and remove the + mark. When I do first new commit to the released version, I add the + mark at github.com. Next commits do not change \optexversion.

We create an expandable macro \numversion which expands to xyz0 for released versions and xyz5 for patched versions. You can use it in the context \ifnum\numversion>number do something\else do something else\fi.

\def\numversion{\ea\numversionA\optexversion\relax}
\def\numversionA#1.#2#3#4#5\relax{#1#2#3\ifx+#45\else0\fi}

(0049) -- P. O., 2021-03-02


Showing differences between versions of the document

Suppose we have two versions of the same document: document-old.tex and document-new.tex. We can print document-new.tex with the added/changed parts colorized. Define:

\def\diffstart/{{\nolocalcolor\Red}}
\def\diffstop/{{\nolocalcolor\Black}}

\regmacro {\def\diffstart/{}\def\diffstop{}}
          {\def\diffstart/{}\def\diffstop{}}
          {\def\diffstart/{}\def\diffstop{}}

and run

wdiff -1 --start-insert='\diffstart/' --end-insert='\diffstop/' \
      document-old.tex document-new.tex > diff.tex
optex diff.tex

The resulting PDF will have the new phrases typeset in red color.

Because \localcolor is the default and we set these two colors as \nolocalcolor, then there can be problems. But most cases will work just fine. Moreover this solution is simple and effective.

Only new texts are highigted without any change of formatting of the document. If you need to see colored deleted texts, then you can view them on your ANSI terminal by using:

wdiff -n -w $'\033[30;41m' -x $'\033[0m' -y $'\033[30;42m' -z $'\033[0m' \
      document-old.tex document-new.tex | less -R

This trick was inspired by an idea from Daniel Gromada. He suggested it for the OPmac tricks set.

(0014) -- P. O., D. G. 2020-05-17


How to run OpTeX at Overleaf

Overleaf is LaTeX oriented site, but we can run most recent OpTeX version here if we do a set of obscure and dirty tricks.

First of all, we have to generate optex.fmt binary file at Overleaf. This file is typically binary incompatible in various computers and various versions of LuaTeX, so you cannot generate it at your computer and simply upload it. Create a zip file with optex files in your computer:

cd ??where optex/ directory is
zip -r optex-recent.zip optex/

Create a new Overleaf project and upload optex-recent.zip. Create the main.tex file with the following contents:

\documentclass[a4paper]{article}
\usepackage{bashful}

\begin{document}

\bash[script,stdout,stderr]
unzip -o optex-recent.zip
cd optex/base/
luatex -ini optex.ini
rm *.opm
\END

\end{document}

Note, that you must use "cd" with respect of the structure used in the ZIP file. If you download ZIP file from github, for example, then use "unzip -o OpTeX-master.zip" and "cd OpTeX-master/optex/base/".

Click "Recompile". The oputput PDF should show the messages from OpTeX format generation. Click on the icon just right to "Recompile" (logs and output files), scroll the window down and click on the button "Other logs & files". The optex.fmt should be here.

The "Other logs & files" are temporary files: Unfortunately, they are removed before each TeX run. So, you need to download optex.fmt to your computer and upload again to the Overleaf project. Do it now.

The same is true for unzipped files from optex-recent.zip. They are lost in the next TeX run. This is reason why we cannot use files from optex-recent.zip when processing OpTeX documents and we must to upload all *.opm files to the Overleaf project again. Create the subdirectory optex/ (lowercase letters) in the project and upload all *.opm files to this directory. The uploader accepts maximum 40 files per one upload action, so more than 80 files from OpTeX package need to be uploaded in three steps. The directory structure in the optex/ directory can be arbitrary, for example all *.opm files are directly in the optex/ directory. Alternative: you can upload not all *.opm files but only these files needed at OpTeX runtime: f-*.opm, hisyntax-*.opm, mathclass.opm, unimath-codes.opm, unimath-table.opm, usebib.opm, bib-*.opm, slides.opm.

Prepare the latexmkrc at main level of directories in the Overleaf project with the one-line content:

$lualatex = 'TEXINPUTS=./optex//:$TEXINPUTS luatex -fmt=optex %O %S';

Upload all files from OpTeX demo/ directory to the main directory level of Overleaf project, especially op-demo.tex and op-ring.png files. This is only for testing.

Select from main Overleaf menu: Compiler: LuaLaTeX, main document: op-demo.tex, Code check: Off.

Now, you have optex.fmt, latexmkrc, op-demo.tex, op-ring.png in the main directory. Try the buttom "Recompile" and wait a while (LuaTeX needs to initialize the OTF font databases when it runs first). If you see the PDF result: "Demonstration", congratulations! The first run gives the result without TOC, second "Recompile" gives TOC too.

If you have additional OpenType fonts not installed at OverLeaf, then you can save them into fonts/ directory in your project and modify the latexmkrc file:

$lualatex = 'TEXINPUTS=./optex//:$TEXINPUTS OPENTYPEFONTS=./fonts// luatex -fmt=optex %O %S';

(0022) -- P. O. 2020-05-29