From: Matthew Owens Date: Sun, 31 Mar 2024 14:51:13 +0000 (+0100) Subject: initial commit X-Git-Url: https://git.owens.tech///git?a=commitdiff_plain;h=fee1cb78a0cd48d951444d0a408ad1390d2e211f;p=untitled-ttrpg.git initial commit --- fee1cb78a0cd48d951444d0a408ad1390d2e211f diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d358731 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +out.tex +venv diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7d5181 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Fief + +TTRPG Source + +## Environment +``` +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +deactivate +``` + +## Building +run `build` script + diff --git a/build b/build new file mode 100755 index 0000000..f5369cb --- /dev/null +++ b/build @@ -0,0 +1,25 @@ +#!/bin/bash + +#pandoc -B header.md --toc -s main.md -o out.pdf +#pandoc -B header.md -f gfm --toc -s main.md -o out.pdf +#pandoc -s --toc -B header.md main.md -o out.pdf +work=~/documents/untitled-ttrpg +src=$work/src +tmp=/tmp +#template=eisvogel +template=template.tex + +cd $work + +source ./venv/bin/activate +#pandoc -s --toc body.md -o out.pdf +#pandoc -s --toc body.md -o out.pdf --from markdown --template eisvogel +#pandoc $src_dir/preamble.md -o $tmp_dir/ +#pandoc -s --toc -B pre-body.md body.md -o out.pdf +#pandoc -s --toc $src/preamble.md $src/body.md -o out.pdf +#pandoc --filter pandoc-mustache -s $src/body.md -o $work/out.pdf +pandoc --lua-filter filters/columns.lua --filter pandoc-mustache -s $src/body.md -o $work/out.pdf +#pandoc --filter pandoc-mustache -s $src/body.md -o $work/out.pdf --template $template +#pandoc --filter pandoc-mustache -t latex -s $src/body.md -o $work/out.tex --template $template +deactivate + diff --git a/filters/columns.lua b/filters/columns.lua new file mode 100644 index 0000000..7300e96 --- /dev/null +++ b/filters/columns.lua @@ -0,0 +1,1087 @@ +--[[-- # Columns - multiple column support in Pandoc's markdown. + +This Lua filter provides support for multiple columns in +latex and html outputs. For details, see README.md. + +@author Julien Dutant +@copyright 2021 Julien Dutant +@license MIT - see LICENSE file for details. +@release 1.1.3 +]] + +-- # Version control +local required_version = '2.9.0' +local version_err_msg = "ERROR: pandoc >= "..required_version + .." required for columns filter" +-- pandoc 2.9 required for pandoc.List insert method +if PANDOC_VERSION == nil then -- if pandoc_version < 2.1 + error(version_err_msg) +elseif PANDOC_VERSION[1] < 3 and PANDOC_VERSION[2] < 9 then + error(version_err_msg) +else + PANDOC_VERSION:must_be_at_least(required_version, version_err_msg) +end +local utils = require('pandoc.utils') -- this is superfluous in Pandoc >= 2.7 I think + +-- # Internal settings + +-- target_formats filter is triggered when those formats are targeted +local target_formats = { + "html.*", + "latex", +} +local options = { + raggedcolumns = false; -- global ragged columns option +} + +-- # Helper functions + +--- type: pandoc-friendly type function +-- panbdoc.utils.type is only defined in Pandoc >= 2.17 +-- if it isn't, we extend Lua's type function to give the same values +-- as pandoc.utils.type on Meta objects: Inlines, Inline, Blocks, Block, +-- string and booleans +-- Caution: not to be used on non-Meta Pandoc elements, the +-- results will differ (only 'Block', 'Blocks', 'Inline', 'Inlines' in +-- >=2.17, the .t string in <2.17). +local type = utils.type or function (obj) + local tag = type(obj) == 'table' and obj.t and obj.t:gsub('^Meta', '') + return tag and tag ~= 'Map' and tag or type(obj) + end + +--- Test whether the target format is in a given list. +-- @param formats list of formats to be matched +-- @return true if match, false otherwise +local function format_matches(formats) + for _,format in pairs(formats) do + if FORMAT:match(format) then + return true + end + end + return false +end + + +--- Add a block to the document's header-includes meta-data field. +-- @param meta the document's metadata block +-- @param block Pandoc block element (e.g. RawBlock or Para) to be added to header-includes +-- @return meta the modified metadata block +local function add_header_includes(meta, block) + + local header_includes = pandoc.List:new() + + -- use meta['header-includes'] + + if meta['header-includes'] then + if type(meta['header-includes']) == 'List' then + header_includes:extend(meta['header-includes']) + else + header_includes:insert(meta['header-includes']) + end + end + + -- insert `block` in header-includes + + header_includes:insert(pandoc.MetaBlocks({block})) + + -- save header-includes in the document's meta + + meta['header-includes'] = header_includes + + return meta +end + +--- Add a class to an element. +-- @param element Pandoc AST element +-- @param class name of the class to be added (string) +-- @return the modified element, or the unmodified element if the element has no classes +local function add_class(element, class) + + -- act only if the element has classes + if element.attr and element.attr.classes then + + -- if the class is absent, add it + if not element.attr.classes:includes(class) then + element.attr.classes:insert(class) + end + + end + + return element +end + +--- Removes a class from an element. +-- @param element Pandoc AST element +-- @param class name of the class to be removed (string) +-- @return the modified element, or the unmodified element if the element has no classes +local function remove_class(element, class) + + -- act only if the element has classes + if element.attr and element.attr.classes then + + -- if the class is present, remove it + if element.attr.classes:includes(class) then + element.attr.classes = element.attr.classes:filter( + function(x) + return not (x == class) + end + ) + end + + end + + return element +end + +--- Set the value of an element's attribute. +-- @param element Pandoc AST element to be modified +-- @param key name of the attribute to be set (string) +-- @param value value to be set. If nil, the attribute is removed. +-- @return the modified element, or the element if it's not an element with attributes. +local function set_attribute(element,key,value) + + -- act only if the element has attributes + if element.attr and element.attr.attributes then + + -- if `value` is `nil`, remove the attribute + if value == nil then + if element.attr.attributes[key] then + element.attr.attributes[key] = nil + end + + -- otherwise set its value + else + element.attr.attributes[key] = value + end + + end + + return element +end + +--- Add html style markup to an element's attributes. +-- @param element the Pandoc AST element to be modified +-- @param style the style markup to add (string in CSS) +-- @return the modified element, or the unmodified element if it's an element without attributes +local function add_to_html_style(element, style) + + -- act only if the element has attributes + if element.attr and element.attr.attributes then + + -- if the element has style markup, append + if element.attr.attributes['style'] then + + element.attr.attributes['style'] = + element.attr.attributes['style'] .. '; ' .. style .. ' ;' + + -- otherwise create + else + + element.attr.attributes['style'] = style .. ' ;' + + end + + end + + return element + +end + +--- Translate an English number name into a number. +-- Converts cardinals ("one") and numerals ("first"). +-- Returns nil if the name isn't understood. +-- @param name an English number name (string) +-- @return number or nil +local function number_by_name(name) + + local names = { + one = 1, + two = 2, + three = 3, + four = 4, + five = 5, + six = 6, + seven = 7, + eight = 8, + nine = 9, + ten = 10, + first = 1, + second = 2, + third = 3, + fourth = 4, + fifth = 5, + sixth = 6, + seventh = 7, + eighth = 8, + ninth = 9, + tenth = 10, + } + + result = nil + + if name and names[name] then + return names[name] + end + +end + +--- Convert some CSS values (lengths, colous) to LaTeX equivalents. +-- Example usage: `css_values_to_latex("1px solid black")` returns +-- `{ length = "1pt", color = "black", colour = "black"}`. +-- @param css_str a CSS string specifying a value +-- @return table with keys `length`, `color` (alias `colour`) if found +local function css_values_to_latex(css_str) + + -- color conversion table + -- keys are CSS values, values are LaTeX equivalents + + latex_colors = { + -- xcolor always available + black = 'black', + blue = 'blue', + brown = 'brown', + cyan = 'cyan', + darkgray = 'darkgray', + gray = 'gray', + green = 'green', + lightgray = 'lightgray', + lime = 'lime', + magenta = 'magenta', + olive = 'olive', + orange = 'orange', + pink = 'pink', + purple = 'purple', + red = 'red', + teal = 'teal', + violet = 'violet', + white = 'white', + yellow = 'yellow', + -- css1 colors + silver = 'lightgray', + fuschia = 'magenta', + aqua = 'cyan', + } + + local result = {} + + -- look for color values + -- by color name + -- rgb, etc.: to be added + + local color = '' + + -- space in front simplifies pattern matching + css_str = ' ' .. css_str + + -- look for colour names + for text in string.gmatch(css_str, '[%s](%a+)') do + + -- if we have LaTeX equivalent of `text`, store it + if latex_colors[text] then + result['color'] = latex_colors[text] + end + + end + + -- provide British spelling + + if result['color'] then + result['colour'] = result['color'] + end + + -- look for lengths + + -- 0 : converted to 0em + if string.find(css_str, '%s0%s') then + result['length'] = '0em' + end + + -- px : converted to pt + for text in string.gmatch(css_str, '(%s%d+)px') do + result['length'] = text .. 'pt' + end + + -- lengths units to be kept as is + -- nb, % must be escaped + -- nb, if several found, the latest type is preserved + keep_units = { '%%', 'pt', 'mm', 'cm', 'in', 'ex', 'em' } + + for _,unit in pairs(keep_units) do + + -- .11em format + for text in string.gmatch(css_str, '%s%.%d+'.. unit) do + result['length'] = text + end + + -- 2em and 1.2em format + for text in string.gmatch(css_str, '%s%d+%.?%d*'.. unit) do + result['length'] = text + end + + end + + return result + +end + +--- Ensures that a string specifies a LaTeX length +-- @param text text to be checked +-- @return text if it is a LaTeX length, `nil` otherwise +local function ensures_latex_length(text) + + -- LaTeX lengths units + -- nb, % must be escaped in lua patterns + units = { '%%', 'pt', 'mm', 'cm', 'in', 'ex', 'em' } + + local result = nil + + -- ignore spaces, controls and punctuation other than + -- dot, plus, minus + text = string.gsub(text, "[%s%c,;%(%)%[%]%*%?%%%^%$]+", "") + + for _,unit in pairs(units) do + + -- match .11em format and 1.2em format + if string.match(text, '^%.%d+'.. unit .. '$') or + string.match(text, '^%d+%.?%d*'.. unit .. '$') then + + result = text + + end + + end + + return result +end + + +-- # Filter-specific functions + +--- Process the metadata block. +-- Adds any needed material to the document's metadata block. +-- @param meta the document's metadata element +local function process_meta(meta) + + -- in LaTeX, require the `multicols` package + if FORMAT:match('latex') then + + return add_header_includes(meta, + pandoc.RawBlock('latex', '\\usepackage{multicol}\n')) + + end + + -- in html, ensure that the first element of `columns` div + -- has a top margin of zero (otherwise we get white space + -- on the top of the first column) + -- idem for the first element after a `column-span` element + if FORMAT:match('html.*') then + + html_header = [[ + +]] + + return add_header_includes(meta, pandoc.RawBlock('html', html_header)) + + end + + return meta + +end + +--- Convert explicit columnbreaks. +-- This function converts any explict columnbreak markup in an element +-- into a single syntax: a Div with class `columnbreak`. +-- Note: if there are `column` Divs in the element we keep them +-- in case they harbour further formatting (e.g. html classes). However +-- we remove their `column` class to avoid double-processing when +-- column fields are nested. +-- @param elem Pandoc native Div element +-- @return elem modified as needed +local function convert_explicit_columbreaks(elem) + + -- if `elem` ends with a `column` Div, this last Div should + -- not generate a columnbreak. We tag it to make sure we don't convert it. + + if #elem.content > 0 + and elem.content[#elem.content].t == 'Div' + and elem.content[#elem.content].classes:includes('column') then + + elem.content[#elem.content] = + add_class(elem.content[#elem.content], 'column-div-in-last-position') + + end + + -- processes `column` Divs and `\columnbreak` LaTeX RawBlocks + filter = { + + Div = function (el) + + -- syntactic sugar: `column-break` converted to `columnbreak` + if el.classes:includes("column-break") then + + el = add_class(el,"columnbreak") + el = remove_class(el,"column-break") + + end + + if el.classes:includes("column") then + + -- with `column` Div, add a break if it's not in last position + if not el.classes:includes('column-div-in-last-position') then + + local breaking_div = pandoc.Div({}) + breaking_div = add_class(breaking_div, "columnbreak") + + el.content:insert(breaking_div) + + -- if it's in the last position, remove the custom tag + else + + el = remove_class(el, 'column-div-in-last-position') + + end + + -- remove `column` classes, but leave the div and other + -- attributes the user might have added + el = remove_class(el, 'column') + + end + + return el + end, + + RawBlock = function (el) + if el.format == "tex" and el.text == '\\columnbreak' then + + local breaking_div = pandoc.Div({}) + breaking_div = add_class(breaking_div, "columnbreak") + + return breaking_div + + else + + return el + + end + + end + + } + + return pandoc.walk_block(elem, filter) + +end + +--- Tag an element with the number of explicit columnbreaks it contains. +-- Counts the number of epxlicit columnbreaks contained in an element and +-- tags the element with a `number_explicit_columnbreaks` attribute. +-- In the process columnbreaks are tagged with the class `columnbreak_already_counted` +-- in order to avoid double-counting when multi-columns are nested. +-- @param elem Pandoc element (native Div element of class `columns`) +-- @return elem with the attribute `number_explicit_columnbreaks` set. +local function tag_with_number_of_explicit_columnbreaks(elem) + + local number_columnbreaks = 0 + + local filter = { + + Div = function(el) + + if el.classes:includes('columnbreak') and + not el.classes:includes('columnbreak_already_counted') then + + number_columnbreaks = number_columnbreaks + 1 + el = add_class(el, 'columnbreak_already_counted') + + end + + return el + + end + } + + elem = pandoc.walk_block(elem, filter) + + elem = set_attribute(elem, 'number_explicit_columnbreaks', + number_columnbreaks) + + return elem + +end + +--- Consolidate aliases for column attributes. +-- Provides syntacic sugar: unifies various ways of +-- specifying attributes of a multi-column environment. +-- When several specifications conflit, favours `column-gap` and +-- `column-rule` specifications. +-- @param elem Pandoc element (Div of class `columns`) with column attributes. +-- @return elem modified as needed. +local function consolidate_colattrib_aliases(elem) + + if elem.attr and elem.attr.attributes then + + -- `column-gap` if the preferred syntax is set, erase others + if elem.attr.attributes["column-gap"] then + + elem = set_attribute(elem, "columngap", nil) + elem = set_attribute(elem, "column-sep", nil) + elem = set_attribute(elem, "columnsep", nil) + + -- otherwise fetch and unset any alias + else + + if elem.attr.attributes["columnsep"] then + + elem = set_attribute(elem, "column-gap", + elem.attr.attributes["columnsep"]) + elem = set_attribute(elem, "columnsep", nil) + + end + + if elem.attr.attributes["column-sep"] then + + elem = set_attribute(elem, "column-gap", + elem.attr.attributes["column-sep"]) + elem = set_attribute(elem, "column-sep", nil) + + end + + if elem.attr.attributes["columngap"] then + + elem = set_attribute(elem, "column-gap", + elem.attr.attributes["columngap"]) + elem = set_attribute(elem, "columngap", nil) + + end + + end + + -- `column-rule` if the preferred syntax is set, erase others + if elem.attr.attributes["column-rule"] then + + elem = set_attribute(elem, "columnrule", nil) + + -- otherwise fetch and unset any alias + else + + if elem.attr.attributes["columnrule"] then + + elem = set_attribute(elem, "column-rule", + elem.attr.attributes["columnrule"]) + elem = set_attribute(elem, "columnrule", nil) + + end + + end + + end + + return elem + +end + +--- Pre-process a Div of class `columns`. +-- Converts explicit column breaks into a unified syntax +-- and count the Div's number of columns. +-- When several columns are nested Pandoc will apply +-- this filter to the innermost `columns` Div first; +-- we use that feature to prevent double-counting. +-- @param elem Pandoc element to be processes (Div of class `columns`) +-- @return elem modified as needed +local function preprocess_columns(elem) + + -- convert any explicit column syntax in a single format: + -- native Divs with class `columnbreak` + + elem = convert_explicit_columbreaks(elem) + + -- count explicit columnbreaks + + elem = tag_with_number_of_explicit_columnbreaks(elem) + + return elem +end + +--- Determine the number of column in a `columns` Div. +-- Looks up two attributes in the Div: the user-specified +-- `columns-count` and the filter-generated `number_explicit_columnbreaks` +-- which is based on the number of explicit breaks specified. +-- The final number of columns will be 2 or whichever of `column-count` and +-- `number_explicit_columnbreaks` is the highest. This ensures there are +-- enough columns for all explicit columnbreaks. +-- This provides a single-column when the user specifies `column-count = 1` and +-- there are no explicit columnbreaks. +-- @param elem Pandoc element (Div of class `columns`) whose number of columns is to be determined. +-- @return number of columns (number, default 2). +local function determine_column_count(elem) + + -- is there a specified column count? + local specified_column_count = 0 + if elem.attr.attributes and elem.attr.attributes['column-count'] then + specified_column_count = tonumber( + elem.attr.attributes["column-count"]) + end + + -- is there an count of explicit columnbreaks? + local number_explicit_columnbreaks = 0 + if elem.attr.attributes and elem.attr.attributes['number_explicit_columnbreaks'] then + + number_explicit_columnbreaks = tonumber( + elem.attr.attributes['number_explicit_columnbreaks'] + ) + + set_attribute(elem, 'number_explicit_columnbreaks', nil) + + end + + -- determines the number of columns + -- default 2 + -- recall that number of columns = nb columnbreaks + 1 + + local number_columns = 2 + + if specified_column_count > 0 or number_explicit_columnbreaks > 0 then + + if (number_explicit_columnbreaks + 1) > specified_column_count then + number_columns = number_explicit_columnbreaks + 1 + else + number_columns = specified_column_count + end + + end + + return number_columns + +end + +--- Convert a pandoc Header to a list of inlines for latex output. +-- @param header Pandoc Header element +-- @return list of Inline elements +local function header_to_latex_and_inlines(header) + +-- @todo check if level interpretation has been shifted, e.g. section is level 2 +-- @todo we could check the Pandoc state to check whether hypertargets are required? + + local latex_header = { + 'section', + 'subsection', + 'subsubsection', + 'paragraph', + 'subparagraph', + } + + -- create a list if the header's inlines + local inlines = pandoc.List:new(header.content) + + -- wrap in a latex_header if available + + if header.level and latex_header[header.level] then + + inlines:insert(1, pandoc.RawInline('latex', + '\\' .. latex_header[header.level] .. '{')) + inlines:insert(pandoc.RawInline('latex', '}')) + + end + + -- wrap in a link if available + if header.identifier then + + inlines:insert(1, pandoc.RawInline('latex', + '\\hypertarget{' .. header.identifier .. '}{%\n')) + inlines:insert(pandoc.RawInline('latex', + '\\label{' .. header.identifier .. '}}')) + + end + + return inlines + +end + +--- Format column span in LaTeX. +-- Formats a bit of text spanning across all columns for LaTeX output. +-- If the colspan is only one block, it is turned into an option +-- of a new `multicol` environment. Otherwise insert it is +-- inserted between the two `multicol` environments. +-- @param elem Pandoc element that is supposed to span across all +-- columns. +-- @param number_columns number of columns in the present environment. +-- @return a pandoc RawBlock element in LaTeX format +local function format_colspan_latex(elem, number_columns) + + local result = pandoc.List:new() + + -- does the content consists of a single header? + + if #elem.content == 1 and elem.content[1].t == 'Header' then + + -- create a list of inlines + inlines = pandoc.List:new() + inlines:insert(pandoc.RawInline('latex', + "\\end{multicols}\n")) + inlines:insert(pandoc.RawInline('latex', + "\\begin{multicols}{".. number_columns .."}[")) + inlines:extend(header_to_latex_and_inlines(elem.content[1])) + inlines:insert(pandoc.RawInline('latex',"]\n")) + + -- insert as a Plain block + result:insert(pandoc.Plain(inlines)) + + return result + + else + + result:insert(pandoc.RawBlock('latex', + "\\end{multicols}\n")) + result:extend(elem.content) + result:insert(pandoc.RawBlock('latex', + "\\begin{multicols}{".. number_columns .."}")) + return result + + end + +end + +--- Format columns for LaTeX output +-- @param elem Pandoc element (Div of "columns" class) containing the +-- columns to be formatted. +-- @return elem with suitable RawBlocks in LaTeX added +local function format_columns_latex(elem) + + -- make content into a List object + pandoc.List:new(elem.content) + + -- how many columns? + number_columns = determine_column_count(elem) + + -- set properties and insert LaTeX environment + -- we wrap the entire environment in `{...}` to + -- ensure properties (gap, rule) don't carry + -- over to following columns + + local latex_begin = '{' + local latex_end = '}' + local ragged = options.raggedcolumns + + -- override global ragged setting? + if elem.classes:includes('ragged') + or elem.classes:includes('raggedcolumns') + or elem.classes:includes('ragged-columns') then + ragged = true + elseif elem.classes:includes('justified') + or elem.classes:includes('justifiedcolumns') + or elem.classes:includes('justified-columns') then + ragged = false + end + if ragged then + latex_begin = latex_begin..'\\raggedcolumns' + end + + if elem.attr.attributes then + + if elem.attr.attributes["column-gap"] then + + local latex_value = ensures_latex_length( + elem.attr.attributes["column-gap"]) + + if latex_value then + + latex_begin = latex_begin .. + "\\setlength{\\columnsep}{" .. latex_value .. "}\n" + + end + + -- remove the `column-gap` attribute + elem = set_attribute(elem, "column-gap", nil) + + end + + if elem.attr.attributes["column-rule"] then + + -- converts CSS value string to LaTeX values + local latex_values = css_values_to_latex( + elem.attr.attributes["column-rule"]) + + if latex_values["length"] then + + latex_begin = latex_begin .. + "\\setlength{\\columnseprule}{" .. + latex_values["length"] .. "}\n" + + end + + if latex_values["color"] then + + latex_begin = latex_begin .. + "\\renewcommand{\\columnseprulecolor}{\\color{" .. + latex_values["color"] .. "}}\n" + + end + + + -- remove the `column-rule` attribute + elem = set_attribute(elem, "column-rule", nil) + + end + + end + + latex_begin = latex_begin .. + "\\begin{multicols}{" .. number_columns .. "}\n" + latex_end = "\\end{multicols}\n" .. latex_end + + elem.content:insert(1, pandoc.RawBlock('latex', latex_begin)) + elem.content:insert(pandoc.RawBlock('latex', latex_end)) + + -- process blocks contained in `elem` + -- turn any explicit columnbreaks into LaTeX markup + -- turn `column-span` Divs into LaTeX markup + + filter = { + + Div = function(el) + + if el.classes:includes("columnbreak") then + return pandoc.RawBlock('latex', "\\columnbreak\n") + end + + if el.classes:includes("column-span-to-be-processed") then + return format_colspan_latex(el, number_columns) + end + + end + + } + + elem = pandoc.walk_block(elem, filter) + + return elem + +end + + +--- Formats columns for html output. +-- Uses CSS3 style added to the elements themselves. +-- @param elem Pandoc element (Div of `columns` style) +-- @return elem with suitable html attributes +local function format_columns_html(elem) + + -- how many columns? + number_columns = determine_column_count(elem) + + -- add properties to the `columns` Div + + elem = add_to_html_style(elem, 'column-count: ' .. number_columns) + elem = set_attribute(elem, 'column-count', nil) + + if elem.attr.attributes then + + if elem.attr.attributes["column-gap"] then + + elem = add_to_html_style(elem, 'column-gap: ' .. + elem.attr.attributes["column-gap"]) + + -- remove the `column-gap` attribute + elem = set_attribute(elem, "column-gap") + + end + + if elem.attr.attributes["column-rule"] then + + elem = add_to_html_style(elem, 'column-rule: ' .. + elem.attr.attributes["column-rule"]) + + -- remove the `column-rule` attribute + elem = set_attribute(elem, "column-rule", nil) + + end + + end + + -- convert any explicit columnbreaks in CSS markup + + filter = { + + Div = function(el) + + -- format column-breaks + if el.classes:includes("columnbreak") then + + el = add_to_html_style(el, 'break-after: column') + + -- remove columbreaks class to avoid double processing + -- when nested + -- clean up already-counted tag + el = remove_class(el, "columnbreak") + el = remove_class(el, "columnbreak_already_counted") + + -- format column-spans + elseif el.classes:includes("column-span-to-be-processed") then + + el = add_to_html_style(el, 'column-span: all') + + -- remove column-span-to-be-processed class to avoid double processing + -- add column-span class to allow for styling + el = add_class(el, "column-span") + el = remove_class(el, "column-span-to-be-processed") + + end + + return el + + end + + } + + elem = pandoc.walk_block(elem, filter) + + return elem + +end + + +-- # Main filters + +--- Formating filter. +-- Applied last, converts prepared columns in target output formats +-- @field Div looks for `columns` class +format_filter = { + + Div = function (element) + + -- pick up `columns` Divs for formatting + if element.classes:includes ("columns") then + + if FORMAT:match('latex') then + element = format_columns_latex(element) + elseif FORMAT:match('html.*') then + element = format_columns_html(element) + end + + return element + + end + + end +} + +--- Preprocessing filter. +-- Processes meta-data fields and walks the document to pre-process +-- columns blocks. Determine how many columns they contain, tags the +-- last column Div, etc. Avoids double-counting when columns environments +-- are nested. +-- @field Div looks for `columns` class +-- @field Meta processes the metadata block +preprocess_filter = { + + Div = function (element) + + -- send `columns` Divs to pre-processing + if element.classes:includes("columns") then + return preprocess_columns(element) + end + + end, + + Meta = function (meta) + + return process_meta(meta) + + end +} + +--- Syntactic sugar filter. +-- Provides alternative ways of specifying columns properties. +-- Kept separate from the pre-processing filter for clarity. +-- @field Div looks for Div of classes `columns` (and related) and `column-span` +syntactic_sugar_filter = { + + Div = function(element) + + -- convert "two-columns" into `columns` Divs + for _,class in pairs(element.classes) do + + -- match xxxcolumns, xxx_columns, xxx-columns + -- if xxx is the name of a number, make + -- a `columns` div and set its `column-count` attribute + local number = number_by_name( + string.match(class,'(%a+)[_%-]?columns$') + ) + + if number then + + element = set_attribute(element, + "column-count", tostring(number)) + element = remove_class(element, class) + element = add_class(element, "columns") + + end + + end + + -- allows different ways of specifying `columns` attributes + if element.classes:includes('columns') then + + element = consolidate_colattrib_aliases(element) + + end + + -- `column-span` syntax + -- mark up as "to-be-processed" to avoid + -- double processing when nested + if element.classes:includes('column-span') or + element.classes:includes('columnspan') then + + element = add_class(element, 'column-span-to-be-processed') + element = remove_class(element, 'column-span') + element = remove_class(element, 'columnspan') + + end + + return element + + end + +} + +--- Read options filter +read_options_filter = { + Meta = function (meta) + + if not meta then return end + + -- global vertical ragged / justified settings + if meta.raggedcolumns or meta['ragged-columns'] then + options.raggedcolumns = true + elseif meta.justifiedcolumns or meta['justified-columns'] then + options.raggedcolumns = false + end + + end +} + +-- Main statement returns filters only if the +-- target format matches our list. The filters +-- returned are applied in the following order: +-- 1. `syntatic_sugar_filter` deals with multiple syntax +-- 2. `preprocessing_filter` converts all explicit +-- columnbreaks into a common syntax and tags +-- those that are already counted. We must do +-- that for all `columns` environments before +-- turning any break back into LaTeX `\columnbreak` blocks +-- otherwise we mess up the count in nested `columns` Divs. +-- 3. `format_filter` formats the columns after the counting +-- has been done +if format_matches(target_formats) then + return { + read_options_filter, + syntactic_sugar_filter, + preprocess_filter, + format_filter + } +else + return +end diff --git a/out.pdf b/out.pdf new file mode 100644 index 0000000..5080395 Binary files /dev/null and b/out.pdf differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..187b301 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +click==8.1.7 +future==1.0.0 +pandoc-mustache==0.1.0 +panflute==2.3.1 +pystache==0.6.5 +PyYAML==6.0.1 diff --git a/src/body.md b/src/body.md new file mode 100644 index 0000000..8a72490 --- /dev/null +++ b/src/body.md @@ -0,0 +1,330 @@ +--- +title: Fief +toc: true +geometry: margin=2cm +mustache: +- ./src/vars.yaml +include-before: | + where does this line end up? + + or this one? +--- +\pagebreak +# Gameplay +{{title}} is a blah blah blah +## Checks +Checks are made against a character's [*skills*](#skills). To make a skill attempt, +roll 3d6 and compare the roll to the skill's [*ceiling roll (CR)*](#ceiling-roll). If +you rolled under the [*CR*](#ceiling-roll), congratulations! You succeeded. + +### Ceiling Roll +A skill's Ceiling Roll is equal to the highest associated [*Stat*](#stats) value, +plus the skill's modifier, plus any situational modifiers. For example: + +> Grug is attempting to intimidate a goblin into fleeing, and a skill +> check is called. The player rolls 3d6 for a total of **9**. Grug is +> [*trained*](#trained-attempts) with intimidation and has a Strength score of +> {{grug_str}}. The total CR is: +> +> > {{grug_str}} (stat value) + 0 (skill value) + 1 (Situational Bonus, Grug has just +> ripped another goblin's head off) = **13** +> +> Grug succeeds. + +### Untrained Attempts +Characters that are not trained in a skill can still make *Untrained Attempts*. +These are treated as a skill value of **-{{untrained_malus}}**. For example: + +> Grug spies a strange goblin shrine, and wants to piece together what it means; +> a skill check is called. The player rolls {{untrained_malus}}d6 for a total of **11**. Grug is +> *untrained* in Knowledge (Occult) and has an Intelligence score of {{grug_int}}. The total CR is: +> +> > 9 (stat value) - {{untrained_malus}} (skill value, untrained) + 0 (no situational bonus) = **6** +> +> Grug fails. + +### Trained Attempts + +If a character is *trained* in a skill, they can attempt to make a skill check +without a penalty. We saw an example of this [above](#ceiling-roll). However, +characters can gain a bonus to their skill values, through either [character creation](#character-creation) +or [advancement](#advancement). The skill value for trained skills begins at +0 +when the skill is learned, up to a maximum of +3 + +### Contested Checks + +When a contested check is required, the character initiating the contest makes +their skill check. If that succeeds, then the defender makes their skill check. +If the Initiator fails, or the defender succeeds, then the Initiator's attempt fails. +Let's looks at an example: + +> Grug is trying to sneak past a guard, a Stealth skill check is called, and Grug succeeds. +> The guard then makes a Notice check, and fails. +> Grug is able to sneak past the guard. + +### Critical Success and Critical Failures +A roll of a **3** on a [*check*](#check) is considered a Critical Success, +likewise an **18** is a Critical Failure. + +#### Criticals in Combat +On a Critical Success *Fighting* or *Shooting* check in combat, the damage dice +are doubled, and the roll value is maximised. For example: + +> Grug swings his sword at a goblin, and rolls a critical success. Rather than +> rolling the {{grug_dam_dice}} damage for his Greatsword, he deals 2x(6+6) = 24 damage. + +#### Critical Healing +More details on Criticals and healing can be found under [*Healing*](#healing) +on page X. But for a quick summary: +A Critical Success on a *Healing* check restores all wounds! +A Critical Failure on a *Healing* check kills the patient! + +#### Criticals in Other Contexts +The results of a Critical Success or Critical Failure on a skill check in other +contexts are left up to the GM, but should be suitably spectacular. + +## Combat +### Initiative +When combat begins, Characters are called to make Initiative checks. Characters that succeed +their check go before characters that fail, with ties being broken by Initiative scores. +Initiative for Monsters/NPCs can either be determined the same way, or placed in order of their Initiative scores. + +Play order then alternates between Characters and Monsters/NPCs + +### Actions in Combat +On a turn in combat, a Character can take one action without penalty. If a Character +wishes to make a second action in their turn, they must declare their intended actions +first. If a Character is taking two actions in a turn, any checks performed by +that Character are made with a **-{{minor_malus}}** situational modifier until the start of +their next turn. +A Character cannot cast two [*Spells*](#magic) during a turn. +During a turn a Character may: + +* Attack +* Move +* Cast a Spell +* Hide +* Search +* Use an Object +* Communicate + +### Attacking and Dealing Damage +To make a successful attack against a foe, a Character must succeed on a [*Fighting*](#skills) or +[*Shooting*](#skills) check to hit a foe. Damage dice are then rolled depending on the weapon type. +If the damage value is greater than the foe's [*Armour Value*](#vitals) + [*Toughness*](#vitals), +the strike lands true and the foe gains a Wound. + +> Grug attempts to stab an Orc, with a Strength of {{grug_str}} and a Fighting skill value of {{grug_fighting}}, +> a skill check is called and the player rolls 3d6 for a total of 7, a success! +> The player rolls {{grug_dam_dice}} for damage, but only rolls a 2, since this +> is under the Orc's Toughness of {{orc_tn}}, the Orc is able to shrug off the +> blow. + +### Moving +A Character can move up to 30ft (6 inches) if they choose to take the *Move* action. + +### Casting a Spell +When a Character takes the *Cast a Spell* action, they declare the spell that +they are beginning to cast. And make a check using their appropriate skill. +On a failure, the spell fizzles. On a success, the Character continues casting +the spell until the start of their next Turn. At the start of the Character's +next turn, the spell resolves; and it's effect(s) occur. +If the Character casting a spell gains a Wound before their spell resolves, it fizzles. +Resolving a spell does not require an action. + +### Hiding +A Character may take the *Hide* action when the line of sight between them and +one or more other characters has been broken. A successful *Stealth* check +hides the Character from the others, who mark the [*Lost Track (Character)*](#status-effects) +condition. A Character may re-enter the sightline of a defender who has +[*Lost Track*](#status-effects) of them without immediate penalty. + +Attacking character who has [*Lost Track*](#status-effects) of you gives you a ++{{minor_boon}} bonus to your [*CR*](#ceiling-roll) to attack them. + +> TODO: Example + +### Search +A Character can search for a hidden door, object or Character in combat with the Search action. +When a Character wishes to Search, a [*Notice*](#skills) check is called. On a success, the +Character finds the door, object, etc. + +When Searching for a Character that you have [*Lost Track*](#status-effects) of, +you have a +{{major_boon}} bonus to your [*Notice*](#skills) check if the target of your search is within +line of sight. + +### Use an Object +Objects may generally be retrieved from a pouch, bandolier, belt etc without an action - but *using* an object; such +as quaffing a potion, zapping a wand etc requires an Action to use. + + +### Communicate +Communicating to other Characters in combat requires an action. This a very broad +action that encompasses everything from communicating tactics; Intimidating or +Persuading foes and more. + +If a Character has [*Lost Track*](#status-effects) of another, you can use the +Communicate action to point out their location, provided that your Character +hasn't also [*Lost Track*](#status-effects) of them. + + +## Healing +The Healing skill is used to remove [*Wounds*](#vitals). Each attempt requires +10 minutes. A success removes one [*Wound*](#vitals). Failing the roll means +that healer isn't able to treat those particular injuries. A change in circumstance, +such as the discovery of medical supplies may warrant another attempt. +Once the Wounds are over an hour old, only [*natural healing*](#natural-healing) +or magic can heal Wounds. +A Critical Success on a *Healing* check restores all wounds! +A Critical Failure on a *Healing* check kills the patient! + +### Natural Healing +Wounded character make a [*Fortitude Save*](#saves) once per night's rest. +Success removes one wound, and a Critical Success removes all wounds. A Critical +Failure when healing naturally *increases* the Character's wounds by one, +this could be the result of blood loss, aggravating the wound or infection. +If this new wound would [*Incapacitate*](#status-effects) the Character, then +they must immediately succeed a second [*Fortitude Save*](#saves) or expire. +If another Character is keeping watch over the wounded Character, and witnesses +the wounded Character about to expire, they can make a *Healing* check; saving and +stabilising the wounded Character on a success. + +### Stabilisation +[*Incapacitated*](#status-effects) Characters can be *Stabilised* with a +successful Healing roll from another Character. + +## Supporting +Successfully *Supporting* another Character grants them a +1 to the [*CR*](#ceiling-roll) +of their next check. To support another Character, first envision how you are trying +to help them, and make the appropriate check when called. For example: + +> Grug's more talkative friend is trying to negotiate a bargain with a shop-keep. +> While his friend is trying to put forward a persuasive argument, Grug grins; +> hefts his Greatsword and gives it a few test swings, keeping his eyes on the shop-keep. +> Grug succeeds his *Intimidation* check, and his friend gains a +1 to the [*CR*](#ceiling-roll) +> of their *Persuasion* check. + +Multiple Characters can *Support* a check, each granting a cumulative +1 to the +[*CR*](#ceiling-roll) + +## Encumberance +Many items in {{title}} can be stuffed into a backpack and not given a second thought. +Items tagged with *Cumbersome* are different, however. A Character can carry a number of +*Cumbersome* items equal to their Strength [*stat*](#stats) $\div$ 4, rounding down. +Some items are *Heavy*. *Heavy* items usually require two Characters to lift. + +## Status Effects +## Situational Rules + +# Character Creation +## Stats +Character in {{title}} are, in part, defined by 8 stats. **Strength**, +**Intelligence**, **Constitution**, **Willpower**, **Dexterity**, **Perception**, +**Beauty** and **Charisma**. To get a better understanding of what these stats +actually _represent_, we can lay them out like so: + + +| | **Physical** | **Cerebral** | +|----------------|--------------|--------------| +| **Power** | Strength | Intelligence | +| **Resistance** | Constitution | Willpower | +| **Prowess** | Dexterity | Perception | +| **Appearance** | Beauty | Charisma | + +To generate your stats, roll 3d6; in order of Strength, Intelligence, Constitution, +Willpower, Dexterity, Perception, Beauty and Charisma. + +## Consider your Character +Given the stats you've rolled, you are beginning to get an idea of where your +character's strengths and weaknesses lie. A value of **10** in a stat can be considered +average for a human, a **7** would be notably deficient, and a **13** would be well above-average. + +## Advantages & Disadvantages +Select two Advantages and Disadvantages for your character: + +### List of Disadvantages +### List of Advantages + +## TODO: Tragic Flaw & Glorious Endeavour + +## Skills +You have **three** skill-points to spend, a skill-point can be spent to become +[*trained*](#trained-attempts) in a skill, or increase the [*CR*](#ceiling-roll) +of a [*trained*](#trained-attempts) skill by one, this can only be done once per skill. +Additionally, choose one [*Save*](#saves) to become [*trained*](#trained-attempts) in. + +### Core Skills + +Core skills are skills that all characters are [*trained*](#trained-attempts) in by default. These are: + +* Athletics (Strength, Dexterity) +* Deception (Charisma) +* Initiative (Dexterity) +* Knowledge, Common (Intelligence) +* Persuasion (Charisma, Beauty) +* Stealth (Dexterity) + +### List of Skills +* Animal Handling (Willpower) +* Escape Artistry (Dexterity) +* Faith (Willpower) +* Fighting (Strength) +* Flirting (Beauty, Charisma) +* Grappling (Strength) +* Healing (Intelligence, Willpower) +* Insight (Perception) +* Intimidation (Charisma, Strength) +* Knowledge, Academics (Intelligence) +* Knowledge, History (Intelligence) +* Knowledge, Nature (Intelligence) +* Knowledge, Occult (Intelligence) +* Knowledge, Religion (Intelligence) +* Notice (Perception) +* Performance (Charisma) +* Shooting (Dexterity) +* Slight of Hand (Dexterity) +* Survival (Willpower) +* Swimming (Strength) +* Thievery (Dexterity) +* Wizardry (Intelligence) + +### Saves +Saves are skills used to resist harmful effects: + +* Reflex (Dexterity) +* Will (Willpower) +* Fortitude (Constitution) + +## Vitals +Characters also have a set of Vitals, these are separate from [*Stats*](#stats) +in that they can change based on circumstance, [*Advantages*](#advantages-&-disadvantages) +and [*Disadvantages*](#advantages-&-disadvantages). +A character's Vitals are as follows: + +* **Speed** - How fast a Character can move in a turn, measured in feet. +* **Size** - How much space a Character takes up, ranging from -1 to +3. See [*Scale*](#scale) on page X for more. +* **Toughness** - A representation of how generally tough a Character is, +incoming Damage is reduced by a Character's Toughness +* **Armour Value** - A representation of how heavily armoured a Character is, +incoming Damage is reduced by a Character's Armour Value +* **Wounds** - A Character can typically take 3 wounds while still functioning +normally, any further damage taken when a Character is at 3 wounds [*incapacitates*](#incapacitation) +them. + +## TODO: Races +## TODO: Wealth +## TODO: Starting Gear + +# TODO: Advancement +# TODO: Dungeoneering +## TODO: Time in the Dungeon +## TODO: Generating a Dungeon + +# TODO: Hex Crawls + +# TODO: Playing with Miniatures +## TODO: Scale +# TODO: Areas of Effect + +# TODO: Running The Game +# TODO: Tables diff --git a/src/preamble.md b/src/preamble.md new file mode 100644 index 0000000..4f757d9 --- /dev/null +++ b/src/preamble.md @@ -0,0 +1 @@ +# Fief diff --git a/src/vars.yaml b/src/vars.yaml new file mode 100644 index 0000000..f6ee163 --- /dev/null +++ b/src/vars.yaml @@ -0,0 +1,11 @@ +title: Fief +untrained_malus: 3 +minor_malus: 2 +major_malus: 4 +minor_boon: 1 +major_boon: 2 +grug_str: 12 +grug_int: 9 +grug_dam_dice: 2d6 +grug_fighting: +1 +orc_tn: 2 diff --git a/template.tex b/template.tex new file mode 100644 index 0000000..6d29e34 --- /dev/null +++ b/template.tex @@ -0,0 +1,594 @@ +% Options for packages loaded elsewhere +\PassOptionsToPackage{unicode$for(hyperrefoptions)$,$hyperrefoptions$$endfor$}{hyperref} +\PassOptionsToPackage{hyphens}{url} +$if(colorlinks)$ +\PassOptionsToPackage{dvipsnames,svgnames,x11names}{xcolor} +$endif$ +$if(CJKmainfont)$ +\PassOptionsToPackage{space}{xeCJK} +$endif$ +% +\documentclass[ +$if(fontsize)$ + $fontsize$, +$endif$ +$if(papersize)$ + $papersize$paper, +$endif$ +$if(beamer)$ + ignorenonframetext, +$if(handout)$ + handout, +$endif$ +$if(aspectratio)$ + aspectratio=$aspectratio$, +$endif$ +$endif$ +$for(classoption)$ + $classoption$$sep$, +$endfor$ +]{$documentclass$} +\usepackage{multicol} +$if(beamer)$ +$if(background-image)$ +\usebackgroundtemplate{% + \includegraphics[width=\paperwidth]{$background-image$}% +} +% In beamer background-image does not work well when other images are used, so this is the workaround +\pgfdeclareimage[width=\paperwidth,height=\paperheight]{background}{$background-image$} +\usebackgroundtemplate{\pgfuseimage{background}} +$endif$ +\usepackage{pgfpages} +\setbeamertemplate{caption}[numbered] +\setbeamertemplate{caption label separator}{: } +\setbeamercolor{caption name}{fg=normal text.fg} +\beamertemplatenavigationsymbols$if(navigation)$$navigation$$else$empty$endif$ +$for(beameroption)$ +\setbeameroption{$beameroption$} +$endfor$ +% Prevent slide breaks in the middle of a paragraph +\widowpenalties 1 10000 +\raggedbottom +$if(section-titles)$ +\setbeamertemplate{part page}{ + \centering + \begin{beamercolorbox}[sep=16pt,center]{part title} + \usebeamerfont{part title}\insertpart\par + \end{beamercolorbox} +} +\setbeamertemplate{section page}{ + \centering + \begin{beamercolorbox}[sep=12pt,center]{part title} + \usebeamerfont{section title}\insertsection\par + \end{beamercolorbox} +} +\setbeamertemplate{subsection page}{ + \centering + \begin{beamercolorbox}[sep=8pt,center]{part title} + \usebeamerfont{subsection title}\insertsubsection\par + \end{beamercolorbox} +} +\AtBeginPart{ + \frame{\partpage} +} +\AtBeginSection{ + \ifbibliography + \else + \frame{\sectionpage} + \fi +} +\AtBeginSubsection{ + \frame{\subsectionpage} +} +$endif$ +$endif$ +$if(beamerarticle)$ +\usepackage{beamerarticle} % needs to be loaded first +$endif$ +\usepackage{amsmath,amssymb} +$if(linestretch)$ +\usepackage{setspace} +$endif$ +\usepackage{iftex} +\ifPDFTeX + \usepackage[$if(fontenc)$$fontenc$$else$T1$endif$]{fontenc} + \usepackage[utf8]{inputenc} + \usepackage{textcomp} % provide euro and other symbols +\else % if luatex or xetex +$if(mathspec)$ + \ifXeTeX + \usepackage{mathspec} % this also loads fontspec + \else + \usepackage{unicode-math} % this also loads fontspec + \fi +$else$ + \usepackage{unicode-math} % this also loads fontspec +$endif$ + \defaultfontfeatures{Scale=MatchLowercase}$-- must come before Beamer theme + \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} +\fi +$if(fontfamily)$ +$else$ +$-- Set default font before Beamer theme so the theme can override it +\usepackage{lmodern} +$endif$ +$-- Set Beamer theme before user font settings so they can override theme +$if(beamer)$ +$if(theme)$ +\usetheme[$for(themeoptions)$$themeoptions$$sep$,$endfor$]{$theme$} +$endif$ +$if(colortheme)$ +\usecolortheme{$colortheme$} +$endif$ +$if(fonttheme)$ +\usefonttheme{$fonttheme$} +$endif$ +$if(mainfont)$ +\usefonttheme{serif} % use mainfont rather than sansfont for slide text +$endif$ +$if(innertheme)$ +\useinnertheme{$innertheme$} +$endif$ +$if(outertheme)$ +\useoutertheme{$outertheme$} +$endif$ +$endif$ +$-- User font settings (must come after default font and Beamer theme) +$if(fontfamily)$ +\usepackage[$for(fontfamilyoptions)$$fontfamilyoptions$$sep$,$endfor$]{$fontfamily$} +$endif$ +\ifPDFTeX\else + % xetex/luatex font selection +$if(mainfont)$ + \setmainfont[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} +$endif$ +$if(sansfont)$ + \setsansfont[$for(sansfontoptions)$$sansfontoptions$$sep$,$endfor$]{$sansfont$} +$endif$ +$if(monofont)$ + \setmonofont[$for(monofontoptions)$$monofontoptions$$sep$,$endfor$]{$monofont$} +$endif$ +$for(fontfamilies)$ + \newfontfamily{$fontfamilies.name$}[$for(fontfamilies.options)$$fontfamilies.options$$sep$,$endfor$]{$fontfamilies.font$} +$endfor$ +$if(mathfont)$ +$if(mathspec)$ + \ifXeTeX + \setmathfont(Digits,Latin,Greek)[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \else + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \fi +$else$ + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} +$endif$ +$endif$ +$if(CJKmainfont)$ + \ifXeTeX + \usepackage{xeCJK} + \setCJKmainfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + $if(CJKsansfont)$ + \setCJKsansfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKsansfont$} + $endif$ + $if(CJKmonofont)$ + \setCJKmonofont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmonofont$} + $endif$ + \fi +$endif$ +$if(luatexjapresetoptions)$ + \ifLuaTeX + \usepackage[$for(luatexjapresetoptions)$$luatexjapresetoptions$$sep$,$endfor$]{luatexja-preset} + \fi +$endif$ +$if(CJKmainfont)$ + \ifLuaTeX + \usepackage[$for(luatexjafontspecoptions)$$luatexjafontspecoptions$$sep$,$endfor$]{luatexja-fontspec} + \setmainjfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +\fi +$if(zero-width-non-joiner)$ +%% Support for zero-width non-joiner characters. +\makeatletter +\def\zerowidthnonjoiner{% + % Prevent ligatures and adjust kerning, but still support hyphenating. + \texorpdfstring{% + \TextOrMath{\nobreak\discretionary{-}{}{\kern.03em}% + \ifvmode\else\nobreak\hskip\z@skip\fi}{}% + }{}% +} +\makeatother +\ifPDFTeX + \DeclareUnicodeCharacter{200C}{\zerowidthnonjoiner} +\else + \catcode`^^^^200c=\active + \protected\def ^^^^200c{\zerowidthnonjoiner} +\fi +%% End of ZWNJ support +$endif$ +% Use upquote if available, for straight quotes in verbatim environments +\IfFileExists{upquote.sty}{\usepackage{upquote}}{} +\IfFileExists{microtype.sty}{% use microtype if available + \usepackage[$for(microtypeoptions)$$microtypeoptions$$sep$,$endfor$]{microtype} + \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts +}{} +$if(indent)$ +$else$ +\makeatletter +\@ifundefined{KOMAClassName}{% if non-KOMA class + \IfFileExists{parskip.sty}{% + \usepackage{parskip} + }{% else + \setlength{\parindent}{0pt} + \setlength{\parskip}{6pt plus 2pt minus 1pt}} +}{% if KOMA class + \KOMAoptions{parskip=half}} +\makeatother +$endif$ +$if(verbatim-in-note)$ +\usepackage{fancyvrb} +$endif$ +\usepackage{xcolor} +$if(geometry)$ +$if(beamer)$ +\geometry{$for(geometry)$$geometry$$sep$,$endfor$} +$else$ +\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} +$endif$ +$endif$ +$if(beamer)$ +\newif\ifbibliography +$endif$ +$if(listings)$ +\usepackage{listings} +\newcommand{\passthrough}[1]{#1} +\lstset{defaultdialect=[5.3]Lua} +\lstset{defaultdialect=[x86masm]Assembler} +$endif$ +$if(lhs)$ +\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} +$endif$ +$if(highlighting-macros)$ +$highlighting-macros$ +$endif$ +$if(tables)$ +\usepackage{longtable,booktabs,array} +$if(multirow)$ +\usepackage{multirow} +$endif$ +\usepackage{calc} % for calculating minipage widths +$if(beamer)$ +\usepackage{caption} +% Make caption package work with longtable +\makeatletter +\def\fnum@table{\tablename~\thetable} +\makeatother +$else$ +% Correct order of tables after \paragraph or \subparagraph +\usepackage{etoolbox} +\makeatletter +\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{} +\makeatother +% Allow footnotes in longtable head/foot +\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}} +\makesavenoteenv{longtable} +$endif$ +$endif$ +$if(graphics)$ +\usepackage{graphicx} +\makeatletter +\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} +\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} +\makeatother +% Scale images if necessary, so that they will not overflow the page +% margins by default, and it is still possible to overwrite the defaults +% using explicit options in \includegraphics[width, height, ...]{} +\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} +% Set default figure placement to htbp +\makeatletter +\def\fps@figure{htbp} +\makeatother +$endif$ +$if(svg)$ +\usepackage{svg} +$endif$ +$if(strikeout)$ +$-- also used for underline +\ifLuaTeX + \usepackage{luacolor} + \usepackage[soul]{lua-ul} +\else + \usepackage{soul} +$if(CJKmainfont)$ + \ifXeTeX + % soul's \st doesn't work for CJK: + \usepackage{xeCJKfntef} + \renewcommand{\st}[1]{\sout{#1}} + \fi +$endif$ +\fi +$endif$ +\setlength{\emergencystretch}{3em} % prevent overfull lines +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +$if(numbersections)$ +\setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$} +$else$ +\setcounter{secnumdepth}{-\maxdimen} % remove section numbering +$endif$ +$if(subfigure)$ +\usepackage{subcaption} +$endif$ +$if(beamer)$ +$else$ +$if(block-headings)$ +% Make \paragraph and \subparagraph free-standing +\ifx\paragraph\undefined\else + \let\oldparagraph\paragraph + \renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} +\fi +\ifx\subparagraph\undefined\else + \let\oldsubparagraph\subparagraph + \renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} +\fi +$endif$ +$endif$ +$if(pagestyle)$ +\pagestyle{$pagestyle$} +$endif$ +$if(csl-refs)$ +% definitions for citeproc citations +\NewDocumentCommand\citeproctext{}{} +\NewDocumentCommand\citeproc{mm}{% + \begingroup\def\citeproctext{#2}\cite{#1}\endgroup} +\makeatletter + % allow citations to break across lines + \let\@cite@ofmt\@firstofone + % avoid brackets around text for \cite: + \def\@biblabel#1{} + \def\@cite#1#2{{#1\if@tempswa , #2\fi}} +\makeatother +\newlength{\cslhangindent} +\setlength{\cslhangindent}{1.5em} +\newlength{\csllabelwidth} +\setlength{\csllabelwidth}{3em} +\newenvironment{CSLReferences}[2] % #1 hanging-indent, #2 entry-spacing + {\begin{list}{}{% + \setlength{\itemindent}{0pt} + \setlength{\leftmargin}{0pt} + \setlength{\parsep}{0pt} + % turn on hanging indent if param 1 is 1 + \ifodd #1 + \setlength{\leftmargin}{\cslhangindent} + \setlength{\itemindent}{-1\cslhangindent} + \fi + % set entry spacing + \setlength{\itemsep}{#2\baselineskip}}} + {\end{list}} +\usepackage{calc} +\newcommand{\CSLBlock}[1]{\hfill\break#1\hfill\break} +\newcommand{\CSLLeftMargin}[1]{\parbox[t]{\csllabelwidth}{\strut#1\strut}} +\newcommand{\CSLRightInline}[1]{\parbox[t]{\linewidth - \csllabelwidth}{\strut#1\strut}} +\newcommand{\CSLIndent}[1]{\hspace{\cslhangindent}#1} +$endif$ +$if(lang)$ +\ifLuaTeX +\usepackage[bidi=basic]{babel} +\else +\usepackage[bidi=default]{babel} +\fi +$if(babel-lang)$ +\babelprovide[main,import]{$babel-lang$} +$if(mainfont)$ +\ifPDFTeX +\else +\babelfont{rm}[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} +\fi +$endif$ +$endif$ +$for(babel-otherlangs)$ +\babelprovide[import]{$babel-otherlangs$} +$endfor$ +$for(babelfonts/pairs)$ +\babelfont[$babelfonts.key$]{rm}{$babelfonts.value$} +$endfor$ +% get rid of language-specific shorthands (see #6817): +\let\LanguageShortHands\languageshorthands +\def\languageshorthands#1{} +$endif$ +$for(header-includes)$ +$header-includes$ +$endfor$ +\ifLuaTeX + \usepackage{selnolig} % disable illegal ligatures +\fi +$if(dir)$ +\ifPDFTeX + \TeXXeTstate=1 + \newcommand{\RL}[1]{\beginR #1\endR} + \newcommand{\LR}[1]{\beginL #1\endL} + \newenvironment{RTL}{\beginR}{\endR} + \newenvironment{LTR}{\beginL}{\endL} +\fi +$endif$ +$if(natbib)$ +\usepackage[$natbiboptions$]{natbib} +\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} +$endif$ +$if(biblatex)$ +\usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex} +$for(bibliography)$ +\addbibresource{$bibliography$} +$endfor$ +$endif$ +$if(nocite-ids)$ +\nocite{$for(nocite-ids)$$it$$sep$, $endfor$} +$endif$ +$if(csquotes)$ +\usepackage{csquotes} +$endif$ +\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} +\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available +\urlstyle{$if(urlstyle)$$urlstyle$$else$same$endif$} +$if(links-as-notes)$ +% Make links footnotes instead of hotlinks: +\DeclareRobustCommand{\href}[2]{#2\footnote{\url{#1}}} +$endif$ +$if(verbatim-in-note)$ +\VerbatimFootnotes % allow verbatim text in footnotes +$endif$ +\hypersetup{ +$if(title-meta)$ + pdftitle={$title-meta$}, +$endif$ +$if(author-meta)$ + pdfauthor={$author-meta$}, +$endif$ +$if(lang)$ + pdflang={$lang$}, +$endif$ +$if(subject)$ + pdfsubject={$subject$}, +$endif$ +$if(keywords)$ + pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, +$endif$ +$if(colorlinks)$ + colorlinks=true, + linkcolor={$if(linkcolor)$$linkcolor$$else$Maroon$endif$}, + filecolor={$if(filecolor)$$filecolor$$else$Maroon$endif$}, + citecolor={$if(citecolor)$$citecolor$$else$Blue$endif$}, + urlcolor={$if(urlcolor)$$urlcolor$$else$Blue$endif$}, +$else$ +$if(boxlinks)$ +$else$ + hidelinks, +$endif$ +$endif$ + pdfcreator={LaTeX via pandoc}} + +$if(title)$ +\title{$title$$if(thanks)$\thanks{$thanks$}$endif$} +$endif$ +$if(subtitle)$ +$if(beamer)$ +$else$ +\usepackage{etoolbox} +\makeatletter +\providecommand{\subtitle}[1]{% add subtitle to \maketitle + \apptocmd{\@title}{\par {\large #1 \par}}{}{} +} +\makeatother +$endif$ +\subtitle{$subtitle$} +$endif$ +\author{$for(author)$$author$$sep$ \and $endfor$} +\date{$date$} +$if(beamer)$ +$if(institute)$ +\institute{$for(institute)$$institute$$sep$ \and $endfor$} +$endif$ +$if(titlegraphic)$ +\titlegraphic{\includegraphics{$titlegraphic$}} +$endif$ +$if(logo)$ +\logo{\includegraphics{$logo$}} +$endif$ +$endif$ + +\begin{document} +$if(has-frontmatter)$ +\frontmatter +$endif$ +$if(title)$ +$if(beamer)$ +\frame{\titlepage} +$else$ +\maketitle +$endif$ +$if(abstract)$ +\begin{abstract} +$abstract$ +\end{abstract} +$endif$ +$endif$ + +$for(include-before)$ +$include-before$ + +$endfor$ +$if(toc)$ +$if(toc-title)$ +\renewcommand*\contentsname{$toc-title$} +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks] +$if(toc-title)$ + \frametitle{$toc-title$} +$endif$ + \tableofcontents[hideallsubsections] +\end{frame} +$else$ +{ +$if(colorlinks)$ +\hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$$endif$} +$endif$ +\setcounter{tocdepth}{$toc-depth$} +\tableofcontents +} +$endif$ +$endif$ +$if(lof)$ +\listoffigures +$endif$ +$if(lot)$ +\listoftables +$endif$ +$if(linestretch)$ +\setstretch{$linestretch$} +$endif$ +$if(has-frontmatter)$ +\mainmatter +$endif$ +\begin{multicols}{2} +$body$ +\end{multicols} + +$if(has-frontmatter)$ +\backmatter +$endif$ +$if(natbib)$ +$if(bibliography)$ +$if(biblio-title)$ +$if(has-chapters)$ +\renewcommand\bibname{$biblio-title$} +$else$ +\renewcommand\refname{$biblio-title$} +$endif$ +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue +$endif$ + \bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$} +$if(beamer)$ +\end{frame} +$endif$ + +$endif$ +$endif$ +$if(biblatex)$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue + \printbibliography[heading=none] +\end{frame} +$else$ +\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ +$endif$ + +$endif$ +$for(include-after)$ +$include-after$ + +$endfor$ +\end{document}