[Tex/LaTex] Bounding box for each letter

boxesluatexpdftex

How can I take input text and replace each character with a solid (and/or hollow) rectangle representing the bounding box for that character? In the case where two characters are closer together from kerning (e.g. microtype) the boxes would overlap. The addition of the boxes should not change the spacing of the text – I'm looking for a "draft mode" for the letters.

Solutions and Problems

Both solutions presented by Yiannis Lazarides and egreg seem to do a reasonable job for single words, though as mentioned, it seems that kerning was not completely respected. Below are the results (without markup/egreg/Yiannis):

enter image description here

Both solutions fail however when multiple words are involved. One of the answers completely eats a space while the other overcompensates. Both of them seem to choke on a line break as well.

enter image description here

Best Answer

A LuaTeX solution. Should work in all situations that I am aware of:

\documentclass{article}
\usepackage{luacode,luatexbase}
\begin{document}
\begin{luacode*}
local GLYPH_ID = node.id("glyph")

local number_sp_in_a_pdf_point = 65782

function math.round(num)
  return math.floor(num * 1000 + 0.5) / 1000
end

-- width/height/depth of a glyph and the whatsit node
local wd,ht,dp,w

-- head is a linked list (next/prev entries pointing to the next node)
function showcharbox(head)
  while head do
    if head.id == 0 or head.id == 1 then
      -- a hbox/vbox
      showcharbox(head.list)
    elseif head.id == GLYPH_ID then
      -- Create a pdf_literal node to draw a box with the dimensions
      -- of the glyph
      w = node.new("whatsit","pdf_literal")
      wd = math.round(head.width  / number_sp_in_a_pdf_point)
      ht = math.round(head.height / number_sp_in_a_pdf_point)
      dp = math.round(head.depth  / number_sp_in_a_pdf_point)

      -- draw a dashed line if depth not zero
      if dp ~= 0 then
        w.data = string.format("q 0.2 G 0.1 w 0 %g %g %g re S f [0.2] 0 d 0 0 m %g 0 l S Q",-dp,-wd,dp + ht,-wd)
      else
        w.data = string.format("q 0.2 G 0.1 w 0 %g %g %g re S f Q",-dp,-wd,dp + ht,-wd)
      end
      -- insert this new node after the current glyph and move pointer (head) to
      -- the new whatsit node
      w.next = head.next
      w.prev = head
      head.next = w
      head = w
    end
    head = head.next
  end
  return true
end

luatexbase.add_to_callback("post_linebreak_filter",showcharbox,"showcharbox")
\end{luacode*}

A \emph{wonderful} serenity has taken {\large possession} of my entire soul, like these
\textsl{sweet}
\textbf{mornings} of spring which I enjoy with my whole heart. I am alone, and feel the
charm of existence in this spot, \textbf{which} was created for the bliss of souls like
mine. I am so happy, my dear friend, so absorbed in the exquisite sense of
mere tranquil existence, that I neglect my talents. I should be incapable of
drawing a single stroke at the present moment; and yet I feel that I never was
a greater artist than now.

\end{document}

which yields:

text with boxes

(detail)

detail on text with boxes

Bonus: it draws the base line if the depth of the glyph is not 0.


Here is a solution that replaces the glyphs by black rectangles (rules):

\documentclass{article}
\usepackage{luacode,luatexbase,microtype}
\begin{document}
\begin{luacode*}
local GLYPH_ID = node.id("glyph")

-- head is a linked list (next/prev entries pointing to the next node)
-- parent it the surrounding h/vbox
function showcharbox(head,parent)
  while head do
    if head.id == 0 or head.id == 1 then
      -- a hbox/vbox
      showcharbox(head.list,head)
    elseif head.id == GLYPH_ID then
      r = node.new("rule")
      r.width  = head.width
      r.height = head.height
      r.depth  = head.depth

      -- replace the glyph by
      -- the rule by changing the
      -- pointers of the next/prev
      -- entries of the rule node
      if not head.prev then
        -- first glyph in a list
        parent.list = r
      else
        head.prev.next = r
      end

      if head.next then
        head.next.prev = r
      end
      r.prev = head.prev
      r.next = head.next

      -- now the glyph points to
      -- nowhere and we should remove
      -- it from the memory
      node.free(head)

      head = r
    end
    head = head.next
  end
  return true
end

luatexbase.add_to_callback("post_linebreak_filter",showcharbox,"showcharbox")
\end{luacode*}

\hsize6cm

A wonderful serenity has taken possession of my entire soul, like these sweet
mornings of spring which I enjoy with my whole heart. I am alone, and feel the
charm of existence in this spot, which was created for the bliss of souls like
mine. I am so happy, my dear friend, so absorbed in the exquisite sense of
mere tranquil existence, that I neglect my talents. I should be incapable of
drawing a single stroke at the present moment; and yet I feel that I never was
a greater artist than now.

\end{document}

glyphs replaced by black rules