53

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

topskip
  • 37,020
Hooked
  • 4,046
  • 9
    this is quite similar to Exercise 11.5 from The TeXbook. Knuth provides a solution- not sure about posting it here though because of copyright... – cmhughes May 29 '12 at 23:17
  • 3
    Am pretty sure there is a question here doing exactly this but can't seem to locate it... – Peter Grill May 30 '12 at 00:18
  • 7
    @cmhughes The code of the TeXbook is available for study and quoting a passage from a book is always allowed (with proper attribution). – egreg May 30 '12 at 07:57
  • 1
    fwiw, vafa khalighi has created a package showcharinbox which answers this (nearly 2-months-old) question. see http://www.ctan.org/pkg/showcharinbox – wasteofspace Jul 24 '12 at 10:09

4 Answers4

42

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

topskip
  • 37,020
  • 1
    Really nice solution! – Gonzalo Medina Jun 02 '12 at 20:56
  • Glad to see you back! Thanks for the great solution - this works great! How would you modify the code to draw a filled rectangle instead of a hollow one? – Hooked Jun 02 '12 at 21:10
  • I've just fixed an embarrassing rounding error in my code – topskip Jun 02 '12 at 21:17
  • @Hooked Just change the re S f in the w.data lines above to re b. Please note that I have updated my code in the mean time. – topskip Jun 02 '12 at 21:25
  • Actually the solution with boxes is a bit easier, except for the pointer management. – topskip Jun 03 '12 at 09:46
  • Big +1 b/c it works in math mode too! – Joe Corneli Dec 09 '12 at 21:51
  • @topskip. Is it possible to adapt the solution to print only the baseline rules? Or maybe only the box around the whole line? – Sigur Sep 27 '14 at 17:36
  • 1
    @Sigur do you know the package lua-visual-debug? It might be what you are after, or it might be a starting point to adapt it for yourself. – topskip Sep 27 '14 at 18:45
  • @topskip, ow, thanks so much. I didn't know it. It is exactly what I need. Unfortunately it requires luatex. Thanks. – Sigur Sep 27 '14 at 19:26
  • 2
    @Sigur well, LuaTeX makes it easy to draw these lines. PDFTeX and other engines are completely lacking this kind of support. So there is no way I could implement this behaviour for other engines. – topskip Sep 29 '14 at 09:12
  • @topskip both examples do not seem to work any more (at least here) using TL2017: both compile fine but the result differs - no boxes are shown. Do you have an idea why? Thx! – lAtExFaN Sep 05 '17 at 10:30
  • @lAtExFaN This code had hard coded codes for glyph ids (37 in older versions of LuaTeX), I have now used a function to determine the id which works with older versions as well. Thank you for the comment! – topskip Sep 06 '17 at 07:14
  • @topskip now it works (again) - thx a lot! – lAtExFaN Sep 06 '17 at 10:45
  • @topskip I tried to apply the code on my book (class: scrbook) which uses footnotes, citations, different fonts, tables etc.pp. Most of the text is boxed (footnotes, tables, font switches, quotes citatation), but some parts are not, e.g. some paragraphs (often after a footnote), the same with scrlttr class: approximately 98% of the text is boxed, but sometimes, again often after normal footnotes (not citations footnotes) the paragraph isn't tetrisified ;-) Have you successfully applied the code to a more complex document? If I find some time I try to assemble an MWE and open a new question. – lAtExFaN Sep 14 '17 at 09:19
  • @lAtExFaN This code is just a proof of concept. If you can create a MWE, just try to contact me somehow, and I'll have a look. – topskip Sep 15 '17 at 06:58
  • @topskip thx a lot for you assistance, before I take the time to construct an MWE I thought I just try to compile the first demo thesis I could find: https://math.berkeley.edu/~vojta/thesis/ and bingo! It shows the previously mentioned result (approx. 98% is fine, ~2% is "unbricked"). Please let me know if I should open up a new question. – lAtExFaN Sep 15 '17 at 10:51
  • @lAtExFaN Please open a new question and ping me somehow – topskip Sep 15 '17 at 12:18
  • @topskip ping: https://tex.stackexchange.com/q/391543/112503 – lAtExFaN Sep 15 '17 at 13:58
23

Here is a solution that respects kerning (but not ligatures):

\documentclass{article}
\usepackage{xcolor}
\makeatletter
\def\showboxes#1{%
  \begingroup\fboxrule=.1pt \fboxsep=-\fboxrule
  \@showboxes#1\@showboxes\@empty
  \endgroup}
