# highlighting words which cannot be hyphenated

is there any package which can automatically highlight unhyphenable words in output PDF? I.e. words which are not in the list of words for hyphenation. For example:

I'm using polyglossia package. Thank you very much!

• Not a complete answer to your question, but a step towards an answer: If you can use LuaLaTeX, you could load the showhyphens package: It will indicate all permissible hyphenation points with short vertical red bars. By extension, all words that contain no vertical red bars are not hyphenatable under the current language and hyphenation rules. – Mico Sep 25 '14 at 19:25
• Thank you, but I'm using XeLaTex :( Anyway, your solution requires a lot of manual work… I'm working with large texts and those vertical red bars aren't visible very well – Ashrak Sep 25 '14 at 19:37
• Maybe the solution is to highlight wide spaces between words… Is that possible? – Ashrak Sep 25 '14 at 19:38
• I guess I don't quite get your exclusive focus on words with no permissible breakpoints. If you used the showhyphens package (why that's supposed to be so much work?), you would quickly find words -- usually long ones -- that have only one break point under the hyphenation rules as currently implemented, but which in fact should have one or more additional break points. – Mico Sep 25 '14 at 19:50

# A proof of concept

We've checked some common packages, such as ulem (Don­ald Arse­neau) and soul (Mel­chior Franz), after that we've moved our attention to the showhyphens, lua-check-hyphen and lua-visual-debug packages created by Patrick Gundlach. Unfortunately, the OP needs the opposite from what these packages offer.

We started our work by analysing and learning from this code from TeX.SX (see also http://wiki.luatex.org/index.php/Show_the_hyphenation_points) by Patrick Gundlach. We are always supported by the reference manual of LuaTeX, where we've noticed defined types of nodes (see Chapter 8 Nodes, first page; page 169+ in the manual). We can read about their fields on the following pages.

We especially needed these types:

• hlist (0), horizontal lists,
• vlist (1), vertical lists,
• disc (7), a place where a word could be hyphenated,
• whasit (8), suitable type for a new node creation,
• glue (10), spaces,
• kern (11), kernings, and,
• glyph (37), the key node type for this sort of work.

Our task was to go through all nodes, collect and store their ID in a Lua table, do some basic analysis at a text level and after that do some basic highlighting of the results (in this example, we used underlining of every glyph).

We are passing a minimum number of letters which must have that word to be highlighted. A single word consists of letters, digits, punctuations etc. We believe that should be sufficient for this proof of concept.

The original code to show hyphens uses recursive algorithm to go through the node tree. We needed two passes, therefore we wrapped that function (show_hyph) to another one (towork). The analysis is done in the showme function. We use head userdata, we could print the fields using the getmetatable function. It is commented out in the code as well as printing additional information about the nodes.

Text analysis consists of several conditions, mainly from:

• ID 0 or ID 10 followed by ID 37 (plus the word cannot be already hyphenated), that's a beginning of a word. It is a new line or a space followed by a glyph.
• ID 7 means that there can be a disc and that word is of no interest to us.
• ID 11 (kerning) or ID 37 (glyph) is a part of a word, we include that part for further analysis.
• ID 37 followed by ID 10 is the end of a word.

We store all information in a Lua table. We needed two variables about a length of the word, for displaying a word we used malsizetrue (glyphs+kerning nodes), for interaction with a user we used malsize (glyphs only). It was strange at first to think about words in these terms, but it was necessary.

We used head.width (sp units) to get proper underlining. In the example below, every letter is underlined separately, it could be improved to draw one single line per word. We are leaving this idea opened for further experiments with the code. That would require to store widths in the first run of the function and using them in the second run at a Lua level.

In the following example, we used some basic text and we set a minimum number of letters per word from 1 to 8. We enclose the code and a preview of the result (8 pages). It works as expected in this example, but I guess that this snippet requires more testing.

We run lualatex mal-hyph.tex.

% lualatex mal-hyph.tex
\documentclass[a4paper]{article}
\pagestyle{empty}
\usepackage{luacode} % the core package in LuaLaTeX, recommended is luatextra

\begin{luacode*}
-- This is the core function for callback...

-- This inner function is recursive...

-- If we want to see userdata of >head<...
-- for i,j in pairs(getmetatable(head)) do
--    print(i,j)
-- end

-- If we need to get nodes ID..., this is the way.
if head.id ==10 then io.write(" ") end
end
--]=] -- be silent

