Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A lua filter enabling multiple columns in Latex, PDF, and HTML documents #191

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions column-div/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
DIFF ?= diff --strip-trailing-cr -u

.PHONY: test

test: test_latex test_html

test_html: sample.md expected.html column-div.lua
@pandoc -s --lua-filter column-div.lua --to=html $< \
| $(DIFF) expected.html -

test_latex: sample.md expected.tex column-div.lua
@pandoc --lua-filter column-div.lua --to=latex $< \
| $(DIFF) expected.tex -

expected.html: sample.md column-div.lua
pandoc -s --lua-filter column-div.lua --output $@ $<

expected.tex: sample.md column-div.lua
pandoc --lua-filter column-div.lua --output $@ $<
138 changes: 138 additions & 0 deletions column-div/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: "Column Div - leverage Pandoc native divs to make columns
an other things"
author: "Christophe Agathon"
---

Column Div
=======

Columns and other things with Pandoc's markdown

This Lua filter for Pandoc improves Pandoc's Div usage.Especially
fenced divs witten in Pandocs markdown.

v1.0. Copyright: © 2021 Christophe Agathon
<[email protected]>
License: MIT - see LICENSE file for details.

Introduction
------------
Pandoc fenced divs can be very powerful allowing providing, in
theory many document formating possibilities. Unfortunately, plain
Panfoc processing doesn't make full adventage of it and discards
some formating in HTML outputs and most of it in Latex outputs.

Multiple columns in document are only partialy accessible in
Beamer (not plain Latex) and HTML outputs.

As a result, it's not possible to render fancy multi columns
PDF document from markdown sources.

The main purpose of this filter is to make it possible and give
similar formating features for both Latex/PDF and HTML outputs.

Usage
-----

### Basic usage

