2

I want to render a two columns pdf document using markdown fenced divs. The minimal example is this :

:::::::::::::: {.columns data-latex=""}
::: {.column width="40%" data-latex="[t]{0.4\textwidth}"}
contents...
:::
::: {.column width="60%" data-latex="[t]{0.6\textwidth}"}
contents...
:::
::::::::::::::

The rendering is OK in html, but apparently somebody decided that multicolumn rendering in latex is for beamer only, so it doesn't work with plain latex and then with pdf. I can't switch to pandoc's html pdf engine since I need latex templating for my final document.

The minipage latex environment seems very convenient to achieve what I want. After quite a lot of investigations, I came with this lua filter :

local pandocList = require 'pandoc.List'

Div = function (div)
  local options = div.attributes['data-latex']
  if options == nil then return nil end

  -- if the output format is not latex, the object is left unchanged
  if FORMAT ~= 'latex' and FORMAT ~= 'beamer' then
    div.attributes['data-latex'] = nil
    return div
  end

  local env = div.classes[1]
  -- if the div has no class, the object is left unchanged
  if not env then return nil end

  local returnedList
  
  -- build the returned list of blocks
  if env == 'column' then
    local beginEnv = pandocList:new{pandoc.RawBlock('tex', '\\begin' .. '{' .. 'minipage' .. '}' .. options)}
    local endEnv = pandocList:new{pandoc.RawBlock('tex', '\\end{' .. 'minipage' .. '}')}
    returnedList = beginEnv .. div.content .. endEnv
  end
  return returnedList
end

Unfortunately, the generated latex document (pandoc --lua-filter ./latex-div.lua -o test.latex test.md) is the following which doesn't render as intended because of the blank line between the end of the first minipage and the begining of the second one :

\begin{document}

\begin{minipage}[t]{0.4\textwidth}

contents\ldots{}

\end{minipage}

\begin{minipage}[t]{0.6\textwidth}

contents\ldots{}

\end{minipage}

\end{document}

I am almost there. How can I get rid of this unwanted blank line without reprocessing the latex file ?

3 Answers 3

1

After further investigations try and errors. I am eventually able to answer my own question (in case it would be useful to someone).

Since the nested divs are processed before the parent div, it's possible to reprocess them to close a minipage environment and open the next one in the same pandoc.RawBlock (and obviously get rid of the unwanted blank line).

Here is the new lua filter code :

local pandocList = require 'pandoc.List'

Div = function (div)
  local options = div.attributes['data-latex']
  if options == nil then return nil end

  -- if the output format is not latex, the object is left unchanged
  if FORMAT ~= 'latex' and FORMAT ~= 'beamer' then
    div.attributes['data-latex'] = nil
    return div
  end

  local env = div.classes[1]
  -- if the div has no class, the object is left unchanged
  if not env then return nil end

  local returnedList
  
  -- build the returned list of blocks
  if env == 'column' then
    local beginEnv = pandocList:new{pandoc.RawBlock('tex', '\\begin' .. '{' .. 'minipage' .. '}' .. options)}
    local endEnv = pandocList:new{pandoc.RawBlock('tex', '\\end{' .. 'minipage' .. '}')}
    returnedList = beginEnv .. div.content .. endEnv

  elseif env == 'columns' then
    -- merge two consecutives RawBlocks (\end... and \begin...)
    -- to get rid of the extra blank line
    local blocks = div.content
    local rbtxt = ''

    for i = #blocks-1, 1, -1 do
      if i > 1 and blocks[i].tag == 'RawBlock' and blocks[i].text:match 'end' 
      and blocks[i+1].tag == 'RawBlock' and blocks[i+1].text:match 'begin' then
        rbtxt = blocks[i].text .. blocks[i+1].text
        blocks:remove(i+1)
        blocks[i].text = rbtxt
      end
    end
    returnedList=blocks
  end
  return returnedList
end

The generated latex document is correct now :

\begin{document}

\begin{minipage}[t]{0.4\textwidth}

contents\ldots{}

\end{minipage}\begin{minipage}[t]{0.6\textwidth}

contents\ldots{}

\end{minipage}

\end{document}
1
  • Thank you, @ChrisAga. All "standard" Lua filters insist on 50/50 columns and this is the only I could find that actually allows me to produce unbalanced columns without too much delving into LaTeX. Also, it's incredibly small\ I haven't been doing Lua for 6 years, so I would not risk an edit, but I quite frankly don't like the whole width="70%" data-latex="[t]{0.7\textwidth}". Feels a lot like a duplication and also feels like Lua could just grab the width attribute and generate the necessary TeX bits from it. I'll have a go at this and I'll put it here if it works.
    – Ricardo
    Commented Jun 30, 2023 at 7:24
1

It turns-out that there is a simpler solution using Tex's \mbox to make the two minipages stick together despite of the blank line.

local pandocList = require 'pandoc.List'