\def\@showboxes#1#2{%
  \ifx#2\@showboxes
    \fbox{\color{gray}#1}\expandafter\@gobble
  \else
    \setbox0=\hbox{#1\kern0pt#2}\setbox2=\hbox{#1#2}%
    \dimen0=\wd0 \advance\dimen0 -\wd2 % \dimen0 contains the kern between the two chars
    \fbox{\color{gray}#1}\kern-\dimen0
    \expandafter\@showboxes
  \fi#2}
\begin{document}
\showboxes{AVov}
\end{document}

enter image description here

For doing more words just add

\usepackage{xparse}
\def\showboxesaux#1{\showboxes{#1} }
\NewDocumentCommand\Showboxes{>{\SplitList{ }}m}
  {\ProcessList{#1}\showboxesaux\unskip}

and use

\Showboxes{The quick brown fox jumped over the lazy dog.}

but hyphenation won't be taken care of.

egreg
  • 1,121,712
  • Great solution! Can this be modified to accept multiple words? At the moment the spaces are swallowed. (See question update). – Hooked May 30 '12 at 19:43
  • @Hooked It can, but I don't think it can be a substitute for the "real thing", that is, doing this with LuaTeX. – egreg May 30 '12 at 19:52
  • Excuse my Lua-ignorance, are you saying that a LuaTeX solution would be able to handle the bounding box problem in a more robust way? – Hooked May 30 '12 at 19:55
  • @Hooked I'm sure it is (but I can't give a solution). You were unlucky that our main LuaTeX expert, Patrick Gundlach, is on his well deserved holidays. – egreg May 30 '12 at 19:58
  • This solution doesn't work with xetex as is. Wouldn't it be better to replace \setbox0=\hbox{{#1}{#2}} by \setbox0=\hbox{#1\kern0pt#2}, because xetex doesn't parse the grouping as expected for unknown reason? – Henri Menke Jul 01 '14 at 15:44
  • @HenriMenke Good catch, thanks. XeTeX builds boxes in a slightly different way than Knuth's TeX (and the other engines based on it); for instance one can't do \showhyphens in XeTeX. – egreg Jul 01 '14 at 16:00
19

Probably the best is the code from the TeXbook and this I will leave up to you. Another alternative is to study the code from the soul package and use the scanners provided.

Here is an adaptation:

\documentclass{article}
\usepackage{soul,graphicx,xcolor}
\fboxrule=0.1pt
\fboxsep=-\fboxrule
\begin{document}
\makeatletter
\def\SOUL@soeverytoken{%
  \fbox{\color{gray}\the\SOUL@token}}
\makeatother
\scalebox{7}{\color{purple}\so{ailbcdefgh}}
\end{document}

Change the gray color to white to have the letters disappear.

enter image description here

You will need to change the \fbox to basic TeX primitives, if you need to compensate for the 0.1pt rule or use the suggestion of egreg in the comments, which I have incorporated.

yannisl
  • 117,160
  • 2
    \fboxsep=-\fboxrule would solve the issue. But you lose kerning information in this way. – egreg May 30 '12 at 10:09
  • @egreg Thanks I added your code. Best is to go to a Knuth-box (I had it as a macro somewhere ...) – yannisl May 30 '12 at 10:16
  • @YiannisLazarides Thank you for the solution, I'll have to play around with soul a bit more. I've updated my question based off your solution, it seems that in addition your interword spacing is too large as well. – Hooked May 30 '12 at 19:42
  • I remember I saw something like this in fontchart.tex – morbusg Jul 24 '12 at 17:21
  • I like this solution, but it doesn't work well for math mode. \so{$(f+g)'(x) = f'(x)+g'(x)$} just puts everything in one box. – Joe Corneli Dec 09 '12 at 21:49
  • For some expressions, it seems to work if the dollar signs are put outside of the \so command – malin Dec 26 '13 at 19:31
7

With context from current TeXLive

\setuppagenumbering[location={}]
\definepapersize[DE][width=15cm,height=15cm]
\setuppapersize [DE][DE]
\setuplayout[width=13cm,height=13cm]
\definefontfeature[default][default][boundingbox={frame,blue,empty}]
\starttext
\bgroup
    \showglyphs
    \showfontkerns
    \showmakeup[hbox]
    \showmakeup[line]
%    \showallmakeup

    »Tee for Two« shows negative kerning.
    \input sapolsky \par
\egroup
\stoptext 

enter image description here