TransWikia.com

Formatting a two-column index with subitems

TeX - LaTeX Asked on May 24, 2021

I have two problems whilst formatting a two-column index with subitems:

documentclass{scrartcl}

usepackage{imakeidx}
usepackage[indentunit=15pt,justific=raggedright, columnsep=10pt, font=footnotesize]{idxlayout}

makeindex

begin{document}

Some text.

index{Donahue!Alice}
index{Donahue!Barney}

index{Jones!Alvin}
index{Jones!Billy}
index{Jones!Carter}
index{Jones!Dennis}

index{Smith!Alex}
index{Smith!Brian}
index{Smith!Carrie}
index{Smith!Dewey}
index{Smith!Eric}
index{Smith!Frank}
index{Smith!Gary}
index{Smith!Huey}
index{Smith!Irma}
index{Smith!Joey}
index{Smith!Kevin}

index{Scullion!Alex}
index{Scullion!Brian}
index{Scullion!Carrie}

index{White!Alice}
index{White!Barney}

idxlayout{columns=2, font=small, columnsep=20pt}

printindex

end{document}

enter image description here

In this MWE (1) the index-entry »Smith« is cut off from its sub-entries and (2) the space between the groups (entry + sub-entry) is uneven.

My two questions: Would it be possible to (1) fix the space between the item-groups and also (2) implement an algorithm that makes the entry stick together with at least one (or two, or three) sub-entries in the same column?

For the MWE this would result in the right column being a little shorter than the left one. »Alex, 1« would be pulled towards »Smith«. In my opinion a little better that what is the case now.

3 Answers

You can redefine @idxitem (which item is let equal to inside theindex) to do some bookkeeping, namely to reset a counter that each subsequent subitem would decrease.

Also subitem adds a penalty when the value of the counter is positive, so a page/column break is prohibited.

documentclass{scrartcl}

usepackage{imakeidx}
usepackage[indentunit=15pt,justific=raggedright, columnsep=10pt, font=footnotesize]{idxlayout}

makeindex

makeatletter
letori@idxitem@idxitem
def@idxitem{clear@penaltiesori@idxitem}
defclear@penalties{subitem@count=3 }
newcountsubitem@count
defsubitem{%
  advancesubitem@count -1
  par
  ifnumsubitem@count>0 penalty10000 fi
  ori@idxitemhspace*{ila@subindent}%
}
makeatother

begin{document}

Some text.

index{Donahue!Alice}
index{Donahue!Barney}

index{Jones!Alvin}
index{Jones!Billy}
index{Jones!Carter}
index{Jones!Dennis}

index{Smith!Alex}
index{Smith!Brian}
index{Smith!Carrie}
index{Smith!Dewey}
index{Smith!Eric}
index{Smith!Frank}
index{Smith!Gary}
index{Smith!Huey}
index{Smith!Irma}
index{Smith!Joey}
index{Smith!Kevin}

index{Scullion!Alex}
index{Scullion!Brian}
index{Scullion!Carrie}

index{White!Alice}
index{White!Barney}

idxlayout{columns=2, font=small, columnsep=20pt}

printindex

end{document}

enter image description here

This will keep two subitems together with the main entry. With

defclear@penalties{subitem@count=2 }

you'd get just one subitem attached.

enter image description here

Correct answer by egreg on May 24, 2021

The vertical spacing is different because the blank space is only inserted when there is a new letter (according to this documentation). Since you dislike it, I am going to fix it anyways.

I tried to write the solution in LaTeX3, but somehow regular expressions do not work in Boolean expressions. For convenience, I am using Lua instead. That is to say, the following code only runs in LuaLaTeX. The idea is simple: read the ind file generated by makeindex and fine-tune it based on our purposes.

You may need to compile the document twice for it work properly.

enter image description here

documentclass{scrartcl}

usepackage{imakeidx}
usepackage[indentunit=15pt,justific=raggedright, columnsep=10pt, font=footnotesize]{idxlayout}
usepackage{etoolbox}
usepackage{luacode}
usepackage{expl3}
usepackage{hyperref}