Div = function (div)
  local options = div.attributes['data-latex']
  if options == nil then return nil end

  -- if the output format is not latex, the object is left unchanged
  if FORMAT ~= 'latex' and FORMAT ~= 'beamer' then
    div.attributes['data-latex'] = nil
    return div
  end

  local env = div.classes[1]
  -- if the div has no class, the object is left unchanged
  if not env then return nil end

  local returnedList
  
  -- build the returned list of blocks
  if env == 'column' then
    local beginEnv = pandocList:new{pandoc.RawBlock('tex', '\\begin' .. '{' .. 'minipage' .. '}' .. options)}
    local endEnv = pandocList:new{pandoc.RawBlock('tex', '\\end{' .. 'minipage' .. '}')}
    returnedList = beginEnv .. div.content .. endEnv

  elseif env == 'columns' then
    -- it turns-out that a simple Tex \mbox do the job
    begin_env = List:new{pandoc.RawBlock('tex', '\\mbox{')}
    end_env = List:new{pandoc.RawBlock('tex', '}')}
    returned_list = begin_env .. div.content .. end_env
  end
  return returnedList
end

Resulting latex code is :

\begin{document}

\mbox{

\begin{minipage}[t]{0.4\textwidth}

contents\ldots{}

\end{minipage}\begin{minipage}[t]{0.6\textwidth}

contents\ldots{}

\end{minipage}

}

\end{document}

Since then I posted a more comprehensive filter on a git repository along with other filters that might be usefull too.

1
  • I recently shared a new version of this filter and edited my answer to post the link
    – ChrisAga
    Commented Jun 20, 2022 at 18:33
0

Intro

Folllowing my comment, and after a bit of struggle, I believe I managed to add a bit of tidiness to ChrisAga's solution to his own question.

NOTE: this solution bears marginal merit. It's nothing but an edit on the author's initial effort and I still consider this as belonging to ChrisAga.

Lua script

local pandocList = require 'pandoc.List'

Div = function (div)
  local width = div.attributes['width']
  local options = ""
  if width ~= nil then 
    w = tonumber(width)
    if w == nil then 
      width = string.gsub(width, '%%', '')
      w = tonumber(width)
    end
    if w ~= nil then
      if w >= 1 then w = w / 100 end
      options = "[t]{" .. w .. "\\textwidth}"
    end
  end

  if options == nil then return nil end

  -- if the output format is not latex, the object is left unchanged
  if FORMAT ~= 'latex' and FORMAT ~= 'beamer' then
    div.attributes['data-latex'] = nil
    return div
  end

  local env = div.classes[1]
  -- if the div has no class, the object is left unchanged
  if not env then return nil end

  local returnedList
  
  -- build the returned list of blocks
  if env == 'column' then
    local beginEnv = pandocList:new{pandoc.RawBlock('tex', '\\begin' .. '{' .. 'minipage' .. '}' .. options)}
    local endEnv = pandocList:new{pandoc.RawBlock('tex', '\\end{' .. 'minipage' .. '}')}
    returnedList = beginEnv .. div.content .. endEnv

  elseif env == 'columns' then
    -- merge two consecutives RawBlocks (\end... and \begin...)
    -- to get rid of the extra blank line
    local blocks = div.content
    local rbtxt = ''

    for i = #blocks-1, 1, -1 do
      if i > 1 and blocks[i].tag == 'RawBlock' and blocks[i].text:match 'end' 
      and blocks[i+1].tag == 'RawBlock' and blocks[i+1].text:match 'begin' then
        rbtxt = blocks[i].text .. blocks[i+1].text
        blocks:remove(i+1)
        blocks[i].text = rbtxt
      end
    end
    returnedList=blocks
  end
  return returnedList
end

Markdown

With this script, now the Markdown can be simplified to something like:

:::::::::::::: {.columns}
::: {.column width="40%"}
contents...
:::
::: {.column width="60%"}
contents...
:::
::::::::::::::

Approach

My goal was to get rid of the duplication in:

::: {.column width="40%" data-latex="[t]{0.4\textwidth}"}

where we say both 40% and 0.4. This not only is convoluted, but also opens the door to inconsistencies.

What I do here is to build the variable options from the width parameter, instead of taking it from the parameter data-latex.

The width parameter can also be expressed in a percentage, [1, 100] scale or [0, 1[ scale. The value 1 is unapologetically considered as part of the [1, 100] scale because otherwise it would be equivalent to a 100% width, which kind of precludes the need for a columnar environment at all.

1
  • This post was about my struggling with newlines in the LaTeX code produced by the Lua filter. The filter itself was just a proof of concept at that time. @Ricardo you might be interested in a more accomplished version here : github.com/chrisaga/hk-pandoc-filters/tree/main/column-div
    – ChrisAga
    Commented Aug 21, 2023 at 8:01

You must log in to answer this question.

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