14

I am trying to reproduce an old dictionary in latex. It is formatted in the following way:

  • three-columns layout
  • first three letters of the first entry of the column in top of it

dictionnary

Here is the code I started to write:

\documentclass[8pt]{amsbook}

\usepackage{multicol}
\usepackage{microtype}
\usepackage{lipsum}
\usepackage[margin=1cm]{geometry}

\setlength{\columnseprule}{1pt}

\begin{document}
    \bfseries
    \begin{multicols}{3}
        \begin{description}
            \item[lorem] \lipsum[1]
            \item[ipsum] \lipsum[2]
            \item[dolor] \lipsum[3]
            \item[sit] \lipsum[4]
            \item[amet] \lipsum[5]
        \end{description}
    \end{multicols}
\end{document}

The problem I have is that the column rule is not center. Moreover, I have no idea on how to add column headings.

attempt

  • Are you open to use book class? – Niranjan Oct 30 at 15:40
  • I prefer AMS classes but if a solution exists with native classes it would already be really great – Blincer Oct 30 at 15:41
  • Do you want the column headings to show the first three letters of the top-most entry of the corresponding column? Because in the image that doesn't seem to be the case. Also, do the entries have to be defined using the description environment? – siracusa Nov 2 at 9:31
  • In the image dictionary, it is the last entry in the column that is taken into account but I find it less intuitive. And the use of description was just an idea. – Blincer Nov 2 at 9:46
11
+75

Here's a solution that produces a three-column layout with headers similiar to the one in the given image. The code works with both the book and amsbook classes, it uses the multicol package for creating the columns and the fancyhdr package for the column headers, and provides a quite simple user interface for printing the dictionary:

\begin{dict}
\dictentry{foo} Description text of foo.
\dictentry{bar} ...
\end{dict}

Using the multicol package makes creating the headers from the individual entries a bit more complicated because it processes the whole text before it splits the result across several columns and pages. Because of that, we do neither know in what column nor on what page an entry is processed at the moment. We thus have to make the shifting of information to the column headers a three-pass process.

On the first pass the columns are typeset and information about the columns the individual entries occur in is written to the .aux file. Fortunately, multicol provides a package option colaction which faciliates exactly that by providing a command \docolaction to execute conditional code depending on the column this command is processed in.

On the second pass we know the corresponding column numbers (the start of) an entry occurs in (in form of \dict@entry@page{<entry number>}{<page number>) calls). From that we can now determine the page number of each entry by counting page numbers up whenever a switch from column three to column one occurs. Again, this information is written to the .aux file along with the entry name.

Finally, on the third pass we can use the information from the .aux file to determine the first and last entry in each column on each page from macro calls of the form \dict@entry@name{<page number>}{<column number>}{<entry name>}. After the third compilation run the headers are fully updated.

Two macros \dict@name@<page>@<column>@first and \dict@name@<page>@<column>@last are provided that can be used in the column header to get the first and last entries of each column and page, respectively. The typesetting of the headers appears in \dict@header. For demonstration purpose it uses both the first and last entry of a column, clipped to the three initial letters (\dict@initials).

Full example dictionary (sample text taken from here):

\documentclass{amsbook}

\usepackage[colaction]{multicol}
\usepackage{fancyhdr}
\usepackage[margin=1cm,bottom=2cm,a6paper,landscape]{geometry}

\makeatletter

\newcounter{dict@page}
\newcounter{dict@entry}
\newcounter{dict@lastcol}

% Called from .aux file to link page number with each entry number
\newcommand\dict@entry@page[2]{%
    \expandafter\gdef\csname dict@#1@page\endcsname{#2}%
}

% Called from .aux file to link entry names with each page and column number
\newcommand\dict@entry@name[3]{%
    \global\let\dict@ifrerunrequired=\iffalse
    \unless\ifcsname dict@name@#1@#2@first\endcsname
        \expandafter\gdef\csname dict@name@#1@#2@first\endcsname{#3}%
    \fi
    \expandafter\gdef\csname dict@name@#1@#2@last\endcsname{#3}%
}


\newcommand\dict@curname@first{???}
\newcommand\dict@curname@last{???}

% Create column header output for column number #1
\newcommand\dict@header[1]{%
    % If name is defined for column, use it ...
    \ifcsname dict@name@\arabic{page}@#1@first\endcsname
        \edef\dict@temp{\csname dict@name@\arabic{page}@#1@first\endcsname}%
        \edef\dict@temp{\expandafter\dict@initials\expandafter{\dict@temp}}%
        \xdef\dict@curname@first{\noexpand\MakeUppercase{\dict@temp}}%
    % ... otherwise use name of previous column
    \else
        \expandafter\gdef\expandafter\dict@curname@first\expandafter{\dict@curname@last}%
    \fi
    \ifcsname dict@name@\arabic{page}@#1@last\endcsname
        \edef\dict@temp{\csname dict@name@\arabic{page}@#1@last\endcsname}%
        \edef\dict@temp{\expandafter\dict@initials\expandafter{\dict@temp}}%
        \xdef\dict@curname@last{\noexpand\MakeUppercase{\dict@temp}}%
    \else
        \expandafter\gdef\expandafter\dict@curname@last\expandafter{\dict@curname@first}%
    \fi
    % Output the column header box
    \makebox[\dimexpr(\textwidth-2\columnsep)/3][c]{%
        \textbf{\dict@curname@first--\dict@curname@last}%
    }%
}

% Get first three letters/tokens of entry name
\newcommand\dict@initials[1]{\dict@initials@#1\relax\relax\relax\@end}
\def\dict@initials@#1#2#3#4\@end{#1#2#3}

% Header setup
\fancyhead[L]{\dict@header1}
\fancyhead[C]{\dict@header2}
\fancyhead[R]{\dict@header3}
\renewcommand\headrulewidth{1.2pt}
\headsep=8pt
\headheight=20pt
\thispagestyle{fancy}
\pagestyle{fancy}

% Column setup
\columnsep=12pt
\columnseprule=1.2pt


% User interface

\newenvironment{dict}{%
    \setcounter{dict@lastcol}{0}%
    \setcounter{dict@page}{\value{page}}%
    \begin{multicols}{3}
    \begin{list}{}{%
        \labelwidth=0pt
        \leftmargin=1em
        \itemindent=-\leftmargin
        \let\makelabel=\descriptionlabel
        \parsep=0pt
    }
    \leftmargin=1.5em
    \footnotesize
}{%
    \end{list}
    \end{multicols}
}

\newcommand\dictentry[1]{%
    \stepcounter{dict@entry}%
    \item[#1]%
    \leavevmode
    % Figure out page breaks from column numbers
    \docolaction{%
        \ifnum\value{dict@lastcol}=3
            \stepcounter{dict@page}
        \fi
        \setcounter{dict@lastcol}{1}%
    }{\setcounter{dict@lastcol}{2}}{\setcounter{dict@lastcol}{3}}%
    % Write page number for each entry
    \immediate\write\@auxout{%
        \string\dict@entry@page{\arabic{dict@entry}}{\arabic{dict@page}}%
    }%
    % If page number is available, write entry name for each page and column number
    \ifcsname dict@\arabic{dict@entry}@page\endcsname
        \immediate\write\@auxout{%
            \string\dict@entry@name{\arabic{dict@page}}{\arabic{dict@lastcol}}{#1}%
        }%
    \fi
    \ignorespaces
}

\makeatother

\begin{document}
\begin{dict}
\dictentry{a} Creates a link around SVG elements
\dictentry{altGlyph} Provides control over the glyphs used to render particular character data
\dictentry{altGlyphDef} Defines a substitution set for glyphs
\dictentry{altGlyphItem} Defines a candidate set of glyph substitutions
\dictentry{animate} Defines how an attribute of an element changes over time
\dictentry{animateMotion} Causes a referenced element to move along a motion path
\dictentry{animateTransform} Animates a transformation attribute on a target element, thereby allowing animations to control translation, scaling, rotation and/or skewing
\dictentry{circle} Defines a circle
\dictentry{clipPath} Clipping is about hiding what normally would be drawn. The stencil which defines what is and what isn't drawn is called a clipping path
color-profile   Specifies a color profile description (when the document is styled using CSS)
\dictentry{cursor} Defines a platform-independent custom cursor
\dictentry{defs} A container for referenced elements
\dictentry{desc} A text-only description for container elements or graphic elements in SVG (user agents may display the text as a tooltip)
\dictentry{ellipse} Defines an ellipse
\dictentry{feBlend} Composes two objects together according to a certain blending mode
\dictentry{filter} Container for filter effects
\dictentry{font} Defines a font
\dictentry{g} Used to group together elements
\dictentry{glyph} Defines the graphics for a given glyph
\dictentry{glyphRef} Defines a possible glyph to use
\dictentry{image} Defines an image
\dictentry{line} Defines a line
\dictentry{linearGradient} Defines a linear gradient. Linear gradients fill the object by using a vector, and can be defined as horizontal, vertical or angular gradients.
\dictentry{marker} Markers can be placed on the vertices of lines, polylines, polygons and paths. These elements can use the marker attributes "marker-start", "marker-mid" and "marker-end"' which inherit by default or can be set to 'none' or the URI of a defined marker. You must first define the marker before you can reference it via its URI. Any kind of shape can be put inside marker. They are drawn on top of the element they are attached to
\dictentry{mask} Masking is a combination of opacity values and clipping. Like clipping you can use shapes, text or paths to define sections of the mask. The default state of a mask is fully transparent which is the opposite of clipping plane. The graphics in a mask sets how opaque portions of the mask are
\dictentry{metadata} Specifies metadata
\dictentry{path} Defines a path
\dictentry{pattern} Defines the coordinates you want the view to show and the size of the view. Then you add shapes into your pattern. The pattern repeats when an edge of the view box (viewing area) is hit
\dictentry{polygon} Defines a graphic that contains at least three sides
\dictentry{polyline} Defines any shape that consists of only straight lines
\dictentry{radialGradient} Defines a radial gradient. Radial gradients are created by taking a circle and smoothly changing values between gradient stops from the focus point to the outside radius.
\dictentry{rect} Defines a rectangle
\dictentry{script} Container for scripts (e.g., ECMAScript)
\dictentry{set} Sets the value of an attribute for a specified duration
\dictentry{stop} The stops for a gradient
\dictentry{style} Allows style sheets to be embedded directly within SVG content
\dictentry{svg} Creates an SVG document fragment
\dictentry{text} Defines a text
\dictentry{tref} References any text element in the SVG document and reuse it
\dictentry{tspan} Identical to the text element but can be nested inside text tags and inside itself
\dictentry{use} Uses a URI to reference a g, svg or other graphical element with a unique id attribute and replicate it. The copy is only a reference to the original so only the original exists in the document. Any change to the original affects all copies.
\end{dict}
\end{document}

outputs

Output of page 1

Output of page 2

Output of page 3

As you can see from the output, there's still room for optimizations. For example, the spacing at the begin and end of the columns doesn't seem optimal. Also, there's no "Label(s) may have changed" warning after the second pass.


EDIT: Thanks to Frank Mittelbach's comment the problem of wrong column headers due to \docolactions happening before the actual start of an item's text could be fixed. Another issue with the headers was fixed, which occured if a single entry spanned more than one column.

  • 1
    I will study your answer more deeply today in order to find a fix but it looks like an amazing job, thank you! – Blincer Nov 5 at 9:31
  • 1
    There is no bug in \docolaction, it is just that you think it is placed after the \item but in fact it is placed before. The item is really only triggered when there is text ie by \everypar. So the \docolaction appears before the break penalty and the space the the item generates. Thus it is on page 2 third column. What you need to change is a) add \leavevmode before \docolaction so that the \item is placed and b) add \ignorespaces at the end so that there is no space produce now that we have started the item text. That'll fix it – Frank Mittelbach Nov 5 at 22:09
  • 1
    A minor issue with your code is that it needs a further run after LaTeX stopped saying "rerun to get x-refs right". That can be dangerous. But otherwise, good job! – Frank Mittelbach Nov 5 at 22:15
  • @FrankMittelbach Many thanks for your note, I didn't know how \item works under the hood. The issue is fixed now. I wasn't able to solve the missing warning, though. Following the approach multicol uses, \@tempswatrue has to be triggered on the second inputting of the .aux file somehow, but I either never get the warning after the second pass, or all the time. – siracusa Nov 6 at 4:00
  • 1
    @siracusa I add also my +1. Many compliments. It is very similar to the vocabulary. – Sebastiano Nov 12 at 12:46
4

I don't know amsbook class much, but book class solves your centering-problem automatically. Now for your headings, I'll recommend fancyhdr package. I also felt that the headers are large in size, therefore I've redefined all the headings. Please see the code below.

\documentclass[8pt]{book}
\usepackage{fancyhdr}
\usepackage{multicol}
\usepackage{microtype}
\usepackage{lipsum}
\usepackage[margin=1cm]{geometry}
\newcommand{\llhead}[1]{\lhead{\LARGE#1}} % You can adjust the size by changing LARGE to any desired size.
\newcommand{\lrhead}[1]{\rhead{\LARGE#1}}
\newcommand{\lchead}[1]{\chead{\LARGE#1}}
\newcommand{\markall}[3]{\llhead{#1}\lchead{#2}\lrhead{#3}}
\setlength{\columnseprule}{1pt}

\begin{document}
\bfseries
\thispagestyle{fancy}
\markall{left head}{center head}{right head}
    \begin{multicols}{3}
        \begin{description}
            \item[lorem] \lipsum[1]
            \item[ipsum] \lipsum[2]
            \item[dolor] \lipsum[3]
            \item[sit] \lipsum[4]
            \item[amet] \lipsum[5]
        \end{description}
    \end{multicols}
\end{document}
  • Thank you for the answer and more particularly for the solution to the problem raised by the AMS class. On the other hand, I was expecting an automatic filling of the column titles (with the command \markboth)? I upvoted your post. – Blincer Oct 30 at 15:48
  • Ok, so these are not really column-titles. These are actual page headings provided by fancyhdr package. By looking at the expected output, this was the simplest solution which came to my mind. Please explain what do you expect by autofilling of the column titles? – Niranjan Oct 30 at 15:52
  • For example, in this template latextemplates.com/template/dictionary full entries are displayed, my goal was similar but with three columns and only part of the word. – Blincer Oct 30 at 15:53
  • so markboth command also is related to the page headings, it has two arguments, first argument is left title and the other one is right head. In your case you expect three heads. Please see the edit. – Niranjan Oct 30 at 16:12

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.