7. Extending troff using Lua

To invoke Lua from troff, we use the macro .eval with its closer .endeval. These are defined in the supplied troff macro file pca-eval.tmac, which you should put in a macro directory.

.eval does only one thing: It allows you to place arbitrary Lua code until the following .endeval, and the text written to standard output by this Lua code is substituted for the .eval ... .endeval. The usefulness of this tactic will be apparent from an example. Consider the following troff document, tau.ms:

    .mso pca-eval.tmac
    The ratio of the circumference of a circle to
    its radius is \(*t \(~=
    -- following prints tau, because cos(tau/2) = -1
    io.write(2*math.acos(-1), '.\n')

Run troff (actually groff, and in unsafe mode too) on tau.ms:

    groff -z -U -ms tau.ms

(The -z avoids generating output, because we are not ready for it yet. The -U runs in “unsafe” mode, i.e., it allows the writing of aux files.) You will find that the groff asks you to run it again:

    Rerun groff with -U

Call groff again as follows:

    groff -U -ms tau.ms > tau.ps

tau.ps will now look like:

The ratio of the circumference of a circle to its radius is τ ≈ 6.2831853071796.

The first groff call produces a Lua file .trofftemp.lua.3 The second groff call invokes Lua to create an auxiliary file for each .eval that gets sourced back into the document.

It is clear that Lua code via .eval can serve as a very powerful second extension language for troff. This benefit is available even when the document is processed by troff2page: We could run troff2page on the same document, tau.ms, and the resulting tau.html will show the same content.

Furthermore, we can embed .eval-enclosed Lua code inside an .if (or .ie or .el) statement so that it gets executed only for troff or only for troff2page. (Recall we used the number register \n[.troff2page] for this purpose on page 3.) In particular, by calling Lua code only for troff2page, the user has the means to extend troff2page to deal with things that the core program as supplied does not.

Note that when troff2page sees .eval-enclosed Lua code, it runs it in a Lua environment that has not only the basic Lua language but also includes the procedures and data structures defined in troff2page. These extras are not available (and are arguably not useful) to the .eval code seen by troff.

Defining color names using HSL

For a more substantial example of .eval’s use, consider defining color names using the HSL (hue/saturation/lightness) scheme rather than the RGB and CMYK schemes provided by groff. For instance, we would like to define the color Venetian red using its HSL spec (357°, 49%, 24%), which to many is a more intuitive description than RGB (91, 31, 34).

While there is an algorithm to convert HSL to RGB, implementing it using groff’s limited arithmetic is tedious. Instead, we’ll define a much easier Lua procedure to do the same, and put it inside an .eval:

    .mso pca-eval.tmac
      local function between_0_and_1(n)
        if n < 0 then
          return n + 1
        elseif n > 1 then
          return n - 1
          return n

      local function tc_to_c(tc, p, q)
        if tc < 1/6 then
          return p + (q - p)*6*tc
        elseif 1/6 <= tc and tc < 1/2 then
          return q
        elseif 1/2 <= tc and tc < 2/3 then
          return p + (q - p)*6*(2/3 - tc)
          return p

      local function hsl_to_rgb(h, s, l)
        h = (h % 360) / 360
        local q
        if l < 1/2 then
          q = l * (s + 1)
          q = 1 + s - l*s
        local p = 2*l - q
        local tr = between_0_and_1(h + 1/3)
        local tg = between_0_and_1(h)
        local tb = between_0_and_1(h - 1/3)
        return tc_to_c(tr, p, q),
          tc_to_c(tg, p, q),
          tc_to_c(tb, p, q)

      function def_hsl_color(name, h, s, l)
        local r, g, b = hsl_to_rgb(h, s, l)
        io.write(string.format('.defcolor %s rgb %s %s %s\n', name, r, g, b))

Here, the Lua procedure def-hsl-color takes an HSL spec and writes out the equivalent RGB groff definition. (The troff2page distribution provides def-hsl-color in the macro file defhsl.tmac.)

We can call this Lua procedure inside another .eval to define venetianred using its HSL spec:

    def_hsl_color('venetianred', 357, .49, .24)

The color name venetianred can now be used like any other groff color name:

    Prismacolor’s burnt ochre pencil is a close match for Derwent’s
    \fB\m[venetianred]Venetian red\m[]\fP, and either can be used to
    emulate the sanguine chalk drawings of the Old Masters.

This produces:

Prismacolor’s burnt ochre pencil is a close match for Derwent’s Venetian red, and either can be used to emulate the sanguine chalk drawings of the Old Masters.

Extending troff2page only

troff2page treats troff’s .ig environment, whenever it uses ## as ender, as containing Lua code that can be used to extend troff2page. This syntactic overloading of .ig ## is an innovation introduced by Oliver Laumann’s unroff.

Any Lua code enclosed within .ig ## ... .## will be processed by troff2page but not by troff, which treats it as a multiline comment of course. Note that .ig ## does not pipe its stdout back into the document, as .eval does. This is to maintain the invariant that as far as output is concerned, .ig ##, like other .igs, is always a comment. However, you can add Lua code within .ig ## to influence how troff2page — but not troff! — processes the rest of the document.

For example, let’s define a \*[url ...] string register that simply typesets its URL argument within angle brackets.

    .ds url \(la\fC\\$1\fP\(ra

This is adequate for the print output. For troff2page though, we’d like to re-define this macro, in Lua, to create a hyperlink. We enclose this re-definition in a .ig ##, which not only allows it to be in Lua, but also makes it apply only when troff2page reads it:

    .ig ##
    defstring('url', function(url)
      return link_start(url) .. url .. link_stop()

The procedures defstring, link_start, and link_stop are defined in the troff2page code.

.ig ## can be used to specify settings that are relevant only when troff2page is used on a document, e.g., stylesheet changes. troff2page uses the output-stream Css_stream to write out style information. The user can also write to this stream, e.g.,

    .ig ##
        h1,h2,h3,h4,h5,h6 {
            color: rgb(61,35,39); /* chocolate */

This sets the HTML headers with a foreground color of chocolate.4

3 This name may be different based on your setting of \*[AUXF] — see page 2.

4 The RGB values used in this manual for the colors chocolate, terracotta pink, and Venetian red are all courtesy Resene Paints.