Copy `column-div.lua` in your document folder or in your pandoc
data directory (details in
[Pandoc's manual](https://pandoc.org/MANUAL.html#option--lua-filter)).
Run it on your document with a `--luafilter` option:

```bash
pandoc --luafilter column-div.lua SOURCE.md -o OUTPUT.pdf

```

or specify it in a defaults file (details in
[Pandoc's manual](https://pandoc.org/MANUAL.html#option--defaults)).

This will generate consistent HTML, Latex and PDF outputs from
Pandoc markdown files.

### Formating the document

Everything is done with Pandoc's fenced divs with class names and
attributes. The attributes are similar to those from Latex and/or
HTML styling.

#### Multiple balanced columns
For Latex and PDF output, you will need to call the multicol
package. This can be done un the YAML header.

**Example:**

```markdown
---
header-includes:
- |
```{=latex}
\usepackage{multicol}

```
---

Some regular text

:::: {.multicols column-count="2"}
Some text formatted on 2 columns
::::
```

* Latex output is done with `multicols` environment.
* HTML output uses `style="column-count: 2"` on a div block.

#### Unbalanced columns

No specific Latex package are needed. We use Nested Pandoc divs in
the same way that columns and column environments are used in
Beamer/Latex.

**Example:**

```markdown

:::::::: {.columns}
:::: {.column width="20%" valign="c"}
Some text or image using 20% of the page width.
::::
:::: {.column width="80%" valign="c"}
Some text or image using 80% of the page with.
::::
::::::::
```

* Beamer/Latex output is based on columns and column environments
* Plain Latex (and PDF) rendering use minipage environments
* HTML rendering is not affected by this filter since Pandoc do it
well already (based on divs with `width` attributes).

#### Other usages

HTML : you can already create divs with whatever class names youl
like and style them with `style=" … "` attributes. This is
proccessed by Pandoc and as nothing to do with this filter.

This filter allows to do the same in Latex (and PDF).
The class name is used as the environment name and a
`data-latex=" … "` attribute allows you to pass options and
parameters to the `\begin` instruction.

To Do
-----

Others multi column features could be implemented as column
spacing, rules, etc.

Since Pandoc does a very good job with the `width` styling
attribute to implement variable column width, it could easily
support HTML balanced column via the `column-count` attribute.

Contributing
------------

PRs welcome.

115 changes: 115 additions & 0 deletions column-div/column-div.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
--[[
column-div - leverage Pandoc native divs to make balanced and unbalanced column
and other things based on class name and attirbutes.

Copyright: © 2021 Christophe Agathon <[email protected]>
License: MIT – see LICENSE file for details

Credits: Romain Lesur and Yihui Xie for the original column filter
implementation (output in beamer format).

Output: latex, pdf, html

Usage: classname attributes
balanced columns .columns column-count
columns(container) .columns
column(each column) .column width(percent) valign(t|c|b)
other divs .<somename> data-latex

See README.md for details

Note: You need to include multicol latex package to get balanced columns
in latex or pdf
I tried to use well known html or latex parameter.
Even if lua doen't like hyphens like in column-count.
--]]
local List = require 'pandoc.List'

function Div(div)
options = ''
local env = div.classes[1]
alerque marked this conversation as resolved.
Show resolved Hide resolved
alerque marked this conversation as resolved.
Show resolved Hide resolved
local returned_list
local begin_env
local end_env
local opt

-- if the div has no class, the object is left unchanged
if not env then return nil end

-- if the output is beamer do columns
if FORMAT:match 'beamer' then
-- build the returned list of blocks
begin_env = List:new{pandoc.RawBlock('tex',
'\\begin' .. '{' .. env .. '}' .. options)}
end_env = List:new{pandoc.RawBlock('tex', '\\end{' .. env .. '}')}
returned_list = begin_env .. div.content .. end_env

-- if the format is latex then do minipage and others (like multicol)
elseif FORMAT:match 'latex' then
-- build the returned list of blocks
if env == 'column' then
--opt = div.attributes['width']
opt = div.attributes.width
if opt then
local width=tonumber(string.match(opt,'(%f[%d]%d[,.%d]*%f[%D])%%'))/100
options = '{' .. tostring(width) .. '\\columnwidth}'
end

opt = div.attributes.valign
if opt then options = '[' .. opt .. ']' .. options end

begin_env = List:new{pandoc.RawBlock('tex',
'\\begin' .. '{' .. 'minipage' .. '}' .. options)}
end_env = List:new{pandoc.RawBlock('tex', '\\end{' .. 'minipage' .. '}')}
returned_list = begin_env .. div.content .. end_env

elseif env == 'columns' then
-- merge two consecutives RawBlocks (\end... and \begin...)
-- to get rid of the unwanted 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
returned_list=blocks

else
-- other environments ex: multicols

-- process supported options
opt = div.attributes['column-count'] -- this synthax needed due to '_'
if opt then options = '{' .. opt .. '}' end

-- default if no known options
if options == '' then options = div.attributes.data-latex end

begin_env = List:new{pandoc.RawBlock('tex',
'\\begin' .. '{' .. env .. '}' .. options)}
end_env = List:new{pandoc.RawBlock('tex', '\\end{' .. env .. '}')}
returned_list = begin_env .. div.content .. end_env
end

-- if the format is html add support for multi columns
elseif FORMAT:match 'html' then
opt = div.attributes['column-count']
if opt then
-- add column-count to style if it exists
if div.attributes.style then
div.attributes.style = div.attributes.style ..
'; column-count: ' .. opt
else
div.attributes.style = 'column-count:' .. opt
end
div.attributes['column-count'] = nil
returned_list = List:new{pandoc.Div(div.content, div.attr)}
end
end
return returned_list
end
63 changes: 63 additions & 0 deletions column-div/expected.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Test</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
</style>

</head>
<body>
<header id="title-block-header">
<h1 class="title">Test</h1>
</header>
<h1 id="column-div-test">column-div test</h1>
<p>content…</p>
<h2 id="three-columns">Three columns</h2>
<div class="multicols" style="column-count:3">
<p>content…</p>
<p>content…</p>
<p>content…</p>
</div>
<h2 id="two-unbalanced-columns">Two unbalanced columns</h2>
<div class="columns">
<div class="column" data-valign="b" style="width:40%;">
<p>contents…</p>
</div><div class="column" data-valign="b" style="width:60%;">
<p>contents…</p>
</div>
</div>
<h2 id="columns-in-columns">Columns in columns</h2>
<div class="multicols" style="column-count:3">
<div class="columns">
<div class="column" data-valign="b" style="width:20%;">
<p>contents…</p>
</div><div class="column" data-valign="b" style="width:80%;">
<p>contents…</p>
</div>
</div>
<div class="columns">
<div class="column" data-valign="b" style="width:20%;">
<p>contents…</p>
</div><div class="column" data-valign="b" style="width:80%;">
<p>contents…</p>
</div>
</div>
<div class="columns">
<div class="column" data-valign="b" style="width:20%;">
<p>contents…</p>
</div><div class="column" data-valign="b" style="width:80%;">
<p>contents…</p>
</div>
</div>
</div>
</body>
</html>
Loading