makeindex

begin{luacode*}

local inspect = require"inspect"

function fine_tune_index()
    local index_filename = tex.jobname .. ".ind"
    local newindex_filename = tex.jobname .. "-new.ind"
    local attr = lfs.attributes(index_filename)
    if attr == nil then
        return
    end

    local index_lines = {}
    for line in io.lines(index_filename) do
        table.insert(index_lines, line)
    end
    
    local new_lines = {}
    local new_line_buffer = {}
    local line=nil
    local has_space=true
    local search_end = #index_lines - 1
    local item_count = 0
    
    function flush_line_buffer()
        if #new_line_buffer > 1 then
            table.insert(new_line_buffer, 1, [[begin{minipage}{linewidth}]])
            table.insert(new_line_buffer, 4, [[end{minipage}]])
        end
        for _, item in ipairs(new_line_buffer) do
            table.insert(new_lines, item)
        end
        new_line_buffer = {}
    end
    
    for i=2,search_end do
        line = index_lines[i]
        if line:find("item") then
            item_count = item_count + 1
            flush_line_buffer()
            if not has_space then
                table.insert(new_lines, "indexspace")
            end
            has_space = false
            table.insert(new_line_buffer, line)
        elseif line:find("indexspace") then
            has_space=true
            flush_line_buffer()
            table.insert(new_lines, line)
        else
            table.insert(new_line_buffer, line)
        end
    end
    
    flush_line_buffer()
    table.insert(new_lines, 1, index_lines[1])
    table.insert(new_lines, index_lines[#index_lines])
    local out_text = table.concat(new_lines, "n")
    local outfile = io.open(newindex_filename, "w")
    outfile:write(out_text)
    outfile:close()
end


end{luacode*}

AtEndDocument{
    directlua{fine_tune_index()}
}

begin{document}

Some text.

index{Donahue!Alice}
index{Donahue!Barney}

index{Jones!Alvin}
index{Jones!Billy}
index{Jones!Carter}
index{Jones!Dennis}

index{Smith!Alex}
index{Smith!Brian}
index{Smith!Carrie}
index{Smith!Dewey}
index{Smith!Eric}
index{Smith!Frank}
index{Smith!Gary}
index{Smith!Huey}
index{Smith!Irma}
index{Smith!Joey}
index{Smith!Kevin}

index{Scullion!Alex}
index{Scullion!Brian}
index{Scullion!Carrie}

index{White!Alice}
index{White!Barney}

idxlayout{columns=2, font=small, columnsep=20pt}


ExplSyntaxOn
% create a new int variable to bypass checks
int_new:c {jobname-new@idxfile}
ExplSyntaxOff
printindex[jobname-new]


end{document}

Answered by Alan Xiang on May 24, 2021

Ok, here's a LaTeX solution.

Unfortunately, the idxlayout package doesn't offer a lot of hooks to make this kind of modifications, so my changes are somewhat invasive and will break if you use different layout options.

If you run into problems, the following could be made a bit more generic.

You should put this code into the preamble, for instance after loading idxlayout. What it does is redefine the required internals from idxlayout to make the desired changes. I tried to be as minimally invasive as possible, but meh.

% Could put this into a separate package which loads idxlayout
makeatletter
% no additional space before new letters
letindexspacerelax
% rescue original definition
letorig@idxitem@idxitem
% we need to know if a subitem is the first after an item
newififafteritem

% item should do what it originally does, plus add vertical space,
% plus set the flag for subitem
renewcommand{@idxitem}{%
  orig@idxitem
  vspace{ila@initsep}%
  afteritemtrue
}

% subitem should refer to original definition of @idxitem (to avoid
% adding vertical space), plus add penalty if it's the first subitem.
renewcommand{subitem}{%
  orig@idxitem
  ifafteritemnobreakfi
  hspace*{ila@subindent}%
  afteritemfalse
}%
renewcommand{subsubitem}{orig@idxitemhspace*{ila@subsubindent}}%
makeatother

Result:

resulting index layout

Answered by Stephan Lehmke on May 24, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP