2

I have a list (not a table) that I want to typeset as a grid for filing labels. The list is in CSV-ish format with comments where similar entries are grouped into a single line.

\startseparatedlist[NaturalTable]
One,One.Two,One.Three
Two
Three
#Four
Five
\stopseparatedlist

The labels are a fixed size: 2" by 0.5":

\startsetups labels
    \setupTABLE[row][each,width=2in,height=0.5in,align={middle,lohi}]
\stopsetups

The typesetting requirements:

  1. The table cells per row are based on \makeupwidth / labelwidth and not CSV columns. Using the database module would require some state to keep track of when to add \eTR/\bTR commands.
  2. The labels should be foldable with a dotted line. My idea was two rows per labels. The bottom frame of the top row is dotted (how: MetaPost?) and the cells are empty. The bottom row contains the actual content.
  3. Row-major ordering.

Here's a minimal non-working example:

Go from:

% Width should control the cells per row.
\setuplayout
    [ backspace=0.75in
    , width=7in
    ]

\definemagiccommand[NaturalTable]
    [ width=2
    , height=1
    , ...=..., === % Maybe inherit from or wrap \defineseparatedlist?
    ]

\magiccommand[NaturalTable]
    One,One.Two,One.Three
    Two
    Three
    #Four
    Five
    Six
\stopmagiccommand

To:

Image of Example Labels Result

Note that all the lines in the final result are the same thickness. I achieved this by manually removing overlapping lines in MetaFun which was... not fun (and still not perfect, see the bottom-right corners).


This is what I have so far. The problems are pretty obvious, and I've created an MWE for each.

\startuseMPgraphic{dottedBottom}
    draw bottomboundary OverlayBox withpen pencircle scaled \frameddimension{rulethickness} dashed withdots;
    setbounds currentpicture to boundingbox OverlayBox;
\stopuseMPgraphic

\defineoverlay[dottedBottom][\useMPgraphic{dottedBottom}]

\startsetups label
    \setupTABLE[frame=off,width=2in,height=0.5in]
    \setupTABLE[row][each][align={middle,lohi}]
    \setupTABLE[row][first][background={dottedBottom}]
\stopsetups

\define[1]\framedlabel{%
    \framed[strut=no,offset=0pt]{%
        \bTABLE[setups=label,split=yes]%
            \bTR \bTD \eTD \eTR%
            \bTR \bTD #1 \eTD \eTR%
        \eTABLE%
    }\hskip 0pt }


\vbox{
    \rightskip 0pt plus 1fil
    \setupinterlinespace[off]
    \leavevmode
    \framedlabel{One}
    \framedlabel{Two}
    \framedlabel{Three}
    \framedlabel{Four}
    \framedlabel{Five}
    \framedlabel{Six}
}


\usemodule[database]

\define\framedlabelfor{
    %TODO
}

\defineseparatedlist[LabelsCSV][separator=comma,command=\framedlabelfor]

\definestartstop[Labels]
    [ before={\vbox\bgroup\rightskip 0pt plus 1fil\setupinterlinespace[off]\leavevmode\startLabelsCSV}
    , after={\stopLabelsCSV\egroup}
    ]

How do I prevent the doubled lines:

\setuplayout
    [ backspace=0.75in
    , width=7in
    ]

\vbox{
    \rightskip 0pt plus 1fil
    \setupinterlinespace[off]
    \leavevmode
    \framed[height=1in,width=2in]{001}\hskip0pt
    \framed[height=1in,width=2in]{002}\hskip0pt
    \framed[height=1in,width=2in]{003}\hskip0pt
    \framed[height=1in,width=2in]{004}\hskip0pt
    \framed[height=1in,width=2in]{005}\hskip0pt
    \framed[height=1in,width=2in]{006}\hskip0pt
    \framed[height=1in,width=2in]{007}\hskip0pt
}

edit: I've sort-of solved the doubled lines by setting the \frameoffset to half the \rulethickness. Unfortunately there are now glitches visible 2-line and 3-line intersections where the unopposed line extends slightly beyond the intersection. See the attached image. Are these rounding errors - they only occur at offsets less than 1pt? And how do I get rid of them?

\vbox{
    \rightskip 0pt plus 1fil
    \setupinterlinespace[off]
    \leavevmode
    \framed[height=1in,width=2in,rulethickness=1pt,frameoffset=0.5pt]{001}\hskip0pt
    \framed[height=1in,width=2in,rulethickness=1pt,frameoffset=0.5pt]{002}\hskip0pt
    \framed[height=1in,width=2in,rulethickness=1pt,frameoffset=0.5pt]{003}\hskip0pt
    \framed[height=1in,width=2in,rulethickness=1pt,frameoffset=0.5pt]{004}\hskip0pt
    \framed[height=1in,width=2in,rulethickness=1pt,frameoffset=0.5pt]{005}\hskip0pt
    \framed[height=1in,width=2in,rulethickness=1pt,frameoffset=0.5pt]{006}\hskip0pt
    \framed[height=1in,width=2in,rulethickness=1pt,frameoffset=0.5pt]{007}\hskip0pt
}

Glitch: Right angle line extends beyond intersection

And how do I create a varargs command:

\define\foreach{
    % TODO
}

\foreach{001}
\foreach{001}{002}
4
  • 1
    Questions seeking debugging help (“why isn't this code working?”) must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Questions without a clear problem statement are not useful to other readers. See minimal working example (MWE). Feb 11, 2020 at 3:13
  • 2
    What are these magiccommand shenanigans? Could you please ask one specific question? And no, “how do I do this complex thing” is not a specific question. Feb 11, 2020 at 7:50
  • 1
    Try saving your data to a buffer and post-process it with Lua.
    – TeXnician
    Feb 11, 2020 at 11:31
  • @HenriMenke: My idea is to use inline frames that autowrap to the text area. Specific: how to align the frames to avoid doubled lines? Fill each frame with a 1-column 2-cell table, no borders. It'll fill the vertical space due to paragraph mode(?). Use MetaFun to draw a dotted line at the bottom of the top cell. Reuse the CSV parser from the database module to draw each frame. Specific question: how to create a variadic command for defineseparatedlist? But if I demonstrate the broad picture without specifics someone might tell me this approach is inefficient and recommend something else.
    – user19087
    Feb 11, 2020 at 14:31

1 Answer 1

1

The command example from the database module throws Lua errors so I gave up on the varargs macro problem and processed the buffer myself in Lua. Halfway through I realized I preferred Lua table syntax to CSV syntax, so two approaches are provided. The first is a mix of Lua/ConTeXt for handing CSV. The other Lua-only for when the data is stored in a Lua.

The fields are processed individually which makes this unsuited for tabular data, unlike the database module which works at the record level. Supporting nested data is as simple as modifying the csvforeach function, for example if I wanted to add a description field only visible in the index. The main advantage to using Lua tables is decoupling the formatting from the structure.

\setuppapersize[letter]

% text heigh/width: 9in/7in
\setuplayout
    [ topspace=0.5in
    , height=10in
    , header=0.5in
    , footer=0.5in
    , backspace=0.75in
    , width=7in
    , leftmargindistance=0.15in
    , leftmargin=0.6in
    , rightmargindistance=0.15in
    , rightmargin=0.6in
    ]

\setupcolors[state=start]

\startluacode
    userdata = userdata or {}

    function userdata.parsecsv(s)
        local field = '"' * lpeg.Cs(((1 - lpeg.P'"') + lpeg.P'""'/'"')^0) * '"'
                          + lpeg.C((1 - lpeg.S'",\r\n')^0)
        local record = lpeg.Ct(field * ("," * field)^0)
        local file = lpeg.Ct(record * (lpeg.S"\r\n" * record)^0 * (lpeg.P"\r\n" + -1))
        return lpeg.match(file,s)
    end

    function userdata.csvforeach(t,c)
        local i = 0
        for r,v in ipairs(t) do
            for f,v in ipairs(v) do
                i = i + 1
                if type(c) == "function" then
                    c(i,v)
                else
                    context("%s{%s}{%s}",c,i,v)
                end
            end
        end
    end
\stopluacode

\define[2]\csvforeach{%
    \ctxlua{%
        userdata.csvforeach(userdata.#1,"\luaescapestring{\normalunexpanded{#2}}")}}

\def\startcsv{\dosingleempty\dostartcsv}

\def\dostartcsv[#1]%
    {\iffirstargument
        \def\csvName{#1}
     \else
        \def\csvName{csv}
     \fi
     \dostartbuffer
        [\csvName]
        [startcsv]
        [stopcsv]}

\def\stopcsv%
    {\ctxlua
        {userdata.\csvName = userdata.parsecsv(buffers.getcontent("\csvName"))}}

\startuseMPgraphic{dottedBottom}
    draw bottomboundary OverlayBox withpen pencircle scaled \frameddimension{rulethickness} dashed withdots;
    setbounds currentpicture to boundingbox OverlayBox;
\stopuseMPgraphic

\defineoverlay[dottedBottom][\useMPgraphic{dottedBottom}]

\startsetups label
    \setupTABLE[frame=off,width=2in,height=0.5in]
    \setupTABLE[row][each][align={middle,lohi}]
    \setupTABLE[row][first][background={dottedBottom}]
\stopsetups

\startsetups labelframe
    \setupframed[strut=no,offset=0pt,rulethickness=1pt,frameoffset=0.5pt]
\stopsetups

\setuphead[title][
    align=middle,
    after={\hairline\blank[4*big]}
]

\setupcolumns[n=3,separator=rule,balance=yes,distance=0.5in]

\define[2]\framedlabel{%
    \setup{labelframe}%
    \framed{%
        \bTABLE[setups=label,split=no]%
            \bTR \bTD \eTD \eTR%
            \bTR \bTD \startitemize \sym{#1.} #2 \stopitemize \eTD \eTR%
        \eTABLE%
    }\hskip 0pt }

\define[2]\labelitem{\sym{#1.} #2}

\startcsv
Of,course
    ,it is!
\stopcsv

\starttext

\vbox{
    \rightskip 0pt plus 1fil
    \setupinterlinespace[off]
    \leavevmode
    \csvforeach{csv}{\framedlabel}
}

\title{Index}

\resetcounter[userpage]

\startcolumns
\startitemize
\csvforeach{csv}{\labelitem}
\stopitemize
\stopcolumns

\page[yes]

% If you prefer Lua, might as well stay in Lua.

\startluacode
    local sp = 65536
    context.vbox(function()
        tex.setglue("rightskip", 0, sp, 0, 2, 0)
        context.setupinterlinespace({"off"})
        context.leavevmode()
        userdata.csvforeach(userdata.csv,context.framedlabel)
    end)

    context.title("Index")
    context.resetcounter({"userpage"})
    context.startcolumns()
    context.startitemize()
    userdata.csvforeach(userdata.csv,context.labelitem)
    context.stopitemize()
    context.stopcolumns()
    context.page({"yes"})
\stopluacode

% Now using Lua tables rather than CSV.


\define\eachtigpar{\EveryPar{%
    \clubpenalties  5 10000 10000 10000 100 0%
    \widowpenalties 5 10000 10000 10000 100 0%
}}

\defineitemgroup[tig]
    [inner=\eachtigpar]


\define[2]\labelitem{\sym{#1.} #2}
\define[1]\labeldescription{\blank[medium]\page[no]#1}

\startluacode
    local data =
        { "One", "Two"
        , "Three"
        , "Four"
        ,{"Five", [=[
                  This deserves an explanation. The explanation
                  is multiple lines long because of \ConTeXt.
                  \startitemize
                  \item Reason one.
                  \item Reason two.
                  \stopitemize
                  ]=]}
        , "six"
        , "seven"
        }

    local function dedent(s)
        local lx,fx = {},{}
        for l in s:gmatch("[^\n]*") do
            if l:match("^%s*$") then
                table.insert(lx,"")
            else
                table.insert(lx,l)
                table.insert(fx,l)
            end
        end
        if #fx ~= 0 then
            local i,s,c,n = 0,false
            while true do
                for _,v in pairs(fx) do
                    if #v - i == 0 then
                        s = true
                        break
                    end
                    n = string.sub(v,i+1,i+1)
                    if not n:match("^%s") then
                        s = true
                        break
                    end
                    if c == nil then
                        c = n
                    elseif c ~= n then
                        s = true
                        break
                    end
                end
                if s then
                    break
                end
                c = nil
                i = i + 1
            end
            for k,v in pairs(lx) do
                lx[k] = string.sub(v,i+1)
            end
        end
        return table.concat(lx,"\n")
    end

    local function trim(s)
        return s:match("^%s*(.-)%s*$")
    end

    local function tableforeach(t,c)
        local i = 0
        for _,v in ipairs(t) do
            local d
            i = i + 1
            if type(v) == "table" then
                v,d = unpack(v)
            end
            if d then
                d = trim(dedent(d))
            end
            c(i,v,d)
        end
    end

    local function tablelabel(i,v,d)
        context.framedlabel(i,v)
    end

    local function tableindex(i,v,d)
        context.labelitem(i,v)
        if not d then
            return
        end
        context.labeldescription(d)
    end

    local sp = 65536
    context.vbox(function()
        tex.setglue("rightskip", 0, sp, 0, 2, 0)
        context.setupinterlinespace({"off"})
        context.leavevmode()
        tableforeach(data,tablelabel)
    end)

    context.title("Index")
    context.resetcounter({"userpage"})
    context.startcolumns()
    context.starttig()
    tableforeach(data,tableindex)
    context.stoptig()
    context.stopcolumns()
    context.page({"yes"})
\stopluacode

\stoptext

It is mainly done though the column and itemgroup formatting could still use some work.

1
  • 1
    You have to use \define[4]\SendMe (or \unexpanded\def\SendMe#1#2#3#4) instead of \def\SendMe#1#2#3#4 in the command example in the MyWay. Keep in mind that the MyWay article is 14 years old. Feb 15, 2020 at 6:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .