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 ?

ChrisAga
  • 141

3 Answers3

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}

ChrisAga
  • 141
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.

ChrisAga
  • 141
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.

Ricardo
  • 101