Consider the following sample LaTeX file:

Test $x^2$

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

  • 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? – Ulrike Fischer Feb 3 '19 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 Feb 3 '19 at 22:42
  • @UlrikeFischer Good point. I guess luatex could output the glyph "reference" to be drawn along with its coordinate origin. – cjorssen Feb 3 '19 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 Feb 3 '19 at 22:57
  • Related: tex.stackexchange.com/q/464875/8425 – cjorssen Feb 3 '19 at 23:04

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.

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
            y = y + n.shift
        elseif  n.id == node.id"vlist" then
            mode = "vmode"
            glue.hmode = 0
        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

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


Test $x^2$

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

Test $x^2$

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'>
@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}
<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>

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.

  • That's pretty much exactly what I was looking for! Thnaks! – cjorssen Feb 7 '19 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 Feb 7 '19 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 – Henri Menke Feb 7 '19 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? – Ulrike Fischer Feb 8 '19 at 13:54
  • @cjorssen I don't get such a message. For me the above MWE processes fine. – Henri Menke Feb 10 '19 at 20:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.