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 font outlines this way:

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

which produces:

Text with 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:

\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\_resetattrs{\fntoutattr=\_noattr}
\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 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, they essentially just set the attribute to the right attribute value (allocating new one if necessary).

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 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).

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


Algorithms printed as pseudo-codes

The LaTeX package bundle algorithms enables to create algorithms formatted by controlled way (pseudo-codes). We create here something similar, but writing the sources is much more confortable than in LaTeX because the source is more similar to the printed result and without useless control sequences. Our pseudo-code is written between \begtt\algol and \endtt as usual verbatim text. The key-words mentioned at pages 1--8 in the algorithms LaTeX documentation are written directly in our source code and they are printed in bold font automatically. If you want to print another word in bold use a pair of !...!, i.e. write !word!. Texts between dollars $...$ are interpreted in math mode. All input characters between $...$ are non-verbatim, i.e. the math mode is interpreted as usual. Other text (outside math mode) is printed verbatim but in Roman font. The example shown at pages 9--10 of algorithms documentation can be prepared by:

code result

\begtt \algol
Require: $n ≥ 0$
Ensure: $y = x^n$
   $y ← 1$
   $X ← x$
   $N ← n$
   while $N ≠ 0$ do
      if $N$ is even then
         $X_{\rm new} ← X × X$
         $N ← N/2$
      else {$N$ is odd}
         $y ← y × X$
         $N ← N − 1$
      end if
   end while
\endtt

Note that the brackets {...} in our example are interpreted in normal TeX way in math mode: $X_{\rm new}$ but they are printed verbatim outside math mode: else {$N$ is odd}.

You can use symbols like ←, they makes your source code more readable. Or you can use common math control sequences in math mode, \leftarrow in this case.

The implementation of the \algol macro is based on hi-syntax macros of OpTeX:

\newtoks \_hisyntaxalg
\_hisyntaxalg = {
   \_hicolor K \bf  % Keywords
   \foreach {Require:}{Ensure:}{Input:}
      {while}{do}{if}{then}{else}{end}{function}{return}{print}
      {for}{all}{range}{continue}{repeat}{until}{loop}
      {not}{and}{or}{xor}{true}{false}
      \do {\replthis{\n#1\n}{\z K{#1}}}
}
\def\algolmath#1${\catcodetable\_optexcatcodes \scantextokens{#1}$}
\bgroup \lccode`~=`! \lowercase{\egroup \def\algolbf#1~}{{\bf#1}}
\def\algol{\catcode`$=3
   \everymath={\algolmath}%
   \def\_ttfont{\rm}%
   \adef!{\algolbf}%
   \hisyntax{ALG}%
}
\def\var#1{{\it\adef_{\vrule height.4pt width4pt\relax}#1}}

You can use \var{variable_name} inside the math mode. The variable_name is printed in italics.

You can add \replthis{:=}{\leftarrow} to the \_hisyntaxalg declaration if you want to write more legible $y := 1$ instead of $y \leftarrow 1$ in your source code. (We assume that you don't write the character ← directly due to a limitation of your keyboard or your text editor.) Or you can try to colorize your pseudo-code...

Another tip: You can change the indentation of printed result by adding

   \def\_indent{\_quitvmode \def\_dsp{ }\_spacefactor=2000\xspaceskip=.7em }

to the \_hisyntax declaration. Each indentation space in the source will have given \xspaceskip width in the output.

(0078) -- P. O. 2022-04-24


Verbatim lines referenced in text

We implement possibility to refer lines printed by \begtt...\endtt if these lines are numbered by \ttline>=0. User needs to select a character unused in the verbatim text (the § in the following example) and use \ttlineref this-character in the header after \begtt and the same character can be used in the verbatim text followed by [label]. Of course, you can use more such labels in single verbatim block. When the verbatim environment is closed then the \lref[label] expands to the the corresponding line number where the [label] was used. For example:

\ttline=0
\begtt \ttlineref §
first line
   second special line §[spec]
   third line
\endtt

Note that the line \lref[spec] is very interesting.

The string §[spec] in this example is not printed in the verbatim environment, it saves only the label [spec] for later use. The \lref[spec] expands to 2 in our example.

The following code implements this feature but only backward references (for sake of simplicity).

\def\ttlineref#1{\adef#1[##1]{\setlref{##1}}}
\protected\def\setlref#1{\sxdef{ttlin:#1}{\the\ttline}}
\def\lref[#1]{\trycs{ttlin:#1}{??}}

You can use the same labels in following verbatim blocks. They are silently rewritten by new line numbers. We suppose that the \lref[label] are used immediately after printed verbatim block and only with labels really declared in previous verbatim block.

You can use this trick with combination of previous OpTeX trick 0078

(0079) -- P. O. 2022-04-25


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

text around circle

\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


Keystrokes using special images

We create a macro \keystroke{text}. Similar macro is provided by keystroke.sty LaTeX package. For example

\keystroke{Q} \keystroke{E} \keystroke{R} \keystroke{T} \keystroke{Y}\par
\keystroke{PgUp} \keystroke{Enter}

gives following result:

keystrokes

The implementation:

\newdimen\keysize \keysize=1.3em
\newbox\keylbox  \newbox\keymbox  \newbox\keyrbox
\setbox\keylbox=\hbox{\picheight=\keysize \inspic{keystroke_left.pdf}}
\setbox\keymbox=\hbox{\picheight=\keysize \inspic{keystroke_middle.pdf}}
\setbox\keyrbox=\hbox{\picheight=\keysize \inspic{keystroke_right.pdf}}

\def\keystroke#1{\setbox0=\hbox{\setfontsize{mag.77}\sans\rm#1}%
   \leavevmode \lower.2\keysize \hbox{\copy\keylbox
      \tmpdim=\wd\keymbox
      \ifdim\wd0>\tmpdim \tmpdim=\wd0 \fi
      \ifdim\tmpdim>\wd\keymbox
         \pdfsave \pdfscale{\expr{\bp{\tmpdim}/\bp{\wd\keymbox}}}{1}%
            \rlap{\copy\keymbox}\pdfrestore
      \else \rlap{\copy\keymbox}\fi
      \raise.33\keysize \hbox to\tmpdim{\hss\box0\hss}\copy\keyrbox}%
}

There are three images: left side, middle and right side. These images are part of the keystroke.sty LaTeX package. We print the middle image in its natural width W if the text used on keystroke has its width less than W. The text is centered to the W size. If the text width is greater than W then we print the middle image with \pdfscale{tw/W}{1} where tw is the width of the text. It means that the middle image is deformed to its new width in order to include whole text. The left and right images are not deformed.

(0070) -- P. O. 2022-02-24


Frames with shadows

We define a macro \shadedframe {text paragraph} which creates the text in a shadedframe with the width given by \hsize. For example
\shadedframe {
  {\bf Title}:
  rweyu fiw wi ewyur uwr rwu wruwr rwu rwur uw qu fiw wi ewyur uwr rwu
  wruwr rwu rwur uw q fiw wi ewyur uwr rwu wruwr rwu rwur uw qwr.
}

outputs to

shaded frame

The implementation is based on the macro \frame and measuring boxes and adding boxes with given dimensions.

\newdimen\shadewidth
\def\shadedframe#1{{%
   \vvkern=4pt \hhkern=4pt \shadewidth=4pt \rulewidth=1pt \hsize=4cm % default values
   \Grey % frame color
   \setbox0=\hbox{\frame{\vbox{\Black \noindent\ignorespaces#1}}}%
   \edef\htzo{\the\ht0}\edef\wdzo{\the\wd0}%
   \LightGrey % shadow color
   \hbox{\vbox{\hbox{\rlap{\Yellow\vrule height\ht0 width\wd0 depth\dp0}\box0}%
               \nointerlineskip \moveright\shadewidth
                  \hbox{\vrule height\shadewidth width\wdzo}\kern-\shadewidth}%
         \kern-\shadewidth \vrule height\htzo width\shadewidth}%
}}

This problem is a good material for example how to create the macro with optional key/value paraeters, see section 2.10 in OpTeX manual.

\def\shadedframedefaults{% defaults:
   frame-color=\Grey,       % color of frame rules
   text-color=\Black,       % color ot text inside the frame
   shadow-color=\LightGrey, % color of shadow
   bg-color=\Yellow,        % background color inside the frame
   rule-width=1pt,          % width of rules used in the frame
   v-margins=4pt,           % vertical space between text and frame
   h-margins=4pt,           % horizontal space between text and frame
   shade-width=4pt,         % the width of the shadow
   text-width=4cm,          % width of the text inside the frame
}
\optdef\shadedframe [] #1{\bgroup
      \readkv\shadedframedefaults \readkv{\the\opt}%
      \vvkern=\kv{v-margins}\hhkern=\kv{h-margins}%
      \rulewidth=\kv{rule-width}\hsize=\kv{text-width}\relax
      \kv{frame-color}%
      \setbox0=\hbox{\frame{\vbox{\kv{text-color}\noindent\ignorespaces#1}}}%
      \edef\htzo{\the\ht0}\edef\wdzo{\the\wd0}%
      \kv{shadow-color}%
      \hbox{\vbox{\hbox{\rlap{\kv{bg-color}\vrule height\ht0 width\wd0 depth\dp0}\box0}%
                  \nointerlineskip \moveright\kv{shade-width}
                     \hbox{\vrule height\kv{shade-width}width\wdzo}\kern-\kv{shade-width}}%
            \kern-\kv{shade-width}\vrule height\htzo width\kv{shade-width}}%
   \egroup
}

For example,

\shadedframe [bg-color=\Blue, text-color=\White, text-width=6cm] {text}

is now possible.

(0071) -- P. O. 2022-02-28


Frames with rounded corners

We create the macro \roundframe {Title}{Text} for making colored frames with rounded corners. For example

\roundframe {Title here}
            {Text dgd adhkd had dsglj dagjadg fsj csgsd
             gs sgls fsglfs gfsl fglf gfs rtyr rire wrurey.}

creates:

rounded frame

The frame shown above was created with following parameters settings:

\def\fmtfirst  #1{\kern2pt\noindent\strut\White\bf #1\parstrut\kern1pt}
\def\fmtsecond #1{\kern3pt\noindent\strut\Black\rm #1\parstrut\kern1pt}
\def\betweenfirstsecond {\Yellow\hrule height2pt}
\roundframeparams {\hsize=6cm \leftskip=3mm \rightskip=3mm \corners=3mm
   \parindent=0pt \let\titlecolor=\Blue \let\bodycolor=\LightGrey
}
\def\bgoval{\inoval[\roundness=\corners \shadow=Y \lwidth=0pt]{\box1}}

The \fmtfirst declares formating of title part, the \fmtsecond declares formatting of text body part. The macro \betweenfirstsecond is used between title and body. \roundframeparams sets next parameters used localy when the frame is created. The \corners is diameter of rounded corners. The \titlecolor and \bodycolor are background colors of appropriate parts. The macro \bgoval need not be defined. If undefined then there are no additional effects which can be achieved by printing backgroud \inoval. We need it if we want to have \shadow=Y or \lwidth positive.

The implementation looks like this:

\newdimen\corners  \newtoks\roundframeparams
\def\parstrut{\par\kern-\prevdepth\kern\dp\strutbox}
\def\twobox#1#2#3#4{%
   \vbox{%
      \setbox0=\vbox{\fmtfirst{#2}}%
      \hbox{\rlap{#1\vrule height\ht0 depth\dp0 width\wd0}\box0}
      \setbox0=\vbox{\fmtsecond{#4}}%
      \betweenfirstsecond \nointerlineskip
      \hbox{\rlap{#3\vrule height\ht0 depth\dp0 width\wd0}\box0}
   }%
}
\def\roundframe#1#2{{%
   \the\roundframeparams \roundness=\corners
   \setbox0=\twobox\titlecolor{#1}\bodycolor{#2}%
   \setbox1=\vbox to\dimexpr\ht0-2\corners{}\wd1=\dimexpr\wd0-2\corners\relax
   \hbox{\ifx\bgoval\undefined \else \raise\corners\rlap{\bgoval}\fi
         \clipinoval .5\wd0 .5\ht0 \wd0 \ht0 {\box0}}%
}}

Note that we need not tikz:). Only OpTeX macros \clipinoval and \inoval are used. The macro \twobox \color{title}\color{text} prints the colored parts of the frame with sharp corners. Then the macro \clipinoval does rounded corners. If we need additional effects then we print \inoval as a part of \bgoval macro. It is printed at the background and with empty box1.

(0073) -- P. O. 2022-03-03


Adding ornaments to the background

We create pages decorated by ornaments like shown here. There is a LaTeX package pgfornament which makes an interface between pgf library (which provides huge amount of ornaments) and user. This package is designed only for LaTeX, so we choose appropriate ornament from pages 15--23 from pfgornament documentation and print it via LaTeX. For example:

\documentclass{article}
\usepackage{pgfornament}
\begin{document}
  \pagestyle{empty}
  \begin{tikzpicture}
     \pgfornament[width=2cm]{61} % ornament 61 is choosen
  \end{tikzpicture}
\end{document}

Apply pdfcrop output.pdf ornam.pdf in order to create cropped PDF image. This image will be used in our OpTeX document. The advantage is that we need not load whole tikz into our document, only single pdf file. For example, the background with four ornaments in the corners of the page can be declared as follows:

\newbox\ornament
\setbox\ornament=\hbox{\inspic{ornam.pdf}} % ornament is loaded

\newdimen\pgdist \pgdist=1mm
\pgbackground={
   \setbox0=\hbox to\dimexpr \pdfpagewidth-2\pgdist
      {\copy\ornament \hfil \rotbox{-90}{\copy\ornament}}
   \moveright\pgdist \vbox to\pdfpageheight{
      \kern\pgdist
      \vbox to0pt{\copy0 \vss}
      \vss
      \vbox to0pt{\vss \rotbox{180}{\box0}}
      \kern\pgdist
   }
}
\footlinedist=50pt

The \pgdist is the distance between ornament border and page border. It is set to 1mm here but warning: common laser printers are not able to print images so close to the edge of the paper.

(0080) -- P. O. 2022-05-05


Moving OpTeX's colors to TikZ environment

The macros for colors have different concept in OpTeX and in Tikz. In OpTeX, you can define a color macro for example by

\def\myColor{\setcmykcolor 1 0 .5 0}

or you can do color mixing by \colordef. On the other hand, Tikz uses the LaTeX concept where colors are defined as words (no control sequences) by \definecolor{myColor}{rgb}{data} and only rgb is supported by \definecolor.

Suppose, we have all colors prepared by OpTeX's macros and now we want to use them in a TikzPicture. For example:

\load [tikz]

\cmykcolordef\myColor{.5\Red + .7\Yellow}  % preparing colors in OpTeX

\tikzcolorlet {myColor} \myColor  % propagating the same color to TikZ

\tikzpicture [fill=myColor]
\fill (0,0) -- (1,0) -- (1,1) -- cycle;
\endtikzpicture

The example above does color mixing in cmyk by OpTeX macros, create \myColor as cmyk color and then puts this color to TikZ by \tikzcolorlet. No conversions are done, the same color in the same color space is used in TikZ. The color spaces cmyk, rgb and grey are supported.

The \tikzcolorlet macro is implemented here:

\def\tikzcolorlet #1#2{\ea\tikzcolorletA #2{#1}}
\def\tikzcolorletA #1#2{%
   \def\next {\tikzcolorletgrey}%
   \ifx#1\setcmykcolor \def\next{\tikzcolorletcmyk}\fi
   \ifx#1\setrgbcolor  \def\next{\tikzcolorletrgb}\fi
   \next #2
}
\def\tikzcolorletcmyk #1 #2 #3 #4 {\tikzcolorletB{cmyk}{#1,#2,#3,#4}}
\def\tikzcolorletrgb  #1 #2 #3    {\tikzcolorletB{rgb}{#1,#2,#3}}
\def\tikzcolorletgrey #1          {\tikzcolorletB{gray}{#1}}
\catcode`\@=11
\def\tikzcolorletB #1#2#3{\sdef{\string\color@#3}{\xcolor@{}{}{#1}{#2}}}
\catcode`\@=12

TikZ needs to define a macro \\color@colorName as

\xcolor@{}{}{color-space}{comma-separated-data}

and the code above does this job.

(0083) -- P. O. 2022-05-18


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


Equation marks for sub-equations

Sometime we want to declare a bunch of equations with the same numeric equation marks but with different suffixes, for example (1.1a), (1.1b). We create a macro \subeqmark suffix. The \subeqmark a starts the bunch of equations with a new number. Following \subeqmark b, \subeqmark c etc. uses the same equation number, they differ only by given suffixes. For example

$$ \eqalignno{
    x + 2y + 3z &= 600 & \subeqmark a \cr
   12x + y - 3z &= 7   & \subeqmark b[label] \cr
    4x - y + 5z &= -5  & \subeqmark c \cr
}
$$
See equation~\ref[label].
$$
  a^2 + b^2 = c^2 \eqno \eqmark
$$

prints the system of equations with numbers (1a), (1b), (1c) and the (1b) is referenced in the text. The next Pythagorean theorem has number (2).

The implementation is simple:

\def \_thednum    {(\the\_dnum\dnumpost)}
\def\dnumpost{}
\def\subeqmark#1{\def\dnumpost{#1}\ifx a#1\else \decr\_dnum\fi \eqmark}

(0074) -- P. O. 2022-03-08


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 unsupported 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
\resetmathchars \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

The \resetmathchars accepts a list of control sequences mentioned in the table printed by \input print-unimath.opm. Its syntax is

\resetmathchars family-name list-of-math-sequences ;
example:
\resetmathchars \stix \stareq \triangleq \veeeq \wedgeq ;

The implementation of \addUmathfont and \resetmathchars macros were moved to the OpTeX format from version 1.7+. Use them directly. If you have older OpTeX version you can declare:

\def\resetmathchars #1{\chardef\mafam=#1\relax \xargs \resetmathcharX}
\def\resetmathcharX#1{\ea\resetmathcharY
   \directlua{tex.print(string.format("\pcent08X", \the#1))}#1}
\def\resetmathcharY#1#2#3#4#5#6#7#8#9{% #9 is given \math-sequence
   \Umathchardef #9\numexpr"#3/2\relax \mafam "#4#5#6#7#8
   \Umathcode "#4#5#6#7#8=\numexpr"#3/2\relax \mafam "#4#5#6#7#8
}
\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 \resetmathchars macro saves the value of family-name to \mafam and applies \resetmathcharX to each math-sequence given in the list. The \resetmathcharX runs \directlua which returns the value of the math-sequence in 8-digits hexa number. First two digits give the family number (unsed), next digit is math-type multiplied by two and the rest is the Unicode slot number. The \resetmathcharY reads these digits and re-sets the given math-sequence by the \Umathchardef primitive and the code by the \Umathcode primitive.

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


Empty math slots from another math font

The previous OpTeX trick implements a macro for loading more Unicode math fonts and gives a macro \resetmathchars for setting individual math characters as characters from the newly loaded math font. Now we implement a method for doing \resetmathchars for all math characters which are missing in the basic math setting. For example, we use \fontfam[lm] and \input print-unimath.opm shows that doszens slots are empty. We can use \addUmathfont for STIXMath-Regular font where almost all slots are supported. Now, we need to fill all empty slots by characters from STIXMath-Regular:

\fontfam[lm]
\addUmathfont \stix {[STIXMath-Regular]}{} {}{} {} % additional font STIX loaded

% fills empty slots from \stix font:
\fillemptyslots \stix

% Testing the result:
\input print-unimath.opm

The \fillemptyslots macro is implemented here. We need to use macros \resetmathcharX and \resetmathcharY from the previous OpTeX trick:

\def\UnicodeMathSymbol#1#2#3#4{%
   \isinlist{\relax\mathord\mathop\mathbin\mathrel\mathpunct}#3\iftrue
      \def\mathsequence{#2}%
      \isinlist\intoperators#2\iftrue
         \edef\mathsequence{\csname\csstring#2op\endcsname}%
      \fi
      {\tracinglostchars=0 \global\setbox0=\hbox{$\mathsequence$}}%
      \ifdim\wd0=0pt \ea\resetmathcharX \mathsequence \fi
   \fi
}
\def\intoperators{\int\iint\iiint\oint\oiint\oiiint
   \intclockwise\varointclockwise\ointctrclockwise\sumint\iiiint\intbar\intBar
   \fint\pointint\sqint\intlarhk\intx\intcap\intcup\upint\lowint
}
\def\fillemptyslots#1{\chardef\mafam=#1\relax \input unimath-table.opm }

The macro defines the macro \UnicodeMathSymbol used in unimath-table.opm and then does \input of this file. All \mahtord,\matop,\mathbin,\mathrel and \mathpunct characters are checked and if they give an empty box then they are completed from the new math font. Integrals are defined as macros (see \_intwithnolimits in unimath-codes.opm), so corresponding \...op control sequences are checked and completed.

(0082) -- P. O. 2022-05-15


Original \cal from Computer Modern fonts

When \fontfam[lm] is used then LatinModern-Math Unicode font is used for math. The different calligraphic shapes are here than Knuth designed for his Computer Modern fonts. If you want to use his original calligraphic glyphs after \fontfam[lm], then you can use \orical instead \cal and the macro \orical is defined here:

\addto\_normalmath{%
  \_loadmathfamily 5 cmsy % CM Standard symbols
}
\_normalmath
\def\orical{\fornum`A..`Z\do{\Umathcode##1 "0 "5 ##1 }}

% For example:
\def\P{{\orical P}} % powerset

The new math family (5 in our case) is declared in the internal \_normalmath macro. This must be done after \fontfam[lm]. Then the \orical macro sets (locally) approriate math codes for all characters A..Z: the class is zero (Ord character], the family is 5 and the code slot is the ASCII code of given letter.

(0077) -- P. O. 2022-04-19


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


Two columns parallel side by side

We implement the macro \twoblocks{first column}{second column} which prints two columns side by side, the first line of both columns are vertically aligned. If there is no space at the current page, the columns are broken to more pages. The macro can be used for bilingual text columns. You can use more \twoblocks one by one. The first line of each \texblocks pair is vertically aligned again. Example:

\twoblocks{
  {\bf The text in English...} \lorem[1-2]
}
{
  {\bf The text in other language...} \lorem[3-4]
}
\twoblocks{
  {\bf Another text in English...} \lorem[5-12]
}
{
  {\bf Another text in other language...} \lorem[15-22]
}

Implementation:

\newdimen\pageblank
\newdimen\columnsht

\def\setcolumn{\advance\hsize by-\colsep \hsize=.5\hsize \penalty0 }
\def\twoblocks{\par
   \begingroup \hbadness=5000 \splittopskip=1em plus1fil \brokenpenalty=0
   \setbox0=\vbox\bgroup \setcolumn
      \aftergroup\twoblocksA
      \let\next=%
}
\def\twoblocksA{%
   \setbox1=\vbox\bgroup \setcolumn
      \aftergroup\twoblocksB
      \let\next=%
}
\def\twoblocksB{\setbox2=\vsplit0 to0pt \setbox2=\vsplit1 to0pt \twoblocksC}
\def\twoblocksC{%
   \penalty0 \vskip\parskip
   \ifdim\pagegoal=\maxdimen \pageblank=\vsize \else \pageblank=\pagegoal \fi
   \advance\pageblank by-\pagetotal
   \ifdim\pageblank<24pt \vfil\break \pageblank=\vsize \fi
   \ifdim\ht0>\ht1 \columnsht=\ht0 \dimen0=\dp0 \else \columnsht=\ht1 \dimen0=\dp1 \fi
   \ifdim\columnsht>\pageblank
      \setbox2=\vsplit0 to\pageblank \setbox3=\vsplit1 to\pageblank
      \line{\xvtop2\hss\xvtop3}
      \kern-\baselineskip \nobreak\vfil\break
      \expandafter\twoblocksC
   \else
      \line{\xvtop0\hss\xvtop1}
      \prevdepth=\dimen0
      \endgroup
   \fi
}
\def\xvtop#1{\vtop{\null\kern-\splittopskip\unvbox#1}}

The left column is accumulated into box0 by the \twoblocks macro. The \twoblocks {text} is transformed to

\setbox0=\vbox\bgroup...\let\next={text}

It means that the opening brace { is ignored because there is \brgoup inside the macro instead it. The right column is saved to box1 similar way by the \twoblocksA macro. The \twoblocksB macro prepares columns to printing: it adds the \splittopskip space at the top of the boxes. The \twobolcksC macro prints columns. If there is sufficient space on the current page (\pageblank) then the boxes are printed using \line with two \xvtop's. The \xvtop macro is the same like \vtop but a correction at the top of the box is added: the \null is exactly at the next line in the document and the baseline of the first line of the column is shifted to the baseline of the \null box. The \dimen0 is the depth of the last line of the larger column and it is set to \prevdepth in order to the next line in the document will be printed to the baseline grid. If there isn't sufficient space on the current page then we get only parts of box0 and box1 of the given height \pageblank using \vsplit primitive, we print these parts as columns by the \line macro and go to the next page and the macro \twoblocksC is recursively repeated.

(0086) -- P. O. 2022-05-30


Different formatting

Indented footnotes

The question on StackExchange includes the need of indented footnotes, like this:

--------------
  1 This is a long footnote
    at more than one line.

We can set this formatting by replacing \_textindent by \textllap in \_fnset:

\def\textllap#1{\noindent\llap{#1\enspace}\ignorespaces}
\addto\_fnset{\leftskip=\parindent \let\_textindent=\textllap}

(0067) -- P. O. 2022-02-02


Line numbers in the left side of the formatted text

The question on the tex.stackexchange asks for the line numbers in the left side of the formatted text. The macro package edmac mentioned here is not usable for OpTeX because it changes the output routine. If you want to change the output routine, you must to do it with recommendation given in the section 2.18 of the OpTeX manual, but old edmac macro package does't respect it.

The solution by wipet does not use edmac, but simply does a box manupulation at the post processing state (when \pend is processed):

\newcount\firstlinenum
\newcount\linenumincrement

\def\pstart{\par \setbox0=\vbox\bgroup}
\def\pend{\par \global\tmpnum=\prevgraf \egroup
   \multiply\tmpnum by\linenumincrement \advance\tmpnum by\firstlinenum
   \firstlinenum=\tmpnum \advance\tmpnum by-1
   \setbox0=\vbox{\unvbox0
      \setbox2=\hbox{}
      \loop
         \unskip\unpenalty \setbox1=\lastbox
         \ifvoid1 \else
            \global\setbox2=\hbox{\llap{\printnum}\box1\penalty0\unhbox2}
            \advance\tmpnum by-\linenumincrement 
            \repeat
   }\noindent\unhbox2 \par
}
\def\printnum{\setfontsize{mag.6}\rm\the\tmpnum\kern.8em}

The signle paragraph surrounded by \pstart...\pend is processed first in the \vbox. The lines from this paragraph are moved from the vertical list to a horizontal list in \box2. The line numbers are added here by \llap. The lines are separated by \penalty0 in the created horizontal list. This is a reason why putting this horizontal list to the paragraph processing again (after \noindend) creates desired paragraph. If you want to do this over more than single paragraph then you must to re-define \par (or \_par in the new OpTeX) in order to do this re-boxing at each paragraph-end between \pstart and \pend.

(0069) -- P. O. 2022-02-02


First line of the paragraph with a different font

The following macro \firstline{text of the paragraph} prints the paragraph with different font of its first line. This font is given by the \firstlineshape. For example:

\let\firstlineshape=\bf
\firstline{The text of the paragraph}
or
\def\firstlineshape{\setfontsize{at12pt}\bf \uppercase} % \uppercase must be last
\firstline{The text of the paragraph}

The implementation:

\def\firstline#1{\par
   \begingroup \hfuzz=\maxdimen
   \setbox0=\vbox{
      \parshape 2 0pt\hsize 0pt\maxdimen
      \firstlineshape{#1}\par
      \global\tmpnum=\prevgraf
      \fornum 2..\prevgraf \do{\setbox0=\lastbox \unskip\unpenalty}
      \global\setbox1=\lastbox
   }
   \ifnum\tmpnum=1 \endgroup \box1 \else
      \setbox0=\vbox{
         \parshape 1 0pt0pt
         \unhcopy1 \par
         \setbox0=\lastbox \setbox0=\hbox{\unhbox0}
         \ifdim\wd0=0pt \prevgraf=\numexpr\prevgraf-1\relax \fi
         \parshape \numexpr\prevgraf+1\relax
             \fornum 1..\prevgraf \do{0pt0pt }0pt\maxdimen
         #1\par
         \setbox0=\box2
         \loop
             \setbox0=\lastbox \unskip\unpenalty
             \ifdim\wd0=\maxdimen
                 \global\setbox2=\hbox{\unhbox0 \ifvoid2 \else\space\unhbox2 \fi}
         \repeat
      }
      \endgroup
      \noindent\box1\penalty0\unhbox2 \par
   \fi
}

In the first step, we create the first line and the rest is in the second (or more) lines with \maxdimen width. Using \fornum we remove the rest and save the first line in the \box1. If there is no more lines then the \box1 is printed else we go to the second step. The box1 is divided to the elements (each element is one line with \hsize=0pt) and the number of the elements N is \prevgraf (or \prevgraf-1 if the last element ends with discretionary). Then we create \parshape where there is N lines with 0pt width and next lines are with \maxdimen width. Only the lines with \maxdimen width are taken in the following \loop and saved into \box2. It is the rest of the pragraph text. Finally, the paragraph is created again using \box1 followed by \unhbox2.

(0076) -- P. O. 2022-04-15


Extra \vsize space for solving widows

Orphans and widows are single-line paragraph-parts which must not be alone at the beginning or at the end of pages. If we need to have exactly the same number of lines at all pages then we have problem with these orphans and widows. Very interesting solution which automatically adds \looseness is implemented in the package lua-widow-control. But sometimes this solution is not fully successful because \looseness is not applicable for arbitrary paragraph. Then we can decide, that two verso/recto pages should be one-line shorter or larger in order to avoid widows and orphans.

The solution presented here is manual-based. You can decide which pages should be shorter or larger in a parameter of \extravsizes macro. For example:

\parskip=0pt
\hsize=11cm
\vsize=\topskip \advance\vsize by25\baselineskip % 26 lines default
\footlinedist=35pt  % more space above pageno, default is 24pt

\extravsizes{ 2:+ 11:- 13:+ }

This example gives pages 2 and 3 larger, pages 10 and 11 shorter and pages 12 and 13 larger by one \baselineskip. You can specify only one page number from the verso/recto pair in the list of extra pages, as shown in the example. The second page from the pages pair is set to the same value automatically. The implementation follows:

\newdimen\vsizedefault
\def\extravsizes#1{%
   \foreach #1\do ##1:##2{%
      \tmpnum=##1
      \sxdef{vs:\the\tmpnum}{##2}
      \advance\tmpnum by \ifodd\tmpnum -\fi 1
      \sxdef{vs:\the\tmpnum}{##2}
   }
   \global\vsizedefault=\vsize \corrvsize  % for page 1
   \global\pgbottomskip=0pt plus1fil minus\baselineskip
}
\addto\_begoutput{\global\vsize=\vsizedefault}
\addto\_endoutput{\corrvsize}

\def\corrvsize{
   \global\vsize=\vsizedefault
   \ifcsname vs:\the\pageno\endcsname
      \global\advance \vsize by \csname vs:\the\pageno\endcsname \baselineskip
   \fi
}

The \extravsize defines macros \vs:pageno as + or -. It does this for given page in the list and for the second page in the pair too. Then it initializes \vsizedefault and sets the \pgbottomskip used in the output routine in order to allow shorter or larger pages without messages about overfull \vbox. The \corrvsize is called here for the page 1. All other \corrvsize's are called from in output routine from the \_endoutput macro (where the \pageno is increased for next page already). The \corrvsize macro advances \vsize by plus or minus one \baselineskip if the macro \vs:pageno is defined.

(0081) -- P. O. 2022-05-06


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


What page-number is current?

We want to ask sometimes what page-number is current. For example we want to put an image to the outer side of the page (to the left side at left page, to the right side at right page). We want to do somethig like this:

\ifodd ???
   \line{\hss \picw=.7\hsize \inspic{picture.jpg}} % right side
\else
   \line{\picw=.7\hsize \inspic{picture.jpg}\hss}  % left side
\fi

We can declare a special counter \picno for such pictures globally incremented and we save to the .ref file:

\_sxdef {pic::picno-value}{\selectpage}

When the .ref file is read during the next TeX run then the control sequence \pic::picno-value is defined as a macro with the current page number. We have to define the macro \selectpage using \refdecl. The whole example looks like this:

\refdecl{\def\selectpage{\_ea\_usesecond\_currpage}}
\def\setpage#1{\_openref \_ewref\_sxdef{{#1}{\_string\selectpage}}}

\newcount\picno

\incr\picno
\ifodd \trycs{pic::\the\picno}{0}
   \line{\hss \picw=.7\hsize \inspic{picture.jpg}} % right side
\else
   \line{\picw=.7\hsize \inspic{picture.jpg}\hss}  % left side
\fi
\setpage{pic::\the\picno}

You can see that \picno must be incremented in the place where we need to know the current page number. The \setpage must be added immediatelly after the box (or inserted into the box) where we need to know the current page number because its internal \write command (used in \setpage) must not be separated (to different pages) from its box.

(0075) -- P. O. 2022-03-15


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


Sorting phrases

We create a macro \sort with the syntax: \sort list of phrases \endsort. The macro sorts the phrases in the given list and prints them. Each phrase is in curly braces in the given list. For example:

\sort {first} {second} {third} {fourth} \endsort

prints: first fourth second third.

This OpTeX trick is meant as an example how to do sorting outside the index macros. The implementation follows:

\def\sort #1\endsort {\def\slist{}%
   \foreach #1\do{\ea\addto\ea\slist\ea{\csname+##1\endcsname}\sdef{+##1}{##1}}
   \_dosorting\slist
   \ea\foreach \slist \do{##1 }%
}

The main key to sorting is the \_dosorting macro from OpTeX. It reads the list of control sequencs in a macro. The list must be in the form \?first \?second etc. The first character od these cs names (? here, + in the given example) are ignored. The control sequences are sorted due to their names and the result is saved to the same macro (\slist in the example). The meanings of the sorted control sequences are irrelevant during sorting.

The macro \sort from our example initializes \slist as the empty macro and adds the control seqences using \foreach to the \slist in the form \+first \+second etc. Each control sequence is defined as the given phrase, for example \+first is defined as first, \+second as second etc. After \slist is sorted by \_dosorting\slist, we print it. We can do this printing simply by expanding \slist, but spaces between phrases are lost in this case. This is a reason why we print the result using the second \foreach.

(0068) -- P. O. 2022-02-11


Declaring macro parameters with more separators

We create \sepdef for declaring macros with a parameter separated by various separators. The syntax is:

\sepdef\macro{list of separators}{#1=parameter #2=separator}
% For example:
\sepdef\firstsentence{{.}{?}{!}}{{\it#1\Red#2}}

The example declares macro \firstsentence which scanns following text until first period or question or exclamation mark occurs. This text is treated as #1 and the actual separator is #2. Our example prints the next sentence in italic and prints the final character of the sentence in red.

The list of separators is in the format {first}{second}... Each separator can include more than single character.

The implementation:

\def\sepdef#1{%
   \def#1{\edef\seqname{\csstring#1}\def\param{}%
      \ea\scantoeol\csname sdAx\csstring#1\endcsname}%
   \sdef{sdAx\csstring#1}##1{\def\tmp{##1}%
      \ea\foreach\seplist\do{\replstring\tmp{####1}{\sep{####1}}}%
      \ea\scantosep\tmp\sep\end}%
   \begingroup \_setscancatcodes \catcode`{=1 \catcode`}=2 \sepdefA #1%
}
\def\sepdefA#1#2{\endgroup\def\seplist{#2}\sdef{sdEx\csstring#1}##1##2}

\def\scantosep#1\sep#2{%
   \addto\param{#1}%
   \ifx\end#2\addto\param{ }\afterfi{\ea\scantoeol\csname sdAx\seqname\endcsname}%
   \else \afterfi{\def\separator{#2}\def\tmp{}\scantosepA}%
   \fi
}
\def\scantosepA#1\sep#2{%
   \addto\tmp{#1}%
   \ifx\end#2\ea\scantosepB
   \else\addto\tmp{#2}\ea\scantosepA
   \fi
}
\def\scantosepB{\edef\tmp{\ea\noexpand\csname sdEx\seqname\endcsname{\param}{\separator}\tmp}%
   \scantextokens\ea{\tmp}%
}

The \sepdef\macro defines \macro which runs \scantoeol\sdAxmacro, i.e. reads single line in verbatim mode. \adAxmacro saves the read line to \tmp and does \replstring{what}{\sep{what}} for each "what" from separator list. Then the \ea\scantosep\tmp\sep\end is run. The \scantosep reads the data separated by \sep. If the next token is \end then we are at the end of line and we must to open next line. The \param macro is accumulated. If the next token is not \end, then we have found first separator and the next separators \sep at the same line are ignored by \scantosepA. Finally, \scantosepB runs \adExmacro{param}{sep} and does new \scatextokens. The macro \adExmacro was defined by \sepdefA.

Because the lines are read in verbatim mode first, the following example with closing brace at the second line works:

{\bf\firstsentence Is it a sentence
  over more lines?  Next idea.} End.

(0072) -- P. O. 2022-02-28


Scanning nested LaTeX environments

LaTeX have somewhat impractical syntax with its environments. For example:

\begin{a}
   preambule
   \begin{b}
       next text
       \begin{c}
          something inside c
       \end{c}
   \end{b}
   \begin{d}
      inside d
   \end{d}
\end{a}

We try to scan such text by our macros. The problem is that the separator \end used by primitive \def isn't sufficient to scan whole environment because there may be nested environments. For example, if we define:

\def\begin#1{\cs{begin:#1}}
\sdef{begin:a}{\scanenvto\text \afterscan{a}}
\def\afterscan#1{environment:#1, body: \meaning\text}

and run the code above, then we want to store whole body of the environment into the macro \text and then the \afterscan{a} macro is processed. This macro is defined here only for testing putrposes: it prints only environment name and the meaning of the absorbed text. Note, that the LaTeX package environ does the similar job.

The \scanenvto is defined here:

\def\scanenvto#1#2#3{%  #1: body macro, #2{#3}: what to do after scanning
   \def\doafterscanenvthis{\let#1=\tmp \envcheck{#3}#2{#3}}%
   \def\tmp{}\def\tmpa{}\scantoend
}
\long\def\scantoend#1\end#2{%
   \addto\tmp{#1}\addto\tmpa{#1}%
   \isinlist\tmpa\begin\iftrue
      \addto\tmp{\end{#2}}%
      \ea\redeftmpa\tmpa\endscanenvtext
      \ea\scantoend
   \else
      \def\envname{#2}\ea\doafterscanenvthis
   \fi
}
\long\def\redeftmpa#1\begin#2#3\endscanenvtext{\def\tmpa{#3}}
\def\envcheck#1{\ismacro\envname{#1}\iffalse \errmessage{unpaired evironments}\fi}

We read the following text to the first \end by \scantoend. If the text includes \begin, then we add the scanned \end{what} to the scanned text, we remove the text to the first \begin in the parallel absorbing macro \tmpa and run \scantoend again. It is recursive algorithm which works. If there is no next \begin in the \tmpa then the whole environment is scanned. We do \envcheck (if the name given by \begin is the same as the name given by end) and we run what a user needs to do after.

(0084) -- P. O. 2022-05-18


Macro parameters selectively expanded

Expl3 language (designed for LaTeX) allows to create macros which get their parameters in various states: full-expanded, no-expanded, once-expanded. We show how to do it. Suppose, we want to create a macro \macro with four unseparated parameters, first is unexpanded, second is fully expanded, third is once expanded and fourth is fully expanded. We create a "prefix-macro" \MPnxex where MP means "macro parameters", n means no-expanded, x means fully expanded, e means once-expanded. The usage is shown in following testing code:

\def\test{\testA aha}
\def\testA{A}

\def\macro #1#2#3#4{\message{\unexpanded{1:#1, 2:#2, 3:#3, 4:#4}}}

\MPnxex\macro \test \test \test \test

The macro above prints message: 1:\test , 2:Aaha, 3:\testA aha, 4:Aaha. It means that the first parameter is unexpanded, second is fully expanded, third is once-expanded and last is fully expanded when the \macro gets its parameters.

The "prefix-macro" \MPnxex is defined by:

\def\MPnxex #1#2#3#4#5{\ea#1\expanded{{\unexpanded{#2}}{#3}{\unexpanded\ea{#4}}{#5}}}

You can define other "prefix-macros" \MP... analogically. The principle is: \MP... designed for n \macro parameters must have n+1 parameters internally, first one is for the \macro itself. Others are parameters preprocessed for the \macro. The simplest \MP... macro prepares all parameters as fully-expanded:

\def\MPxxx #1#2#3#4{\ea#1\expanded{{#2}{#3}{#4}}}

If you want to create unexpanded first parameter, replace #2 by \unexpanded{#2}. If you want to create once-expanded first parameter, replace #2 by \unexpanded\ea{#2}. And replace the symbol x to the symbol n or e in the \MP... macro name, of course.

(0085) -- P. O. 2022-05-25


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

List of figures, list of tables

OpTeX provides only \maketoc for making table of contents. We create commands \makeLOF for creating a list of figures and \makeLOT for creating a list of tables.

We create macros \captionF resp. \captionT for the captions of the figure or of the table respectively with the following syntax:

\captionT [label]{A short caption} A detail caption \par
\captionF [label]{A short caption} A detail caption \par

All parameters are obligatory. The short caption is used in the generated list of figures/tables, the detail caption is printed immediatelly. The commands \caption/f or \caption/t cannot be used if you want to put the information about the figure/table to the generated list.

The implementation:

\_refdecl{%
\_def\lotlist{} \_def\loflist{}^^J
\_def\Xtab#1#2#3{\_addto\lotlist{\lline{#1}{#2}{#3}}\_ea\_addto\_ea\lotlist\_ea{\_currpage}}^^J
\_def\Xfig#1#2#3{\_addto\loflist{\lline{#1}{#2}{#3}}\_ea\_addto\_ea\loflist\_ea{\_currpage}}
}
\def\captionT [#1]#2{\caption/t[#1]\_ewref\Xtab{{#1}{\_thetnum}{#2}}\ignorespaces}
\def\captionF [#1]#2{\caption/f[#1]\_ewref\Xfig{{#1}{\_thefnum}{#2}}\ignorespaces}

\def\lline#1#2#3#4#5{\line{\hskip2em\llap{\bf#2 } #3 \_tocdotfill\ \_ilink[pg:#4]{#5}}}

\def\makell#1{\par
   \ifx\lotlist\undefined \opwarning{\noexpand#1 empty, try to run TeX again}
   \else #1\fi
}
\def\makeLOF{\makell\loflist}
\def\makeLOT{\makell\lotlist}

The \refdecl declares new commands used in the ref file in the format:

\Xtab{label}{table-number}{short caption}
\Xfig{label}{figure-number}{short caption}

These commands put the data to the \loflist or \lotlist in the format:

\lline{label}{number}{short caption}{gpagenum}{pagenum}

for each figure/table. The macro \lline defines the typography of each object used in the generated lists. You can change it, of course. For example, if you use \bf\ref[#1] insetad \bf#2 then you have the numbers in the list clickable (after \hyperlinks declaration).

(0066) -- P. O. 2022-02-01


The text "on page \pgref" printed only sometimes

We create the macro \onpage which can be used just after \ref[label] and it expands only to space if the \ref occurs at the same page as referred page. It expands to the text "on page \pgref[label]" if it is referred to a different page than the current one. For example:

Section \ref[mylabel]\onpage shows bla bla.

\vfil\break

\sec [mylabel] Section bla bla

prints the text: "Section 1.1 on page 2 shows bla bla" but if you remove the \vfil\break in this example then the text "Section 1.1 shows bla bla" is printed because the referred section is on the same page. The same task was formulated at Stack exchange, see witpet's answer.

You must run TeX twice in order to get the required result.

The implementation is simple:

\def\onpage{\label[x?\_lastreflabel]\wlabel{}%
   \space
   \ea\ifx\csname _pgref:x?\_lastreflabel\ea\endcsname
          \csname _pgref:\_lastreflabel\endcsname
   \else on page \pgref[\_lastreflabel] \fi
}

If \ref[label] is used then the \onpage macro creates an internal label x?label and writes it to the .ref file. The test if \_pgref:label is equal to \_pgref:x?label is done and if these pages differ then the text "on page \pgref[label]" is printed. Note that the macro \_lastreflabel` includes the label from the last used \ref macro.

(0087) -- P. O. 2022-06-06


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

More pages on single sheet

We create macros \sheet NUM { page spec. }, and \prinsheets. These macros read pages from given PDF document (defined in the \document macro) and prints sheets with more pages per single sheet. For example:

\def\document{name}  % the name of the source document without .pdf extension
\sheet 1 { [ 1 | 2 ] } \printsheets

This example prints two pages per single sheet, pages are [ 1 | 2 ] on the first sheet, [ 3 | 4 ] on the second, [ 5 | 6 ] on the third, etc.

The [...] in the page spec. means a single row. You can have more rows in the single sheet.

The \sheet 1 must be declared always. You can declare more: \sheet 2, \sheet 3 etc. All document pages declared in the whole bunch of sheets are read first and then the sheets are printed. If there are unprinted document pages, then the next pages for the bunch of sheets are read again and the sheets are printed, etc. For example, you can declare \sheet 1 and \sheet 2 for duplex printing.

The paper dimensions (for printing) is calculated directly from the sheet dimensions without margins if \margins is unused. You can set the paper dimensions and left+top margins by \margins (see section 1.2.1 of OpTeX doc). The setting right+bottom margins is irrelevant because the sheets aren't scaled. But you can scale document pages to a desired size by \def\pgparam{width10cm} (for example). If it is unused then the document pages have their natural size.

You can try more complicated example of "imposition pages to sheets", see wiki about it. The example looks like:

\def\document{name} % included document is name.pdf
\vspacing=18mm      % spaces between rows is 18mm (default is 0mm)
\sheet 1 {
  [ v5 | v12 |14mm|  v9 | v8 ]
  [ p4 | p13 |14mm| p16 | p1 ]
}
\sheet 2 {
  [ v7 | v10 |14mm| v11 | v6 ]
  [ p2 | p15 |14mm| p14 | p3 ]
}
\printsheets

The example shows more syntax rules. The page numbers can be prefixed by letters: none or p (no rotation), r (90 degrees), l (-90 degrees), v (180 degrees). The page separators | are optional and if they are used (for greater clarity) then they must be followed by a space. If there is no space then the dimension terminated by second | is read and the space between pages is declared here.

You can add crop-marks to the output of sheets using cropmarks.tex from olsak-misc macro collection released on CTAN and included in TeXlive.

The implementation of \sheet and \printsheets:

\newcount\docpage  \newdimen\vspacing
\voffset=0pt \hoffset=0pt \pdfpagewidth=0pt
\def\pgparam{} % additional parameters for \pdfximage, width5cm for example

\def\sheet{\afterassignment\scansheet \chardef\sheetnumber}
\def\scansheet{\ifnum\sheetnumber=1 \tmpnum=0 \fi
   \ea\scansheetA \csname sheetbox\the\sheetnumber\endcsname
}
\def\scansheetA#1#2{\def\tmp{\vbox\bgroup\baselineskip=0pt \lineskip=\vspacing}%
   \foreach #2\do ##1[##2]{%
      \addto\tmp{\hbox\bgroup}\scansheetB ##2X\addto\tmp{\egroup}%
   }
   \addto\tmp{\egroup}\let#1=\tmp
}
\def\scansheetB #1{\trycs{pg.#1}{\pgscan#1}}
\sdef{pg.p}{\pgscan}
\def\pgscan{\afterassignment\pgset\docpage=}
\def\pgset{\incr\tmpnum \ea\addto\ea\tmp\ea{\ea{\ea\box\the\docpage}}\scansheetB}
\sdef{pg.|}{\isnextchar{ }{\scansheetB}{\marginset}}
\def\marginset#1|{\addto\tmp{\kern#1\relax}\scansheetB}
\sdef{pg.r}{\addto\tmp{\rotbox{90}}\pgscan}  \sdef{pg.l}{\addto\tmp{\rotbox{-90}}\pgscan}
\sdef{pg.v}{\addto\tmp{\rotbox{180}}\pgscan} \sdef{pg.X}{}

\def\printsheets{\chardef\numpages=\tmpnum
   \setbox1=\hbox{\pdfximage \pgparam {\document.pdf}\pdfrefximage\pdflastximage}
   \dimen0=\wd1 \dimen1=\dimexpr\ht1+\dp1\relax
   \docpage=1 \tmpnum=1 \printsbunch
   \end
}
\def\printsbunch{% scans document pags and prints the bunch of sheets
   \loop \ifnum\tmpnum<\numpages        % scan \numpages boxes from document
         \incr\tmpnum \setboxnum\tmpnum
         \repeat
   \ifdim\pdfpagewidth=0pt
      \setbox0=\csname sheetbox1\endcsname \pdfpagewidth=\wd0 \pdfpageheight=\ht0 \shipout\box0 
   \else \shipout\csname sheetbox1\endcsname \fi    % prints scanned boxes as sheet 1
   \incr\pageno \tmpnum=2
   \loop \ifcsname sheetbox\the\tmpnum\endcsname % print \seet 2 \sheet3 etc.
         \shipout \csname sheetbox\the\tmpnum\endcsname
         \incr\pageno \incr\tmpnum
         \repeat
   \ifnum\docpage<\pdflastximagepages \tmpnum=0 \ea\printsbunch \fi
}
\def\setboxnum#1{\incr\docpage  % does \setbox#1=\hbox{next page from the document}
   \setbox#1=\ifnum\docpage>\pdflastximagepages
            \hbox{}\wd#1=\dimen0 \ht#1=\dimen1   % void page
      \else \hbox{\pdfximage\pgparam page\docpage{\document.pdf}\pdfrefximage\pdflastximage}\fi
}

The \sheet NUM { pg. spec } scans the rows declaration [...] using \foreach and creates the macro \sheetboxNUM which is \vbox{\hbox{}\hbox{}...}. Each \hbox (single row) includes the list of {\box pgNUM} prefixed (optionally) by \rotbox{degrees}. The number of declared document pages for whole bunch of sheets is set by \tmpnum and saved to \numpages by \printsheets. The \printsheets macro reads first page of the document and sets parameters read from it and runs \printsbunch. The \printsbunch macro reads next pages of the document for whole bunch of sheets (using \loop) and saves them individually to the \box pgNUM. The \shipout\sheetboxNUM prints the given sheet. The \sheetbox1 is printed and the PDF dimensions are measured and set (if this is really the first sheet). The second \loop prints other sheets from the single bunch of sheets. The \printsbunch macro is repeated (recursive call) for the next bunch of sheets if there are document pages not yet printed.

(0088) -- P. O. 2022-06-14


Printing booklets

We create macro \printbooklet which reads pages from a PDF document and creates sheets with two pages (one next to other) per single sheet. When the sheets are printed in landscape format with duplex printing (short edge) and you fold the printed output in the half then you get a booklet of whole document. Usage:

\margins/1 a4l (0,0,0,0)mm  % A4 landscape format with no additional margins
\def\document {name}        % name.pdf is the document which is read
\def\pgparam{width148.5mm}  % pages are scaled to the half of A4 landscape width
\printbooklet               % creates the booklet

For example, if the document has 15 pages, then the output looks like:

[   | 1 ]  [ 2 | 15 ]  [ 14 | 3 ]  [ 4 | 13 ]  [ 12 | 5 ]  [ 6 | 11 ]  [ 10 | 7 ]  [ 8 | 9 ]

The implementation:

\newcount\docpage \newcount\doclastpage
\newdimen\interspace
\def\pgparam{}  % additional parameters for \pdfximage, width5cm for example
\def\printbooklet{
   \setbox1=\hbox{\pdfximage \pgparam {\document.pdf}\pdfrefximage\pdflastximage}
   \dimen0=\wd1 \dimen1=\dimexpr\ht1+\dp1\relax
   \doclastpage=\pdflastximagepages
   \advance\doclastpage by3 \divide\doclastpage by4 \multiply\doclastpage by4
   \docpage=1
   \loop
      \ifnum\docpage=1 \else \setbox1=\page\docpage \fi \incr\docpage
      \setbox2=\page\doclastpage                        \decr\doclastpage
      \setbox3=\page\docpage                            \incr\docpage
      \setbox4=\page\doclastpage                        \decr\doclastpage
      \shipout\hbox{\box2\kern\interspace\box1}         \incr\pageno
      \shipout\hbox{\box3\kern\interspace\box4}         \incr\pageno
      \ifnum\docpage<\doclastpage \repeat
   \end
}
\def\page#1{\ifnum#1>\pdflastximagepages \hbox to\dimen0{\vbox to\dimen1{}\hss}%
   \else \hbox{\pdfximage\pgparam page#1{\document.pdf}\pdfrefximage\pdflastximage}\fi
}

The number of sheet-pairs is doc-pages / 4 rounded up. The doc-pages is read from the \pdflastximagepages when first page from the document is read. Then the \doclastpage is calculated as number of sheet-pairs multiplied by 4. It is ideally the same as \pdflastximagepages but it can be greater: the void pages must be used in such case. The \page NUM macro returns real page from the document or void page, if NUM>\pdflastximagepages. The sheet-pairs are printed in the loop.

(0089) -- P. O. 2022-06-17


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 OpTeX (version from the summer) when you insert the file latexmkrc with contents:

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

to your project and select "LuaLaTeX" engine in the Overleaf menu. Or simply copy this project to your project.

If we want to run more recent OpTeX version (not yet installed at Overleaf) then we have to 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