legalmd/legalmd/latex_renderer.py

174 lines
6.7 KiB
Python

# legalmd: Markdown-based legal markup
# Copyright © 2019 Lee Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import mistletoe
import mistletoe.latex_renderer
LMARG = '1cm'
def format(fstr, *args, **kwargs):
fstr2 = fstr
fstr2 = fstr2.replace('\\N', '\n')
fstr2 = fstr2.replace('{', '{{')
fstr2 = fstr2.replace('}', '}}')
fstr2 = fstr2.replace('<', '{')
fstr2 = fstr2.replace('>', '}')
kwargs['lmarg'] = LMARG
return fstr2.format(*args, **kwargs)
class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
def __init__(self, *extras):
super().__init__(*extras)
self.render_map['Subrules'] = self.render_subrules
self.render_map['SubrulesItem'] = self.render_subrules_item
self.render_map['Note'] = self.render_note
self.render_map['Definition'] = self.render_definition
self.heading_last = False
def render_raw_text(self, token):
result = super().render_inner(token)
result = result.replace('★★★', r'\texorpdfstring{\freeserif ★★★}{★★★}')
return result
def render_heading(self, token):
if token.level == 1:
# Part
heading_last, self.heading_last = self.heading_last, True
return format(r'{\par\vspace{1cm plus 0.3cm minus 0.3cm}\bfseries\fontsize{13pt}{15pt}\selectfont\centering\uppercase{Part <label>—<content>}\phantomsection\addcontentsline{toc}{section}{Part <label>—<content>}\nopagebreak\par}',
label=token.label,
content=self.render_inner(token)
)
if token.level == 2:
# Division
heading_last, self.heading_last = self.heading_last, True
return format(r'{\par\vspace{<space_above>}\bfseries\fontsize{13pt}{15pt}\selectfont\centering Division <label>—<content>\phantomsection\addcontentsline{toc}{subsection}{Division <label>—<content>}\nopagebreak\par}',
space_above='1cm plus 0.3cm minus 0.3cm' if not heading_last else '0cm',
label=token.label,
content=self.render_inner(token)
)
if token.level == 3:
# Section
heading_last, self.heading_last = self.heading_last, False
return format(r'{\par\bfseries\makebox[<lmarg>][l]{<label>}<content>\phantomsection\addcontentsline{toc}{subsubsection}{\protect\numberline{<label>} <content>}\nopagebreak\par}',
label=token.label,
content=self.render_inner(token)
)
def render_paragraph(self, token):
self.heading_last = False
return format(r'\N{\par\leftskip=<lmarg>\updatealign <content>\par}\N',
content=self.render_inner(token)
)
def render_definition(self, token):
self.heading_last = False
return format(r'\N{\par\leftskip=\dimexpr<lmarg>+1cm\relax\hangindent=1cm\updatealign <content>\par}\N',
content=self.render_inner(token)
)
def render_subrules(self, token):
self.heading_last = False
return format(r'\N{<content>}\N',
content=self.render_inner(token)
)
def render_subrules_item(self, token):
if token.label:
return format(r'\N{\par\leftskip=\dimexpr<lmarg>+<level>cm\relax\hangindent=1cm\updatealign\parskip=<parskip>\makebox[<lmarg>][l]{<label>}<content>\par}\N',
parskip=r'\parskip',# if token.level <= 1 else '0cm',
label=token.label,
level=token.level,
content=self.render_inner(token)
)
else:
return format(r'\N{\par\leftskip=\dimexpr<lmarg>+<level>cm+1cm\relax\updatealign\parskip=<parskip> <content>\par}\N',
parskip=r'\parskip',# if token.level <= 1 else '0cm',
level=token.level,
content=self.render_inner(token)
)
def render_note(self, token):
self.heading_last = False
return format(r'\N{\nopagebreak\par\footnotesize\selectfont\settowidth{\notetaglength}{\bfseries <label>:}\addtolength{\notetaglength}{1em}\leftskip=\lastalign\hangindent=\notetaglength\makebox[\notetaglength][l]{\bfseries <label>:}<content>\par}\N',
label=token.label,
content=self.render_inner(token)
)
def render_document(self, token):
template = r'''
\documentclass[a4paper,12pt]{article}
<packages>
% Configuration
\usepackage[top=1.25cm,bottom=1.13cm,inner=2cm,outer=2cm,headheight=8pt,headsep=0.5cm,footskip=1cm,includehead,includefoot]{geometry}
\frenchspacing
\usepackage[hidelinks,bookmarksnumbered=true,unicode]{hyperref}
\usepackage{bookmark} % Non-sequential bookmarks
\usepackage{parskip}
\setlength{\parskip}{0.35cm plus 0.1cm minus 0.1cm}
\usepackage{fancyhdr}
\setlength{\emergencystretch}{3em}
\usepackage{microtype}
\newlength\notetaglength
\newlength\lastalign\newcommand{\updatealign}{\global\lastalign=\dimexpr\leftskip+\hangindent\relax}
% TOC format
\usepackage{titletoc}
\makeatletter
\newcounter{toc@section} % For some reason \newif doesn't work here
\newcommand{\toc@sectiontrue}{\setcounter{toc@section}{1}}
\newcommand{\toc@sectionfalse}{\setcounter{toc@section}{0}}
\titlecontents{section}[0pt]{\vspace{0.21cm}\toc@sectionfalse\bfseries}{}{\uppercase}{\titlerule*[1pc]{.}\contentspage}[]
\titlecontents{subsection}[0pt]{\vspace{0.21cm}\bfseries\scshape}{}{\toc@sectionfalse}{\titlerule*[1pc]{.}\contentspage}[]
\dottedcontents{subsubsection}[\dimexpr<lmarg>+0.5cm\relax]{\ifnum\value{toc@section}=0\vspace{0.21cm}\fi\toc@sectiontrue}{1cm}{1pc}[]
\makeatother
% Fonts
\usepackage[math-style=ISO, bold-style=ISO]{unicode-math}
\setmainfont[RawFeature=-tlig]{TeX Gyre Termes}
\setsansfont[RawFeature=-tlig]{TeX Gyre Heros}
\setmonofont[RawFeature=-tlig]{TeX Gyre Cursor}
\setmathfont[RawFeature=-tlig]{TeX Gyre Termes Math}
\newfontfamily{\freeserif}{FreeSerif}[RawFeature=-tlig]
\renewcommand{\familydefault}{\sfdefault}
% Front matter
\begin{document}
\fancypagestyle{plain}{\fancyhf{}}
\pagestyle{fancy}\fancyhf{}\renewcommand{\headrulewidth}{0pt}
\lhead{\textsf{\scriptsize <title>}}\rhead{\textsf{\scriptsize <author>}}\lfoot{\textsf{\scriptsize <footer>}}\rfoot{\textsf{\scriptsize\thepage}}
\pagenumbering{roman}
{\bfseries\centering\fontsize{13pt}{15pt}\selectfont\uppercase{<title>}\par INDEX\par}
\makeatletter\@starttoc{toc}\makeatother
\newpage\pagenumbering{arabic}
% Content
<inner>
\end{document}'''
self.footnotes.update(token.footnotes)
return format(template,
inner=self.render_inner(token),
packages=self.render_packages(),
title=token.title,
author=token.author,
footer=token.footer
)