Pandoc/Quarto filter for smart first-line indents in HTML and LaTeX/PDF.
Quarto/Pandoc’s support of first-line indents is limited: it’s not available in HTML output and delegated to LaTeX PDF output. This filter provides a first-line indentation style with smart defaults, full customization, and manual control for fine-grain adjustments.
Paragraphs are typically separated in either of two ways: by vertical whitespace (common on the web) or by indenting their first line (common in books). There is some variation in the first-line indent style itself: some apply it to every paragraph, others don’t apply it to paragraphs below a section heading, blockquote or the like. They also vary in size, the most common being between half an em (the width of the letter ‘m’) for narrow text to 3 ems for wide text. 1 to 1.5em are probably the most standard values.
Quarto and Pandoc use vertical whitespace by default. In HTML outputs
that cannot be changed. In LaTeX/PDF output one can switch to first-line
indent by setting the metadata variable indent
to
true
. There are some limitations, however:
article
,
book
) and Memoir (memoir
) use 1.5 em, the KOMA
classes (scrartcl
, scrbook
) 1 em. Pandoc uses
the standard article class by default, Quarto its KOMA equivalent, so
you get 1.5 em with the first and 1 em with the second. You need to
insert LaTeX code in your document to change this.This filter provides first-line indentation in HTML output and improves its handling in both PDF and HTML outputs.
indent
is set to false
.header-includes
field. This can be disabled if you want to
provide your own CSS.\indent
and \noindent
at the
beginning of the paragraph in the markdown source. These are LaTeX
commands but will work with HTML output too.\indent
at
the beginning of the paragraph.Install this filter in a document’s folder by running:
quarto install extension dialoa/first-line-indent
on the command line (terminal in RStudio).
Use it by adding first-line-indent
to the
filters
entry of your YAML header.
---
filters:
- first-line-indent
---
Copy the file first-line-indent.lua
in your document
folder. Pass the filter to Pandoc via the --lua-filter
(or
-L
) command line option.
pandoc --lua-filter first-line-indent.lua ...
Or specify it in a defaults file (see Pandoc’s manual: defaults).
You can place the filter file Pandoc’s user data dir, or in an
arbitrary folder (-L path/to/first-line-indent.lua
). See Pandoc’s
manual:Lua filters.
Copy the file first-line-indent.lua
in your document
folder. Use pandoc_args
to invoke the filter. See the R
Markdown Cookbook for details.
---
output:
word_document:
pandoc_args: ['--lua-filter=first-line-indent.lua']
---
You can place the filter in another folder, provided you specify its path:
---
output:
word_document:
pandoc_args: ['--lua-filter=../path/to/first-line-indent.lua']
---
See also the sample input file and the resulting HTML output.
To apply first-line indentation to your entire document, set
indent
to true
in the YAML header:
---
indent: true
---
In Quarto, indent
may also be set per format:
---
format:
html:
indent: false
pdf:
indent: true
---
The filter applies some typesetting adjustments, e.g. no first-line indentation after lists. See [typesetting-background] below for details. If you’re not happy with the adjustments, you can control them via options and manually apply or remove indents from some paragraphs.
Whether or not first-line indentation is activated for the whole
document, you can manually add or remove it from a particular paragraph
by inserting \indent
or \noindent
at the
beginning of the paragraph:
> This is a blockquote
\indent This paragraph will have an indent even though it follows a blockquote.
Even though \indent
and \noindent
are LaTeX
commands, the filter handles them in HTML output too.
Warning: citations after \indent
. If
the paragraph starts with a square-bracketed citation,
\indent
or \noindent
must be marked as a “Raw
Inline”, as follows:
`\indent`{=tex} [@Smith2008] says....
That is because Pandoc/Quarto interprets \indent [@cite]
as a LaTeX command with a bracketed option rather than a LaTeX command
followed by a citation.
Filter options are specified in the document’s YAML header:
indent: true
first-line-indent:
size: 2em
auto-remove: true
set-metadata-variable: true
set-header-includes: true
remove-after:
- BlockQuote
- BulletList
- CodeBlock
- DefinitionList
- HorizontalRule
- OrderedList
dont-remove-after: Table
remove-after-class:
- statement
dont-remove-after-class:
Different options can be provided for different output formats. This is standard with Quarto, but the filter also reads these with Pandoc:
format:
html:
indent: true
first-line-indent:
size: 2em
pdf:
indent: true
first-line-indent:
size: 1.5em
Format-specific options override global ones. For instance, to disable first line indentation in HTML output only:
# Format-specific options
format:
html:
indent: false
first-line-indent:
set-header-includes: false
# Global options
indent: true
first-line-indent:
size: 2em
Options can be passed in a separate metadata file (Quarto, Pandoc or defaults (Pandoc only).
indent
(default true
)If set to false
, paragraphs are separated with vertical
whitespace rather than first line indentation. This essentially
deactivates the filter, though \indent
can still be used to
add indent to individual paragraphs for HTML output as well as PDF.
size
(default nil
)String specificing size of the first-line indent. Must be in a format
suitable for all desired outputs. 1.5em
, 2ex
,
.5pc
, 10pt
, 25mm
,
2.5cm
, 0.3in
, all work in LaTeX and HTML.
25px
only works in HTML. LaTeX commands
(\textheight
) are not supported.
auto-remove
(default true
)Whether the filter automatically removes first line indentation from
paragraphs that follow blocks of given types, unless they start with
\indent
. Set to false
to disable. Use the
remove-after...
and dont-remove-after...
options below to control which block types and Div classes are handled
that way. By default first-line indentation is removed after Blockquote,
lists (DefinitionList, BulletList, OrderedList, which include numbered
example lists) and HorizontalRule blocks.
set-metadata-variable
(default:
true
):Whether the filter adds the metavariable indent
with the
value true
when it is missing. Without this Pandoc’s LaTeX
template does not use first-line indentation in PDF output.
set-header-includes
(default true
)Whether the filter should add formatting code to the document’s
header-includes
metadata field. Set it to
false
if you use a custom template instead.
remove-after
, dont-remove-after
Whether to remove first-line indentations automatically after blocks
of a certain type. These options can be a single string or a list of
strings. The strings are case-sensitive and should correspond to block types in Lua
filters: BlockQuote, BulletList, CodeBlock, DefinitionList, Div,
Header, HorizontalRule, LineBlock, Null, OrderedList, Para, Plain,
RawBlock, Table. Inactive if auto-remove
is false.
remove-after-class
,
dont-remove-after-class
Decide whether to remove first-line indentation automatically after
elements of certain classes. For instance, you may use decide that when
a block with class “continuing” is followed by a paragraph, the latter
should not be first-line indented. Useful for Div elements, if you use
Divs of certain classes to wrap and typeset material that doesn’t end a
paragraph. Inactive if auto-remove
is false.
To illustrate, suppose you don’t want to filter to remove first-line indent after definition lists. You can add the following lines in the document’s metadata block (if the source is markdown):
first-line-indent:
dont-remove-after: DefinitionList
In LaTeX output the filters adds \noindent
commands at
beginning of paragraphs that shouldn’t be indented. These can be
controlled in LaTeX as usual.
In HTML output paragraphs that are explicitly marked to have no
first-line indent are preceded by an empty div
with class
no-first-line-indent-after
and those that are explictly
marked (with \indent
in the markdown source) to have a
first-line indent are preceded by an empty div
with class
first-line-indent-after
, as follows:
<ul>
<li>A bullet</li>
<li>list</li>
</ul>
<div class="no-first-line-indent-after"></div>
<p>This paragraph should not have first-line indent.</p>
...<div class="first-line-indent-after"></div>
<p>This paragraph should have first-line indent.</p>
These can be styled in CSS as follows:
p {text-indent: 1.5em;
margin: 0;
}
header p {text-indent: 0;
margin: 1em 0;
}:is(h1, h2, h3, h4, h5, h6) + p {
text-indent: 0;
}> p, li > div > p, li > div > div > p {
li text-indent: 0;
margin-bottom: 1rem;
}.no-first-line-indent-after + p {
divtext-indent: 0;
}.first-line-indent-after + p {
divtext-indent: SIZE;
}
The first four rules provide global first line indentation.
p
rule adds first-line indentation to every
paragraph and removes the default vertical space between
paragraphs.header p
rule restores the default whitespace
separation setting for paragraphs in the <header>
element.is(h1, h2, h3, h4, h5, h6) + p
rule removes
first-line indentation from every paragraph that follows a heading.li > p
rule restores the vertical whitespace
separation style within lists. It only targets paragraphs that are
direct child of a list (li > p
) rather that all
paragraphs within a list (li p
) in case a list item
contains e.g. a block quote that requires first line indentation.
However in case a list item’s paragraphs are contained within some Div,
we also target paragraphs that are child of a Div, or sub-Div, of a list
item (li > div > p
and
li > div > div >p
).The last two rules provide explicit local indentation. The
div.no-first-line-indent-after) + p
rule removes indent
from paragraphs placed just after a Div with the
no-first-line-indent-after
class, and the second rule keeps
them in paragraphs that follow a first-line-indent-after
Div.
The indentation filter adds the following rule:
.labelled-lists-list > p {
divtext-indent: 0;
}
To avoid interference with Dialoa’s labelled-lists filter.
quote
environmentThe filter applies first line indent style within block quotes, with no indent on the first line.
To achieve this in PDF output, the LaTeX quote
environment (used by Quarto/Pandoc for block quotes) is redefined as
follows in header-includes
:
\renewenvironment{quote}
\list{}{\listparindent 1.5em%
{\itemindent \listparindent
\rightmargin \leftmargin
\parsep \z@ \@plus \p@}%
\item\relax}
\endlist} {
Which is the definition of LaTeX’s quotation
environment—see the standard
classes source.
If you redefine the quote
environment, you should use
this code as basis.
header-includes
The filter adds its commands at the beginning of the
header-includes
field. You can thus use
header-includes
to override the filter’s commands.
Issues and PRs welcome.
Copyright 2021-2023 Julien Dutant. License MIT - see license file for details.
---
title: "First-line Indent"
author: Julien Dutant
date: 15 Mar 2023
## for Quarto only (Pandoc ignores this)
filters:
- first-line-indent
# Filter options. You do not need to specify any,
# remove all the lines below the filter still
# separates paragraphs with indentation rather
# than vertical whitespace.
first-line-indent:
set-metadata-variable: true
set-header-includes: true
auto-remove: true
remove-after: Table
dont-remove-after:
- DefinitionList
- OrderedList
size: "2em"
remove-after-class: chuckit
dont-remove-after-class: keepit
---
This document illustrates first-line indent typesetting. In English
typography, paragraphs just below a section heading aren't indented,
because a heading is enough to separate them from what is before. The
same should apply to the first paragraph of a document with a
title---so this paragraph is not indented.
This paragraph is indented. But after this quote:
> Lorem ipsum dolor sit amet, consectetur adipiscing elit.
the paragraph continues, so there should not be a first-line indent.
We want this quote to end a paragraph:
> Lorem ipsum dolor sit amet, consectetur adipiscing elit.
\indent The text below therefore begins a new paragraph and should`\indent`.
have a first-line indent. We have to manually specify using
# Basic tests
After a heading (in English typographic style) the paragraph does not
have a first-line indent.
## Manually specifying indentation on certain paragraphs
In the couple of paragraphs that follow the quotes below, we`\noindent` and `\indent` respectively. This
have manually specified
is to check that the filter doesn't add its own commands to those.
> Lorem ipsum dolor sit amet, consectetur adipiscing elit.
\indent Here we've explicitly required a first line indent.
\noindent Here we've explicitly required *not* to have one.
## Automatic removal of first line indentation
We can also check that indent is removed after lists:
* A bullet
* list
And after code blocks:
```lua
local variable = "value"
```
Or horizontal rules.
---
We check that this behavour is overriden for specified classes. We
created a custom class to preserve indentation after certain elements:
``` {.markdown .keepit}
This code block
should be followed
by an indented
paragraph
```
And another one to remove indents after others:
::: chuckit
This paragraph's Div container should not
be followed by indentation,
:::
as specified in this document's options.
# Further tests
In this document we added a few custom filter options.
## Size
The size of first-line indents is 2em instead of the default 1.5em
(Pandoc) or 1em (Quarto).
## Indent within quotes
Blockquotes first-line indentation:
> Blockquotes should not be indented on their first paragraph but
> otherwise have the same size of ident as the main text.
>
> Hence this second paragraph has a first-line indentation of
> 2em.
## Keep or remove indentation after certain types of elements
We also added an option to
automatically remove indent after tables:
Right Left Center Default
------- ------ ---------- ------- 12 12 12 12
123 123 123 123
1 1 1 1
Table: Demonstration of simple table syntax.
So this paragraph's first line is not indented. We added the option
*not* to remove ident after ordered lists and definition lists:
Definition
: This is a definition block.
So this paragraph is indented.
1. An ordered
2. list
And this one is too.
## Recursion and nesting
The paragraphs below are nested within a Div element---actually, two
nested Divs, in order to check that the filter is applied recursively
within Divs.
::: {#div}
::::: {#subdiv}
The first paragraph within a Div is indented normally, but the
list below
* list item
* list item
should not be followed by a indented paragraph.
The last paragraph within Divs should be indented normally.
:::::
:::
The filter is also applied recursively within blockquotes. A
blockquote's first paragraph shouldn't be indented, but any subsequent
ones should. Within the block quotes, indents should be removed after
special blocks, as in the main text.
> The first paragraph of this blockquote does not
> have a first line indent.
>
> The subsequent paragraph has one. It's followed:
>
> * by a
> * list
>
> after which there is no first line indentation.
>
> This next paragraph is first line indented again..
## List content
Within lists, paragraphs should be separated by vertical whitespace.
* This list item contains multiple paragraphs.
The second one should not be indented, but separated by vertical
whitespace.
--[[# first-line-indent.lua – First line indentation filter
Copyright: © 2021–2023 Contributors
License: MIT – see LICENSE for details
@TODO latex_quote should use options.size (or better, a specific option)
@TODO option for leaving indents after headings (French style)
@TODO smart setting of the post-heading style based on `lang`
@TODO option to leave indent at the beginning of the document
]]
PANDOC_VERSION:must_be_at_least '2.17'
stringify = pandoc.utils.stringify
equals = pandoc.utils.equals
pandoctype = pandoc.utils.type
-- # Options
---@class Options Options map with default values.
---@field format string|nil output format (currently: 'html' or 'latex')
---@field indent boolean whether to use first line indentation globally
---@field set_metadata_variable boolean whether to set the `indent`
-- metadata variable.
---@field set_header_includes boolean whether to provide formatting code in
-- header-includes.
---@field auto_remove_indents boolean whether to automatically remove
-- indents after specific block types.
---@field remove_after table list of strings, Pandoc AST block types
-- after which first-line indents should be automatically removed.
---@field remove_after_class table list of strings, classes of elements
-- after which first-line indents should be automatically removed.
---@field dont_remove_after_class table list of strings, classes of elements
-- after which first-line indents should not be removed. Prevails
-- over remove_after.
---@field size string|nil a CSS / LaTeX specification of the first line
-- indent length
---@field recursive table<string, options> Pandoc Block types to
--- which the filter is recursively applied, with options map.
--- The option `dont_indent_first` controls whether indentation
--- is removed on the first paragraph.
local Options = {
format = nil,
indent = true,
set_metadata_variable = true,
set_header_includes = true,
auto_remove = true,
remove_after = pandoc.List({
'BlockQuote',
'BulletList',
'CodeBlock',
'DefinitionList',
'HorizontalRule',
'OrderedList',
}),
remove_after_class = pandoc.List({
'statement',
}),
dont_remove_after_class = pandoc.List:new(),
size = nil, -- default let LaTeX decide
size_default = '1.5em', -- default value for HTML
recursive = {
Div = {dont_indent_first = false},
BlockQuote = {dont_indent_first = true},
}
}
-- # Filter global variables
---@class code map pandoc objects for indent/noindent Raw code.
local code = {
tex = {
indent = pandoc.RawInline('tex', '\\indent '),
noindent = pandoc.RawInline('tex', '\\noindent '),
},
latex = {
indent = pandoc.RawInline('latex', '\\indent '),
noindent = pandoc.RawInline('latex', '\\noindent '),
},
html = {
indent = pandoc.RawBlock('html',
'<div class="first-line-indent-after"></div>'),
noindent = pandoc.RawBlock('html',
'<div class="no-first-line-indent-after"></div>'),
}
}
---LATEX_QUOTE_ENV: LaTeX's definition of the quote environement
---used to define HeaderIncludes.
---a \setlength{\parindent}{<size>} will be appended
---@type string
local LATEX_QUOTE_ENV = [[\makeatletter
\renewenvironment{quote}
{\list{}{\listparindent 1.5em%
\itemindent \listparindent
\rightmargin \leftmargin
\parsep \z@ \@plus \p@}%
\item\noindent\relax}
{\endlist}
\makeatother
]]
---@class HeaderIncludes map of functions to produce
---header-includes code given a size parameter (string|nil),
--- either for global or for local indentation markup.
--- optionally wrap the constructed global header markup (e.g. <style> tags).
--- glob = {html : function, latex: function}
--- wrap = {html : function, latex: function}
--- loc = {html : function, latex: function}
HeaderIncludes = {
glob = {
html = function(size)
size = size or Options.size_default
local code = [[ p {
text-indent: SIZE;
margin: 0;
}
header p {
text-indent: 0;
margin: 1em 0;
}
:is(h1, h2, h3, h4, h5, h6) + p {
text-indent: 0;
}
li > p, li > div p {
text-indent: 0;
margin-bottom: 1rem;
}
]]
return code:gsub("SIZE", size)
end,
latex = function(size)
local size_code = size and '\\setlength{\\parindent}{'..size..'}\n'
or ''
return LATEX_QUOTE_ENV .. size_code
end,
},
wrap = {
html = function(header_str)
return "<style>\n/* first-line indent styles */\n" .. header_str
.. "/* end of first-line indent styles */\n</style>"
end,
latex = function(str) return str end,
},
loc = {
html = function(size)
size = size or Options.size_default
local code = [[ div.no-first-line-indent-after + p {
text-indent: 0;
}
div.first-line-indent-after + p {
text-indent: SIZE;
}
]]
return code:gsub("SIZE", size)
end,
latex = function(_) return '' end,
}
}
-- # encapsulate Quarto/Pandoc variants
---format_match: whether format matches a string pattern
---ex: format_match('html5'), format_match('html*')
---in Quarto we try removing non-alphabetical chars
---@param pattern string
---@return boolean
local function format_match(pattern)
return quarto and (quarto.doc.is_format(pattern)
or quarto.doc.is_format(pattern:gsub('%A',''))
)
or FORMAT:match(pattern)
end
---add_header_includes: add a block to the document's header-includes
---meta-data field.
---@param meta pandoc.Meta the document's metadata block
---@param blocks pandoc.Blocks list of Pandoc block elements (e.g. RawBlock or Para)
--- to be added to the header-includes of meta
---@return pandoc.Meta meta the modified metadata block
local function add_header_includes(meta, blocks)
-- Pandoc
local function pandoc_add_headinc(meta,blocks)
local header_includes = pandoc.MetaList( { pandoc.MetaBlocks(blocks) })
-- add any exisiting meta['header-includes']
-- it can be MetaInlines, MetaBlocks or MetaList
if meta['header-includes'] then
if pandoctype(meta['header-includes']) == 'List' then
header_includes:extend(meta['header-includes'])
else
header_includes:insert(meta['header-includes'])
end
end
meta['header-includes'] = header_includes
return meta
end
-- Quarto
local function quarto_add_headinc(blocks)
quarto.doc.include_text('in-header', stringify(blocks))
end
return quarto and quarto_add_headinc(blocks)
or pandoc_add_headinc(meta,blocks)
end
-- # Helper functions
-- ensure_list: turns Inlines and Blocks meta values into list
local function ensure_list(elem)
if elem and (pandoctype(elem) == 'Inlines'
or pandoctype(elem) == 'Blocks') then
elem = pandoc.List:new(elem)
end
return elem
end
--- classes_include: check if one of an element's class is in a given
-- list. Returns true if match, nil if no match or the element doesn't
-- have classes.
---@param elem table pandoc AST element
---@param classes table pandoc List of strings
local function classes_include(elem,classes)
if elem.classes then
for _,class in ipairs(classes) do
if elem.classes:includes(class) then return true end
end
end
end
--- is_indent_cmd: check if an element is a LaTeX indent command
---@param elem pandoc.Inline
---@return string|nil 'indent', 'noindent' or nil
-- local function is_indent_cmd(elem)
-- return (equals(elem, code.latex.indent)
-- or equals(elem, code.tex.indent)) and 'indent'
-- or (equals(elem, code.latex.noindent)
-- or equals(elem, code.tex.noindent)) and 'noindent'
-- or nil
-- end
local function is_indent_cmd(elem)
return elem.text and (
elem.text:match('^%s*\\indent%s*$') and 'indent'
or elem.text:match('^%s*\\noindent%s*$') and 'noindent'
)
or nil
end
-- # Filter functions
--- Add format-specific explicit indent markup to a paragraph.
--- Returns a list of blocks containing a single paragraph
--- or a rawblock followed by a paragraph, depending on format.
---@param type string 'indent' or 'noindent', type of markup to add
---@param elem pandoc.Para
---@return pandoc.Blocks
local function indent_markup(type, elem)
local result = pandoc.List:new()
if not (type == 'indent' or type == 'noindent') then
result:insert(elem)
elseif format_match('latex') then
-- in LaTeX, replace any `\indent` or `\noindent`
-- at the beginning of the paragraph with
-- with the one corresponding to `type`
if elem.content[1] and is_indent_cmd(elem.content[1]) then
elem.content[1] = code.tex[type]
else
elem.content:insert(1, code.tex[type])
end
result:insert(elem)
elseif format_match('html') then
result:extend({ code.html[type], elem })
end
return result
end
--- process_blocks: process indentations in a list of blocks.
-- Adds output code for explicitly specified first-line indents,
-- automatically removes first-line indents after blocks of the
-- designed types unless otherwise specified.
---@param blocks pandoc.Blocks element (list of blocks)
---@param dont_indent_first boolean whether to indent the first paragraph
local function process_blocks(blocks, dont_indent_first)
dont_indent_first = dont_indent_first or false
-- tag for the first element
local is_first_block = true -- tags the doc's first element
-- tag to trigger indentation auto-removal on the next element
local dont_indent_next_block = false
local result = pandoc.List:new()
for _,elem in pairs(blocks) do
-- Paragraphs: if they have explicit LaTeX indent markup
-- reproduce it in the output format, otherwise
-- remove indentation if needed, provided `auto_remove` is on.
if elem.t == "Para" then
if elem.content[1] and is_indent_cmd(elem.content[1]) then
-- 'indent' or 'noindent' ?
local type = is_indent_cmd(elem.content[1])
result:extend(indent_markup(type, elem))
elseif is_first_block and dont_indent_first then
result:extend(indent_markup('noindent', elem))
elseif dont_indent_next_block and Options.auto_remove then
result:extend(indent_markup('noindent', elem))
else
result:insert(elem)
end
dont_indent_next_block = false
-- Non-Paragraphs: check first whether it's an element after
-- which indentation must be removed. Next insert it, applying
-- this function recursively within the element if needed.
else
if Options.auto_remove then
if Options.remove_after:includes(elem.t) and
not classes_include(elem, Options.dont_remove_after_class) then
dont_indent_next_block = true
elseif elem.classes and
(elem, Options.remove_after_class) then
classes_include
dont_indent_next_block = true
else
dont_indent_next_block = false
end
end
-- recursively process the element if needed
if Options.recursive[elem.t] then
local dif = Options.recursive[elem.t].dont_indent_first
elem.content = process_blocks(elem.content, dif)
end
-- insert
result:insert(elem)
end
-- ensure `is_first_block` turns to false
-- even if the first block wasn't a paragraph
-- or if it had explicit indent marking
is_first_block = false
end
return result
end
--- process_doc: Process indents in the document's body text.
-- Adds output code for explicitly specified first-line indents,
-- automatically removes first-line indents after blocks of the
-- designed types unless otherwise specified.
local function process_doc(doc)
local dont_indent_first = false
-- if no output format, do nothing
if not Options.format then return end
-- if the doc has a title, do not indent first paragraph
if doc.meta.title then
dont_indent_first = true
end
doc.blocks = process_blocks(doc.blocks, dont_indent_first)
return doc
end
--- read_user_options: read user options from meta element.
-- in Quarto options may be under format/pdf or format/html
-- the latter override root ones.
local function read_user_options(meta)
local user_options = {}
if meta.indent == false then
Options.indent = false
end
if meta['first-line-indent'] then
user_options = meta['first-line-indent']
end
local formats = {'pdf', 'html', 'latex'}
if meta.format then
for format in ipairs(formats) do
if format_match(format) and meta.format[format] then
for k,v in meta.format[format] do
user_options[k] = v
end
end
end
end
if user_options['set-metadata-variable'] == false then
Options.set_metadata_variable = false
end
if user_options['set-header-includes'] == false then
Options.set_header_includes = false
end
-- size
-- @todo using stringify means that LaTeX commands in
-- size are erased. But it ensures that the filter gets
-- a string. Improvement: check that we have a string
-- and throw a warning otherwise
if user_options.size and pandoctype(user_options.size == 'Inlines') then
Options.size = stringify(user_options.size)
end
if user_options['auto-remove'] == false then
Options.auto_remove = false
end
-- autoremove elements and classes
-- for elements we only need a whitelist, remove_after
-- for classes we need both a whitelist (remove_after_class)
-- and a blacklist (dont_remove_after_class).
-- first insert user values in `remove_after`, `remove_after_class`
-- and `dont_remove_after_class`.
for optname, metakey in pairs({
remove_after = 'remove-after',
remove_after_class = 'remove-after-class',
dont_remove_after_class = 'dont-remove-after-class',
}) do
local user_value = ensure_list(user_options[metakey])
if user_value and pandoctype(user_value) == 'List' then
for _,item in ipairs(user_value) do
Options[optname]:insert(stringify(item))
end
end
end
-- then remove blacklisted entries from `remove_after`
-- and `remove_after_class`.
for optname, metakey in pairs({
remove_after = 'dont-remove-after',
remove_after_class = 'dont-remove-after-class'
}) do
local user_value = ensure_list(user_options[metakey])
if user_value and pandoctype(user_value) == 'List' then
-- stringify the list
for i,v in ipairs(user_value) do
user_value[i] = stringify(v)
end
-- filter to that returns true iff an item isn't blacklisted
local predicate = function (str)
return not(user_value:includes(str))
end
-- apply the filter to the whitelist
Options[optname] = Options[optname]:filter(predicate)
end
end
end
--- set_meta: insert options in doc's meta
--- Sets `indent` and extends `header-includes` if needed.
---@param meta pandoc.Meta
---@return pandoc.Meta|nil meta nil if no changes
local function set_meta(meta)
local changes = false -- only return if changes are made
local header_code = nil
local format = Options.format
-- set the `indent` metadata variable unless otherwise specified or
-- already set to false
if Options.set_metadata_variable and not(meta.indent == false) then
meta.indent = true
changes = true
end
-- set the `header-includes` metadata variable
if Options.set_header_includes and Options.indent then
-- do we apply first line indentation globally?
if Options.indent then
header_code = HeaderIncludes.glob[format](Options.size)
end
-- provide local explicit indentation styles
header_code = header_code .. HeaderIncludes.loc[format](Options.size)
-- wrap the header if needed
header_code = HeaderIncludes.wrap[format](header_code)
-- insert if not empty
if header_code ~= '' then
(meta, { pandoc.RawBlock(format, header_code)})
add_header_includeschanges = true
end
end
return changes and meta or nil
end
--- process_metadata: process user options.
-- read user options, set the `indent` metadata variable,
-- add formatting code to `header-includes`.
local function process_metadata(meta)
local changes = false -- only return if changes are made
local header_code = nil
local format = format_match('html') and 'html'
or (format_match('latex') and 'latex')
if not format then
return nil
else
Options.format = format
end
(meta) -- places values in global `options`
read_user_options
return set_meta(meta)
end
--- Main code
-- Returns the filters in the desired order of execution
return {
{
Meta = process_metadata
},
{
Pandoc = process_doc
}
}