end -- if, head.id, 0 (hlist) or 1 (vlist)

counter=counter+1 -- number of nodes
malglyphs[counter]=head.id -- saving IDs for later analysis

if run then -- We can add nodes after analysis...
if maldraw[counter]==1 then -- These letters belong to unhyphenateable words.
local n = node.new("whatsit","pdf_literal") -- Underline them, please,
n.mode = 0
distb=0
n.data = "q 1 w 0 0 1 RG "..-dista.." -2 m "..distb.." -2 l S Q" -- with some common line.
end -- if we are done with lines.

if head.id == 7 and run then -- a divis, this is an original example, addition of disc
local n = node.new("whatsit","pdf_literal")
n.mode = 0
n.data = "q 0.9 w 1 .1 .1 RG 0 2 m 0 7 l S Q" -- an original setting was: q 0.3 w 0 2 m 0 7 l S Q
end -- if, run

end -- while, node three

end -- function, show_hyph

-- This function analyses ID of nodes.
function showme()
-- Initialize some variables.
malwrite=0 -- Can I store a unhyphenateable word?
maldraw={} -- Table with 1s for storing information.
malsize=0 -- A size of the last word (only letters, ID 37).
malsizetrue=0 -- A size of the last word (letters+kerning, IDs 11+37).
broken=0 -- Is a word already hyphenated?

for malcounter=1,#malglyphs do -- Let's analyse all nodes.

-- Print some letters from actual position backward.
-- print(malglyphs[malcounter-2], malglyphs[malcounter-1], malglyphs[malcounter])

-- Are we at the beginning of the word?
if  (malglyphs[malcounter-1]==0 or malglyphs[malcounter-1]==10) and malglyphs[malcounter]==37 and broken~=1 then malwrite=1; malsize=0; malsizetrue=0 end

-- Are we at the end of the word? Can we modify data in our Lua table?
if malglyphs[malcounter-1]==37 and (malglyphs[malcounter]==10 or malglyphs[malcounter]==12) then
if malwrite==1 and malsize>=malmax then -- used to be: malsize>0
for malback=1,malsizetrue do -- set letters of unhyphenated word for underlining/highlighting, plus kerning
maldraw[malcounter-malback]=1 -- mark them letter by letter
end -- for, malback
end -- if, malwrite
end -- if, malglyphs...

-- Are we in the middle of the hyphenated word?
if malglyphs[malcounter]==7 then broken=1 end -- Yes, we are!
if malglyphs[malcounter]==37 then broken=0 end -- No, at least not at the beginning of the line!

-- Is this a node with letter or kerning in some word?
if malglyphs[malcounter]==37 or malglyphs[malcounter]==11 then
malsizetrue=malsizetrue+1 -- letters+kernings
if malglyphs[malcounter]==37 then malsize=malsize+1 end -- Only letters.
else
malwrite=0 -- Stop analysing actual word, it has no meaning anymore.
end -- stop searching

end -- for, malcounter

-- If we need some information from the Lua table...
--[[for allchars=1,#malglyphs do
if maldraw then print(allchars, maldraw[allchars]) end
]]
end -- function, showme

-- This is the first run, we need data about nodes.
malglyphs={}; run=nil; counter=0
showme()

-- This is the second run, we will highlight letters (and also kerning with an invisible pen :-) ).
malglyphs={}; run=1; counter=0
showme()

return true -- Let's finish our work and send data back to a callback.
end -- major function, towork()
\end{luacode*}

% Some TeX commands for general use...
% A minimum of letters required in an unhyphenateable word to be highlighted.
\def\malactivateset#1{
\luaexec{
print("malmax "..#1)
malmax=tonumber(#1) or 0
}}

% This command activates a callback.
\def\malactivate{
} % End of \activateme...

% This command deactivates a callback.
\def\maldeactivate{
\directlua{luatexbase.remove_from_callback("post_linebreak_filter","towork")}
} % End of \deactivateme...

% This text is an example for TeX.SX.
\def\maltext{\newpage
Minimum of required letters: \the\malletters.\par
Hello, the World!\par
Here is some text without meaning.\par
\kant[1]\par
} % End of \maltext...

% Small changes in paper mirror...
\rightskip=7cm
\parindent=0pt

\begin{document}
% A small example...
\malactivate % This command activates the algorithm...
\newcount\malletters