6

Consider the following sample LaTeX file:

\documentclass{standalone}
\begin{document}
Test $x^2$
\end{document}

When processed by one of the TeX engines, one gets a pdf with glyphs that are associated with boxes nicely put together by the shipout process (IIUC).

Say that the pdf page is a canvas. I'd like luatex to output a file with the coordinates of each glyph box in the cartesian coordinate system of the canvas (let's take the origin at the bottom left of the canvas).

Any ideas? (Maybe the lua-visual-debug package is a good starting point but I am not an expert in that field and I barely understand how it works traversing all page nodes).

The whole idea here is to let luatex build pages, paragraphs or formulæ and use a scriptable graphics editor like gimp or inkscape to actually draw on the canvas (in my use case, that would be blender but I don't think it matters).

Note that what I really need are the coordinates of the glyph origin (if that concept even exists!), that is where to draw the glyph on the canvas (the box is just the TeX way of seeing its output ignoring how the glyph are actually drawn, IIUC again).

7
  • Let's assume that you get a large file of coordinates of all the glyphs - from text, header, footer, floats, footnotes, marginpars, from normal chars, from accents, ligatures, math supscripts, from nominator and denominator of fractions, and other stuff. How will you identify the coordinate you are interested in? Commented Feb 3, 2019 at 22:40
  • 1
    there is a quite old answer of mine which does something similar: tex.stackexchange.com/a/213647/2891 - I don't expect it to work without modifications as LuaTeX change a lot in the meantime
    – michal.h21
    Commented Feb 3, 2019 at 22:42
  • @UlrikeFischer Good point. I guess luatex could output the glyph "reference" to be drawn along with its coordinate origin.
    – cjorssen
    Commented Feb 3, 2019 at 22:56
  • @michal.h21 Really interesting. Thanks for the link. I need some time to dig and try to understand and maybe adapt to my needs.
    – cjorssen
    Commented Feb 3, 2019 at 22:57
  • Related: tex.stackexchange.com/q/464875/8425
    – cjorssen
    Commented Feb 3, 2019 at 23:04

2 Answers 2

7
+200

Inspect the shipout box

What you could do is override the \shipout primitive to execute some custom Lua code which walks the vertical list and inspects the nodes. This needs a fairly recent LuaTeX to be able to use token.scan_list. I have not yet found out how to determine the starting position in a sensible manner, so all coordinates are relative to the lower left corner of the bounding box of the first glyph. The coordinates are in pt.

\documentclass{standalone}
\directlua{
local mode
local x = 0
local y = 0
local glue = { hmode = 0, vmode = 0 }

local function glyph_boxes(head)
    for n in node.traverse(head) do
        if n.id == node.id"hlist" then
            mode = "hmode"
            y = y - n.shift
            glyph_boxes(n.list)
            y = y + n.shift
        elseif  n.id == node.id"vlist" then
            mode = "vmode"
            glue.hmode = 0
            glyph_boxes(n.list)
        elseif n.id == node.id"glue" then
            glue[mode] = glue[mode] + n.width
        elseif n.id == node.id"kern" then
            glue[mode] = glue[mode] + n.kern
        elseif n.id == node.id"glyph" then
            x = x + glue.hmode
            local c = string.utfcharacter(n.char)
            local f = font.getfont(n.font)
            print(c, f.name, x / 2^16, y / 2^16)
            x = x + n.width
            glue.hmode = 0
        end
    end
end

function shipout()
    local box = token.scan_list()
    tex.setbox(255, box)
    glue.vmode = 0
    print() % just for nice formatting
    glyph_boxes(tex.box[255])
    tex.shipout(255)
end
}

\def\shipout{\directlua{shipout()}}

\begin{document}
Test $x^2$
\end{document}

Output in the log:

T   [lmroman10-regular]:+tlig;  0.0 0.11000061035156
e   [lmroman10-regular]:+tlig;  6.3899993896484 0.11000061035156
s   [lmroman10-regular]:+tlig;  10.830001831055 0.11000061035156
t   [lmroman10-regular]:+tlig;  14.770004272461 0.11000061035156
x   cmmi10  21.990005493164 0.11000061035156
2   cmr7    27.705276489258 3.7389221191406

SVG export

Another option to obtain glyph coordinates is using the dvisvgm driver. This requires a bit more handwork but is in general less complex. In that case the document is simply

\documentclass{standalone}
\begin{document}
Test $x^2$
\end{document}

Typeset using

dvilualatex test.tex # or simply latex
dvisvgm --font-format=woff --no-merge test

The log might contain some information about not being able to embed certain fonts. This is irrelevant because we don't want to render the SVG but only extract data. The --no-merge option prevents the driver from merging adjacent letters into a single XML entity. I have removed the base64 encoded font data for brevity.

<?xml version='1.0' encoding='UTF-8'?>
<!-- This file was generated by dvisvgm 2.3.5 -->
<svg height='8.109622pt' version='1.1' viewBox='-72.000004 -72.000007 48.811833 8.109622' width='48.811833pt' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
<style type='text/css'>
<![CDATA[
@font-face{font-family:cmr7;src:url(/* base64 data */) format('woff');}
@font-face{font-family:cmmi10;src:url(/* base64 data */) format('woff');}
text.f0 {font-family:cmmi10;font-size:9.96264px}
text.f1 {font-family:cmr7;font-size:6.973848px}
text.f2 {font-family:[lmroman10-regular]:+tlig;;font-size:10px}
]]>
</style>
<g id='page1'>
<text class='f2' x='-72.000004' y='-63.890385'>T</text>
<text class='f2' x='-63.662905' y='-63.890385'>e</text>
<text class='f2' x='-54.498905' y='-63.890385'>s</text>
<text class='f2' x='-45.334905' y='-63.890385'>t</text>
<text class='f0' x='-32.853344' y='-63.890385'>x</text>
<text class='f1' x='-27.159412' y='-67.505749'>2</text>
</g>
</svg>

This is hopefully all the information you require. You have the class for each letter which you can match to the respective font using the embedded CSS and the x and y coordinates on the canvas. I cannot tell you where the origin is though. The units are probably bp.

6
  • That's pretty much exactly what I was looking for! Thnaks!
    – cjorssen
    Commented Feb 7, 2019 at 11:04
  • May I ask you to elaborate a bit on what token.scan_list is actually doing. IIUC, it takes the argument of the original \shipout (which should be a box) and set \box255 with its content. Am I right?
    – cjorssen
    Commented Feb 7, 2019 at 11:52
  • 1
    @cjorssen token.scan_list scans a box expression, i.e. \box255, \namedbox, \hbox{...} or \vbox{...}. You can obtain recent LuaTeX for TeX Live from github.com/hmenke/texlive-luatex-dev Commented Feb 7, 2019 at 20:10
  • Why do you need token.scan_list? Can't you use atbegshi to parse the shipout box like eg lua-visual-debug does it? Commented Feb 8, 2019 at 13:54
  • @cjorssen I don't get such a message. For me the above MWE processes fine. Commented Feb 10, 2019 at 20:30
3

There is a special function for that in LuaTeX: pdf.getpos(), it gets coordinates right from the engine. In order to use it, you need to add a Lua whatsit to the shipout box.

\documentclass{scrbook}

\usepackage{fontspec}
\usepackage{luacode}

\setmainfont{Libertinus Serif}
\begin{luacode}
    function PrintPos()
        print(pdf.getpos())
    end
    function ProcessList(head)
        for n in node.traverse(head) do
            if node.is_glyph(n) then
                local printpos = node.new("whatsit","late_lua")
                printpos.data = PrintPos
                node.insert_before(head, n, printpos)
            end
            if n.head ~= nil then
                ProcessList(n.head)
            end
        end
    end
    function PrintCoordinates()
        local soBox = tex.getbox("ShipoutBox")
        ProcessList(soBox)
    end
\end{luacode}
\AddToHook{shipout/before}{\directlua{PrintCoordinates()}}

\begin{document}
Hello World!
\end{document}

It outputs:

5157309  49122001
5478085  49122001
5667536  49122001
5856987  49122001
6398072  49122001
7023118  49122001
7384798  49122001
7651752  49122001
7841203  49122001
30992932 7677017

The last coordinate is the page number. And... don't mess with the shipout routine and box255. Just add a hook and use ShipoutBox. Otherwise you can get just a partial output if you use packages like scrlayer-notecolumn. Also keep in mind that some packages can also have hooks for shipout/before and may discard the whole page.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .