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/>.
|
|
|
|
|
|
|
|
import mistletoe
|
|
|
|
import mistletoe.latex_renderer
|
|
|
|
|
|
|
|
LMARG = 1 #cm
|
|
|
|
|
|
|
|
def cm_to_twip(cms):
|
|
|
|
return int(cms * 0.3937008 * 1440)
|
|
|
|
|
|
|
|
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'] = cm_to_twip(LMARG)
|
|
|
|
|
|
|
|
return fstr2.format(*args, **kwargs)
|
|
|
|
|
|
|
|
class RTFRenderer(mistletoe.base_renderer.BaseRenderer):
|
|
|
|
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
|
|
|
|
|
2019-09-11 09:10:52 +10:00
|
|
|
self.quotemargin = 0
|
2019-09-09 13:25:17 +10:00
|
|
|
self.heading_last = False
|
|
|
|
|
|
|
|
def render_strong(self, token):
|
|
|
|
return format(r'{\b <>}', self.render_inner(token))
|
|
|
|
|
|
|
|
def render_emphasis(self, token):
|
|
|
|
return format(r'{\i <>}', self.render_inner(token))
|
|
|
|
|
|
|
|
def render_inline_code(self, token):
|
|
|
|
raise Exception('NYI')
|
|
|
|
|
|
|
|
def render_raw_text(self, token):
|
|
|
|
return token.content.replace('\\', '\\\\').replace('{', r'\{').replace('}', r'\}')
|
|
|
|
|
|
|
|
def render_strikethrough(self, token):
|
|
|
|
raise Exception('NYI')
|
|
|
|
|
|
|
|
def render_image(self, token):
|
|
|
|
raise Exception('NYI')
|
|
|
|
|
|
|
|
def render_link(self, token):
|
2019-09-22 03:45:28 +10:00
|
|
|
return r'{{\field{{\*\fldinst{{HYPERLINK "{target}"}}}}{{\fldrslt{{\ul\cf1 {inner}}}}}}}'.format(target=token.target, inner=self.render_inner(token))
|
2019-09-09 13:25:17 +10:00
|
|
|
|
|
|
|
def render_auto_link(self, token):
|
2019-09-22 03:45:28 +10:00
|
|
|
return self.render_link(token)
|
2019-09-09 13:25:17 +10:00
|
|
|
|
|
|
|
def render_escape_sequence(self, token):
|
2019-09-22 03:45:28 +10:00
|
|
|
return self.render_inner(token)
|
2019-09-09 13:25:17 +10:00
|
|
|
|
2019-09-14 23:02:14 +10:00
|
|
|
def render_cross_reference(self, token):
|
|
|
|
return '{reference_type}{reference_num}'.format(
|
|
|
|
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:
|
|
|
|
# Part
|
|
|
|
heading_last, self.heading_last = self.heading_last, True
|
|
|
|
return format(r'{\sb<space_above>\keepn\b\fs26\qc\caps <content>\par}',
|
|
|
|
space_above=cm_to_twip(1),
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
if token.level == 2:
|
|
|
|
# Division
|
|
|
|
heading_last, self.heading_last = self.heading_last, True
|
|
|
|
return format(r'{\sb<space_above>\keepn\b\fs26\qc <content>\par}',
|
|
|
|
space_above=cm_to_twip(1 if not heading_last else 0),
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
if token.level == 3:
|
|
|
|
# Section
|
|
|
|
heading_last, self.heading_last = self.heading_last, False
|
|
|
|
return format(r'{\keepn\b\li<hangindent> <content>\par}',
|
2019-09-15 00:31:53 +10:00
|
|
|
hangindent=cm_to_twip(LMARG)+self.quotemargin,
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
if token.level == 4:
|
|
|
|
heading_last, self.heading_last = self.heading_last, False
|
|
|
|
return format(r'{\keepn\i\li<hangindent> <content>\par}',
|
2019-09-11 09:10:52 +10:00
|
|
|
hangindent=cm_to_twip(LMARG)+self.quotemargin,
|
2019-09-09 22:39:45 +10:00
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
def render_numbered_heading(self, token):
|
2019-09-09 13:25:17 +10:00
|
|
|
if token.level == 1:
|
|
|
|
# Part
|
|
|
|
heading_last, self.heading_last = self.heading_last, True
|
|
|
|
return format(r'{\sb<space_above>\keepn\b\fs26\qc\caps Part <label>\u8212?<content>\par}',
|
|
|
|
space_above=cm_to_twip(1),
|
|
|
|
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'{\sb<space_above>\keepn\b\fs26\qc Division <label>\u8212?<content>\par}',
|
|
|
|
space_above=cm_to_twip(1 if not heading_last else 0),
|
|
|
|
label=token.label,
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
if token.level == 3:
|
|
|
|
# Section
|
|
|
|
heading_last, self.heading_last = self.heading_last, False
|
2019-09-11 09:10:52 +10:00
|
|
|
return format(r'{\keepn\b\fi-<hangindent>\li<leftmargin> <label>\tab <content>\par}',
|
2019-09-09 13:25:17 +10:00
|
|
|
hangindent=cm_to_twip(LMARG),
|
2019-09-11 09:10:52 +10:00
|
|
|
leftmargin=cm_to_twip(LMARG)+self.quotemargin,
|
2019-09-09 13:25:17 +10:00
|
|
|
label=token.label,
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
def render_quote(self, token):
|
2019-09-11 09:10:52 +10:00
|
|
|
self.quotemargin = cm_to_twip(LMARG)
|
|
|
|
inner = self.render_inner(token)
|
|
|
|
self.quotemargin = 0
|
|
|
|
return inner
|
2019-09-09 13:25:17 +10:00
|
|
|
|
|
|
|
def render_paragraph(self, token):
|
|
|
|
self.heading_last = False
|
|
|
|
|
2019-09-11 09:10:52 +10:00
|
|
|
return format(r'{\li<leftmargin> <content>\par}',
|
|
|
|
leftmargin=cm_to_twip(LMARG)+self.quotemargin,
|
2019-09-09 13:25:17 +10:00
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
def render_block_code(self, token):
|
|
|
|
raise Exception('NYI')
|
|
|
|
|
|
|
|
def render_list(self, token):
|
2019-09-22 03:47:17 +10:00
|
|
|
self.heading_last = False
|
|
|
|
|
|
|
|
return format(r'{\li<leftmargin> <content>}',
|
|
|
|
leftmargin=cm_to_twip(LMARG)+self.quotemargin,
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
2019-09-09 13:25:17 +10:00
|
|
|
|
|
|
|
def render_list_item(self, token):
|
2019-09-22 03:47:17 +10:00
|
|
|
return format(r'\bullet\~<content>', content=self.render_inner(token))
|
2019-09-09 13:25:17 +10:00
|
|
|
|
|
|
|
def render_table(self, token):
|
2019-09-09 22:39:45 +10:00
|
|
|
result = [format(r'{\sa0\trowd\trgaph120\trleft<lmarg>')]
|
|
|
|
|
|
|
|
# Get column weights
|
|
|
|
if hasattr(token, 'header'):
|
2019-09-11 11:10:43 +10:00
|
|
|
weights = [col.weight for col in token.header.children]
|
2019-09-09 22:39:45 +10:00
|
|
|
else:
|
|
|
|
weights = [1 for col in token.column_align]
|
|
|
|
|
|
|
|
# Get running totals
|
|
|
|
cellx = [sum(weights[0:i+1]) for i in range(len(weights))]
|
|
|
|
# Convert to widths
|
|
|
|
cellx = [x * cm_to_twip(21-LMARG-4) / cellx[-1] + cm_to_twip(LMARG) for x in cellx]
|
|
|
|
|
|
|
|
for x in cellx:
|
|
|
|
result.append(format(r'\clbrdrt\brdrs\clbrdrb\brdrs\cellx<>', int(x)))
|
|
|
|
result.append(' ')
|
|
|
|
|
|
|
|
if hasattr(token, 'header'):
|
|
|
|
result.append(self.render_table_header(token.header))
|
|
|
|
|
|
|
|
for i, row in enumerate(token.children):
|
|
|
|
result.append(self.render_table_row(row))
|
|
|
|
|
|
|
|
result.append(r'}')
|
|
|
|
return ''.join(result)
|
|
|
|
|
|
|
|
def render_table_header(self, token):
|
2019-09-11 11:10:43 +10:00
|
|
|
cells1 = ['{\\b Column ' + child.label + '\\intbl\\cell}' for child in token.children]
|
|
|
|
cells2 = ['{\\b ' + self.render_inner(child) + '\\intbl\\cell}' for child in token.children]
|
2019-09-09 22:39:45 +10:00
|
|
|
return ''.join(cells1) + '\\row' + ''.join(cells2) + '\\row '
|
2019-09-09 13:25:17 +10:00
|
|
|
|
|
|
|
def render_table_row(self, token):
|
2019-09-11 11:10:43 +10:00
|
|
|
return ''.join([self.render_table_cell(cell) for cell in token.children]) + '\\row '
|
2019-09-09 13:25:17 +10:00
|
|
|
|
2019-09-11 11:10:43 +10:00
|
|
|
def render_table_cell(self, token):
|
|
|
|
if token.colnum == 0 and token.label:
|
2019-09-09 22:39:45 +10:00
|
|
|
return format(r'{<rownum>. <content>\intbl\cell}',
|
2019-09-11 11:10:43 +10:00
|
|
|
rownum=token.label,
|
2019-09-15 00:32:59 +10:00
|
|
|
content=self.render_inner(token).replace(r'\newline', r'\line')
|
2019-09-09 22:39:45 +10:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
return format(r'{<content>\intbl\cell}',
|
2019-09-15 00:32:59 +10:00
|
|
|
content=self.render_inner(token).replace(r'\newline', r'\line')
|
2019-09-09 22:39:45 +10:00
|
|
|
)
|
2019-09-09 13:25:17 +10:00
|
|
|
|
|
|
|
def render_thematic_break(self, token):
|
|
|
|
raise Exception('NYI')
|
|
|
|
|
|
|
|
def render_line_break(self, token):
|
|
|
|
return r'\line '
|
|
|
|
|
|
|
|
def render_subrules(self, token):
|
|
|
|
self.heading_last = False
|
|
|
|
|
|
|
|
return self.render_inner(token)
|
|
|
|
|
|
|
|
def render_subrules_item(self, token):
|
|
|
|
if token.label:
|
|
|
|
return format(r'{\fi<fi>\li<li> <label>\tab <content>\par}',
|
|
|
|
fi=cm_to_twip(-1),
|
2019-09-11 09:10:52 +10:00
|
|
|
li=cm_to_twip(LMARG + token.level + 1)+self.quotemargin,
|
2019-09-09 13:25:17 +10:00
|
|
|
label=token.label,
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return format(r'{\li<li> <content>\par}',
|
2019-09-11 09:10:52 +10:00
|
|
|
li=cm_to_twip(LMARG + token.level + 1)+self.quotemargin,
|
2019-09-09 13:25:17 +10:00
|
|
|
label=token.label,
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
def render_note(self, token):
|
|
|
|
self.heading_last = False
|
|
|
|
|
2019-09-11 10:34:54 +10:00
|
|
|
return format(r'{\fs20\li<leftindent>{\b <label>:} <content>\par}',
|
|
|
|
leftindent=cm_to_twip(LMARG + token.level),
|
2019-09-09 13:25:17 +10:00
|
|
|
label=token.label,
|
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
def render_definition(self, token):
|
|
|
|
self.heading_last = False
|
|
|
|
|
|
|
|
return format(r'{\fi<fi>\li<li> <content>\par}',
|
|
|
|
fi=cm_to_twip(-1),
|
2019-09-22 03:48:10 +10:00
|
|
|
li=cm_to_twip(LMARG + token.level + 1),
|
2019-09-09 13:25:17 +10:00
|
|
|
content=self.render_inner(token)
|
|
|
|
)
|
|
|
|
|
|
|
|
def render_document(self, token):
|
2019-09-22 03:45:28 +10:00
|
|
|
result_str = format(r'{\rtf1\deff0{\fonttbl{\f0 TeX Gyre Heros{\*\falt FreeSans}{\*\falt Liberation Sans}{\*\falt Arial};}}{\colortbl;\red0\green0\blue0\;\red0\green0\blue255;}{\info{\title <title>}{\author https://gitlab.com/RunasSudo/legalmd}}\paperw<paperw>\paperh<paperh>\margl<margin>\margr<margin>\margt<margin>\margb<margin>{\header\f0\fs16\tqr\tx<flushright> <title>\tab <author>}{\footer\f0\fs16\tqr\tx<flushright> <footer>\tab\chpgn}\fs1\~\par\sa<parskip>\fs24 <inner>}',
|
2019-09-09 13:25:17 +10:00
|
|
|
paperw=cm_to_twip(21),
|
|
|
|
paperh=cm_to_twip(29.7),
|
|
|
|
margin=cm_to_twip(2),
|
|
|
|
flushright=cm_to_twip(21 - 2 - 2),
|
|
|
|
parskip=cm_to_twip(0.35),
|
|
|
|
inner=self.render_inner(token),
|
|
|
|
title=token.title,
|
|
|
|
author=token.author,
|
|
|
|
footer=token.footer
|
|
|
|
)
|
|
|
|
|
|
|
|
result = []
|
|
|
|
for char in result_str:
|
|
|
|
if ord(char) <= 0x7f:
|
|
|
|
result.append(char)
|
|
|
|
else:
|
|
|
|
result.append(r'\u{}?'.format(ord(char)))
|
|
|
|
return ''.join(result)
|