2019-09-09 13:25:17 +10:00
# 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/>.
2019-09-14 23:02:14 +10:00
import hashlib
2019-09-09 13:25:17 +10:00
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 )
2019-09-14 23:02:14 +10:00
self . render_map [ ' CrossReference ' ] = self . render_cross_reference
2019-09-09 22:39:45 +10:00
self . render_map [ ' NumberedHeading ' ] = self . render_numbered_heading
2019-09-09 13:25:17 +10:00
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 ) :
2019-09-09 22:39:45 +10:00
result = super ( ) . render_raw_text ( token )
2019-09-09 13:25:17 +10:00
result = result . replace ( ' ★★★ ' , r ' \ texorpdfstring { \ freeserif ★★★} { ★★★} ' )
return result
2019-09-22 03:45:28 +10:00
def render_link ( self , token ) :
template = ' \\ href {{ {target} }} {{ {inner} }} '
inner = self . render_inner ( token )
return template . format ( target = token . target , inner = inner )
def render_auto_link ( self , token ) :
return ' \\ url {{ {} }} ' . format ( token . target )
2019-09-14 23:02:14 +10:00
def render_cross_reference ( self , token ) :
reference = token . get_reference ( )
if not reference :
raise Exception ( ' Unable to resolve reference " {} " ' . format ( token . reference_num ) )
sha = hashlib . sha256 ( )
sha . update ( reference . full_label ( ) . encode ( ' utf-8 ' ) )
return format ( r ' \ hyperlink { <linkname>} { <reference_type><reference_num>} ' ,
linkname = sha . hexdigest ( ) ,
reference_type = ( token . reference_type + ' ~ ' ) if token . reference_type else ' ' ,
reference_num = token . reference_num
)
2019-09-09 13:25:17 +10:00
def render_heading ( self , token ) :
2019-09-09 22:39:45 +10:00
if token . level == 1 :
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 { <content>} \ phantomsection \ addcontentsline {toc} {section} { <content>} \ nopagebreak \ par} ' ,
content = self . render_inner ( token )
)
if token . level == 2 :
heading_last , self . heading_last = self . heading_last , True
return format ( r ' { \ par \ vspace { <space_above>} \ bfseries \ fontsize {13pt} {15pt} \ selectfont \ centering <content> \ phantomsection \ addcontentsline {toc} {subsection} { <content>} \ nopagebreak \ par} ' ,
space_above = ' 1cm plus 0.3cm minus 0.3cm ' if not heading_last else ' 0cm ' ,
content = self . render_inner ( token )
)
if token . level == 3 :
heading_last , self . heading_last = self . heading_last , False
2019-09-15 00:31:53 +10:00
return format ( r ' { \ par \ leftskip=<lmarg> \ bfseries <content> \ phantomsection \ addcontentsline {toc} {subsubsection} { <content>} \ nopagebreak \ par} ' ,
content = self . render_inner ( token )
)
if token . level == 4 :
heading_last , self . heading_last = self . heading_last , False
return format ( r ' { \ par \ leftskip=<lmarg> \ itshape <content> \ nopagebreak \ par} ' ,
2019-09-09 22:39:45 +10:00
content = self . render_inner ( token )
)
def render_numbered_heading ( self , token ) :
2019-09-15 00:32:48 +10:00
if token . full_label ( ) :
sha = hashlib . sha256 ( )
sha . update ( token . full_label ( ) . encode ( ' utf-8 ' ) )
2019-09-22 03:43:36 +10:00
hyperlink = format ( r ' \ makebox[0pt] { \ hypertarget { <linkname>} {} } ' ,
2019-09-15 00:32:48 +10:00
linkname = sha . hexdigest ( )
)
else :
hyperlink = ' {} '
2019-09-14 23:02:14 +10:00
2019-09-09 13:25:17 +10:00
if token . level == 1 :
# Part
heading_last , self . heading_last = self . heading_last , True
2019-09-14 23:02:14 +10:00
return format ( r ' { \ par \ vspace { 1cm plus 0.3cm minus 0.3cm}<hyperlink> \ bfseries \ fontsize {13pt} {15pt} \ selectfont \ centering \ uppercase { Part <label>—<content>} \ phantomsection \ addcontentsline {toc} {section} { Part <label>—<content>} \ nopagebreak \ par} ' ,
hyperlink = hyperlink ,
2019-09-09 13:25:17 +10:00
label = token . label ,
content = self . render_inner ( token )
)
if token . level == 2 :
# Division
heading_last , self . heading_last = self . heading_last , True
2019-09-14 23:02:14 +10:00
return format ( r ' { \ par \ vspace { <space_above>}<hyperlink> \ bfseries \ fontsize {13pt} {15pt} \ selectfont \ centering Division <label>—<content> \ phantomsection \ addcontentsline {toc} {subsection} { Division <label>—<content>} \ nopagebreak \ par} ' ,
hyperlink = hyperlink ,
2019-09-09 13:25:17 +10:00
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
2019-09-14 23:02:14 +10:00
return format ( r ' { \ par<hyperlink> \ leftskip= \ quotemargin \ bfseries \ makebox[<lmarg>][l] { <label>}<content> \ phantomsection \ addcontentsline {toc} {subsubsection} { \ protect \ numberline { <label>} <content>} \ nopagebreak \ par} ' ,
hyperlink = hyperlink ,
2019-09-09 13:25:17 +10:00
label = token . label ,
content = self . render_inner ( token )
)
def render_paragraph ( self , token ) :
self . heading_last = False
2019-09-11 10:34:54 +10:00
return format ( r ' \ N { \ par \ leftskip= \ dimexpr \ quotemargin+<lmarg> \ relax <content> \ par} \ N ' ,
2019-09-11 09:10:52 +10:00
content = self . render_inner ( token )
)
def render_quote ( self , token ) :
return format ( r ' { \ quotemargin=<lmarg> \ renewcommand { \ addcontentsline}[3] {} <content>} ' ,
2019-09-09 13:25:17 +10:00
content = self . render_inner ( token )
)
def render_definition ( self , token ) :
self . heading_last = False
2019-09-22 03:48:10 +10:00
return format ( r ' \ N { \ par \ leftskip= \ dimexpr<lmarg>+<level>cm \ relax \ relax \ hangindent=1cm <content> \ par} \ N ' ,
level = token . level ,
2019-09-09 13:25:17 +10:00
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 :
2019-09-15 00:32:48 +10:00
if token . full_label ( ) :
sha = hashlib . sha256 ( )
sha . update ( token . full_label ( ) . encode ( ' utf-8 ' ) )
2019-09-22 03:43:36 +10:00
hyperlink = format ( r ' \ makebox[0pt] { \ hypertarget { <linkname>} {} } ' ,
2019-09-15 00:32:48 +10:00
linkname = sha . hexdigest ( )
)
else :
hyperlink = ' {} '
2019-09-14 23:02:14 +10:00
return format ( r ' \ N { \ par<hyperlink> \ leftskip= \ dimexpr \ quotemargin+<lmarg>+<level>cm \ relax \ hangindent=1cm \ parskip=<parskip> \ makebox[<lmarg>][l] { <label>}<content> \ par} \ N ' ,
hyperlink = hyperlink ,
2019-09-09 13:25:17 +10:00
parskip = r ' \ parskip ' , # if token.level <= 1 else '0cm',
label = token . label ,
level = token . level ,
content = self . render_inner ( token )
)
else :
2019-09-11 10:34:54 +10:00
return format ( r ' \ N { \ par \ leftskip= \ dimexpr<lmarg>+<level>cm+1cm \ relax \ parskip=<parskip> <content> \ par} \ N ' ,
2019-09-09 13:25:17 +10:00
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
2019-09-22 03:48:10 +10:00
return format ( r ' \ N { \ nopagebreak \ par \ footnotesize \ selectfont \ settowidth { \ notetaglength} { \ bfseries <label>:} \ addtolength { \ notetaglength} {1em} \ leftskip= \ dimexpr<lmarg>+<level>cm \ relax \ hangindent= \ notetaglength \ makebox[ \ notetaglength][l] { \ bfseries <label>:}<content> \ par} \ N ' ,
2019-09-11 10:34:54 +10:00
level = token . level ,
2019-09-09 13:25:17 +10:00
label = token . label ,
content = self . render_inner ( token )
)
2019-09-09 22:39:45 +10:00
def render_table ( self , token ) :
2019-09-11 11:25:33 +10:00
align = [ r ' l@ { \ hspace {1ex} } ' ] + [ ' X ' for col in token . column_align ]
2019-09-09 22:39:45 +10:00
if hasattr ( token , ' header ' ) :
for i , col in enumerate ( token . header . children ) :
2019-09-11 11:25:33 +10:00
align [ i + 1 ] + = ' [ {} ] ' . format ( col . weight )
2019-09-09 22:39:45 +10:00
result = [
format ( ' { \\ footnotesize \\ begin {longtabu} to \\ dimexpr \\ columnwidth-<lmarg> \\ relax [r] { ' ) ,
' ' . join ( align ) ,
' } \\ toprule \n ' ]
if hasattr ( token , ' header ' ) :
result . append ( self . render_table_header ( token . header ) + ' \\ midrule[ \\ heavyrulewidth] \n ' )
for i , row in enumerate ( token . children ) :
result . append ( self . render_table_row ( row ) )
if i != len ( token . children ) - 1 :
result . append ( ' \\ midrule \n ' )
result . append ( r ' \ bottomrule \ end {longtabu} } ' )
return ' ' . join ( result )
def render_table_header ( self , token ) :
2019-09-11 11:25:33 +10:00
cells1 = [ ]
cells2 = [ ]
for cell in token . children :
if cell . colnum == 0 :
2019-09-15 00:33:25 +10:00
coldef = format ( r ' p { \ dimexpr \ linewidth-1cm-1ex-<colx> \ tabucolX-<colnd> \ tabcolsep \ relax} ' ,
2019-09-11 12:16:16 +10:00
colx = sum ( col . weight for col in token . children [ 1 : ] ) ,
2019-09-15 00:33:25 +10:00
colnd = 2 * len ( token . children )
2019-09-11 12:16:16 +10:00
)
cells1 . append ( format ( ' \\ multicolumn {2} { <coldef>} { \\ bfseries Column <label>} ' ,
coldef = coldef ,
label = cell . label
) )
cells2 . append ( format ( ' \\ multicolumn {2} { <coldef>} { \\ bfseries <content>} ' ,
coldef = coldef ,
content = self . render_inner ( cell )
) )
2019-09-11 11:25:33 +10:00
else :
cells1 . append ( ' { \\ bfseries Column ' + cell . label + ' } ' )
cells2 . append ( ' { \\ bfseries ' + self . render_inner ( cell ) + ' } ' )
2019-09-09 22:39:45 +10:00
return ' & ' . join ( cells1 ) + ' \\ \\ \\ midrule \n ' + ' & ' . join ( cells2 ) + ' \\ \\ '
def render_table_row ( self , token ) :
2019-09-11 11:25:33 +10:00
cells = [ ( token . children [ 0 ] . label + ' . ' ) if token . children [ 0 ] . label else ' ' ] + [ self . render_table_cell ( child ) for child in token . children ]
2019-09-11 11:37:10 +10:00
return ' & ' . join ( cells ) + ' \\ strut \\ \\ '
2019-09-09 22:39:45 +10:00
2019-09-11 11:10:43 +10:00
def render_table_cell ( self , token ) :
2019-09-11 11:25:33 +10:00
return self . render_inner ( token )
2019-09-09 22:39:45 +10:00
2019-09-09 13:25:17 +10:00
def render_document ( self , token ) :
template = r '''
\documentclass [ a4paper , 12 pt ] { article }
< packages >
% Configuration
\usepackage [ top = 1.25 cm , bottom = 1.13 cm , inner = 2 cm , outer = 2 cm , headheight = 8 pt , headsep = 0.5 cm , footskip = 1 cm , includehead , includefoot ] { geometry }
\frenchspacing
\usepackage [ hidelinks , bookmarksnumbered = true , unicode ] { hyperref }
\usepackage { bookmark } % Non - sequential bookmarks
\usepackage { parskip }
\setlength { \parskip } { 0.35 cm plus 0.1 cm minus 0.1 cm }
\usepackage { fancyhdr }
\setlength { \emergencystretch } { 3 em }
\usepackage { microtype }
2019-09-09 22:39:45 +10:00
\usepackage { longtable } \usepackage { tabu } \usepackage { booktabs }
2019-09-11 09:10:52 +10:00
\newlength \quotemargin
2019-09-09 13:25:17 +10:00
\newlength \notetaglength
% 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 } [ 0 pt ] { \vspace { 0.21 cm } \toc @sectionfalse \bfseries } { } { \uppercase } { \titlerule * [ 1 pc ] { . } \contentspage } [ ]
\titlecontents { subsection } [ 0 pt ] { \vspace { 0.21 cm } \bfseries \scshape } { } { \toc @sectionfalse } { \titlerule * [ 1 pc ] { . } \contentspage } [ ]
\dottedcontents { subsubsection } [ \dimexpr < lmarg > + 0.5 cm \relax ] { \ifnum \value { toc @section } = 0 \vspace { 0.21 cm } \fi \toc @sectiontrue } { 1 cm } { 1 pc } [ ]
\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 } { 0 pt }
\lhead { \textsf { \scriptsize < title > } } \rhead { \textsf { \scriptsize < author > } } \lfoot { \textsf { \scriptsize < footer > } } \rfoot { \textsf { \scriptsize \thepage } }
\pagenumbering { roman }
{ \bfseries \centering \fontsize { 13 pt } { 15 pt } \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
)