0004605: Multiple improvements for LaTeX output (patch from Harald Rosemann)

svn: r18035
This commit is contained in:
Brian Matherly 2011-08-14 14:12:53 +00:00
parent fe4df17781
commit 603238aeb0

View File

@ -1,4 +1,6 @@
#
# -*- coding: utf-8 -*-
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
@ -9,6 +11,7 @@
# 2009 Benny Malengier
# 2010 Peter Landgren
# Copyright (C) 2011 Adam Stein <adam@csh.rit.edu>
# 2011 Harald Rosemann
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -51,6 +54,34 @@ import Utils
_CLICKABLE = r'''\url{\1}'''
#------------------------------------------------------------------------
#
# Special settings for LaTeX output
#
#------------------------------------------------------------------------
# If there is one overwide column consuming too much space of paperwidth its
# line(s) must be broken and wrapped in a 'minipage' with appropriate width.
# The table construction beneath does this job but for now must know which
# column. (Later it shall be determined in another way)
# As to gramps in most cases it is the last column, hence:
MINIPAGE_COL = -1 # Python indexing: last one
# In trunk there is a 'LastChangeReport' where the last but one
# is such a column. If you like, you can instead choose this setting:
# MINIPAGE_COL = -2 # Python indexing: last but one
# ----------------------------------
# For an interim mark of an intended linebreak I use a special pattern. It
# shouldn't interfere with normal text. The charackter '&' is used in LaTeX
# for column separation in tables and may occur there in series. The pattern
# is used here before column separation is set. On the other hand incoming
# text can't show this pattern for it would have been replaced by '\&\&'.
# So the choosen pattern will do the job without confusion:
PAT_FOR_LINE_BREAK = '&&'
#------------------------------------------------------------------------
#
# Latex Article Template
@ -58,7 +89,29 @@ _CLICKABLE = r'''\url{\1}'''
#------------------------------------------------------------------------
_LATEX_TEMPLATE_1 = '\\documentclass[%s]{article}\n'
_LATEX_TEMPLATE = '''\\usepackage[T1]{fontenc}
_LATEX_TEMPLATE = '''%
%
% Vertical spacing between paragraphs:
% take one of three possibilities or modify to your taste:
%\\setlength{\\parskip}{1.0ex plus0.2ex minus0.2ex}
\\setlength{\\parskip}{1.5ex plus0.3ex minus0.3ex}
%\\setlength{\\parskip}{2.0ex plus0.4ex minus0.4ex}
%
% Vertical spacing between lines:
% take one of three possibilities or modify to your taste:
\\renewcommand{\\baselinestretch}{1.0}
%\\renewcommand{\\baselinestretch}{1.1}
%\\renewcommand{\\baselinestretch}{1.2}
%
% Indentation; substitute for '1cm' of gramps
% take one of three possibilities or modify to your taste:
\\newlength{\\grbaseindent}%
\\setlength{\\grbaseindent}{3.0em}
%\\setlength{\\grbaseindent}{2.5em}
%\\setlength{\\grbaseindent}{2.0em}
%
%
\\usepackage[T1]{fontenc}
%
% We use latin1 encoding at a minimum by default.
% GRAMPS uses unicode UTF-8 encoding for its
@ -75,7 +128,8 @@ _LATEX_TEMPLATE = '''\\usepackage[T1]{fontenc}
%\\usepackage[latin1,utf8]{inputenc}
\\usepackage{graphicx} % Extended graphics support
\\usepackage{longtable} % For multi-page tables
\\usepackage{calc} % For margin indents
\\usepackage{calc} % For some calculations
\\usepackage{ifthen} % For table width calculations
%
% Depending on your LaTeX installation, the margins may be too
% narrow. This can be corrected by uncommenting the following
@ -84,19 +138,50 @@ _LATEX_TEMPLATE = '''\\usepackage[T1]{fontenc}
%\\addtolength{\\oddsidemargin}{-0.5in}
%\\addtolength{\\textwidth}{1.0in}
%
% Create a margin-adjusting command that allows LaTeX
% to behave like the other gramps-supported output formats
\\newlength{\\leftedge}
\\setlength{\\leftedge}{\\parindent}
\\newlength{\\grampstext}
\\setlength{\\grampstext}{\\textwidth}
\\newcommand{\\grampsindent}[1]{%
\\setlength{\\parindent}{\\leftedge + #1}%
\\setlength{\\textwidth}{\\grampstext - #1}%
}
%
% New lengths and commands
% for calculating widths and struts in tables
%
\\newlength{\\grtempint}%
\\newlength{\\grminpgwidth}%
\\setlength{\\grminpgwidth}{0.8\\textwidth}
%
\\newcommand{\\inittabvars}[2]{%
\\ifthenelse{\\isundefined{#1}}%
{\\newlength{#1}}{}%
\\setlength{#1}{#2}%
}%
%
\\newcommand{\\grcalctextwidth}[2]{%
\\settowidth{\\grtempint}{#1}% #1: text
\\ifthenelse{\\lengthtest{\\grtempint > #2}}% #2: grcolwidth_
{\\setlength{#2}{\\grtempint}}{}%
}%
%
\\newcommand{\\grminvaltofirst}[2]{%
\\setlength{\\grtempint}{#2}%
\\ifthenelse{\\lengthtest{#1 > \\grtempint}}%
{\\setlength{#1}{\\grtempint}}{}%
}%
%
\\newcommand{\\grmaxvaltofirst}[2]{%
\\setlength{\\grtempint}{#2}%
\\ifthenelse{\\lengthtest{#1 < \\grtempint}}%
{\\setlength{#1}{\\grtempint}}{}%
}%
%
% lift width heigh
\\newcommand{\\tabheadstrutceil}{%
\\rule[0.0ex]{0.00em}{3.5ex}}%
\\newcommand{\\tabheadstrutfloor}{%
\\rule[-2.0ex]{0.00em}{2.5ex}}%
\\newcommand{\\tabrowstrutceil}{%
\\rule[0.0ex]{0.00em}{3.0ex}}%
\\newcommand{\\tabrowstrutfloor}{%
\\rule[-1.0ex]{0.00em}{3.0ex}}%
%
%
\\begin{document}
'''
@ -106,7 +191,7 @@ _LATEX_TEMPLATE = '''\\usepackage[T1]{fontenc}
#
#------------------------------------------------------------------------
# These tables coorelate font sizes to Latex. The first table contains
# These tables correlate font sizes to Latex. The first table contains
# typical font sizes in points. The second table contains the standard
# Latex font size names. Since we use bisect to map the first table to the
# second, we are guaranteed that any font less than 6 points is 'tiny', fonts
@ -121,6 +206,90 @@ _FONT_NAMES = ['tiny', 'scriptsize', 'footnotesize', 'small', '',
def map_font_size(fontsize):
""" Map font size in points to Latex font size """
return _FONT_NAMES[bisect(_FONT_SIZES, fontsize)]
#------------------------------------------------------------------------
#
# auxiliaries to facilitate table construction
#
#------------------------------------------------------------------------
# patterns for regular expressions, module re:
TBLFMT_PAT = re.compile(r'({\|?)l(\|?})')
# constants for routing in table construction:
(CELL_BEG, CELL_TEXT, CELL_END,
ROW_BEG, ROW_END, TAB_BEG, TAB_END,
NESTED_MINPG) = range(8)
FIRST_ROW, SUBSEQ_ROW = range(2)
def get_charform(col_num):
"""
Transfer column number to column charakter,
limited to letters within a-z;
26, there is no need for more.
early test of column count in start_table()
"""
if col_num > ord('z') - ord('a'):
raise ValueError, ''.join((
'\n number of table columns is ', repr(col_num),
'\n should be <= ', repr(ord('z') - ord('a'))))
return chr(ord('a') + col_num)
def get_numform(col_char):
return ord(col_char) - ord('a')
#------------------------------------------
# 'aa' is sufficient for up to 676 table-rows in each table;
# do you need more?
# uncomment one of the two lines
ROW_COUNT_BASE = 'aa'
# ROW_COUNT_BASE = 'aaa'
#------------------------------------------
def str_incr(str_counter):
""" for counting table rows """
lili = list(str_counter)
while 1:
yield ''.join(lili)
if ''.join(lili) == len(lili)*'z':
raise ValueError, ''.join((
'\n can\'t increment string ', ''.join(lili),
' of length ', str(len(lili))))
for i in range(len(lili)-1, -1, -1):
if lili[i] < 'z':
lili[i] = chr(ord(lili[i])+1)
break
else:
lili[i] = 'a'
#------------------------------------------------------------------------
#
# Structure of Table-Memory
#
#------------------------------------------------------------------------
class Tab_Cell():
def __init__(self, colchar, span, head):
self.colchar = colchar
self.span = span
self.head = head
self.content = ''
self.nested_minpg = False
class Tab_Row():
def __init__(self):
self.cells =[]
self.tail = ''
self.addit = '' # for: \\hline, \\cline{}
self.spec_var_cell_width = ''
self.total_width = ''
class Tab_Mem():
def __init__(self, head):
self.head = head
self.tail =''
self.rows =[]
#------------------------------------------------------------------------
#
# Functions for docbackend
@ -154,10 +323,11 @@ def latexescapeverbatim(text):
text = text.replace('{', '\\{')
text = text.replace('}', '\\}')
text = text.replace(' ', '\\ ')
text = text.replace('\n', '\\newline\n')
text = text.replace('\n', '~\\newline\n')
#spaces at begin are normally ignored, make sure they are not.
#due to above a space at begin is now \newline\n\
text = text.replace('\\newline\n\\ ', '\\newline\n\\hspace*{0.1cm}\\ ')
text = text.replace('\\newline\n\\ ',
'\\newline\n\\hspace*{0.1\\grbaseindent}\\ ')
return text
#------------------------------------------------------------------------
@ -229,73 +399,16 @@ class LateXBackend(DocBackend):
"""
Check to make sure filename satisfies the standards for this filetype
"""
if self._filename[-4:] != ".tex":
if not self._filename.endswith(".tex"):
self._filename = self._filename + ".tex"
#------------------------------------------------------------------------
#
# Convert from roman to arabic numbers
#
#------------------------------------------------------------------------
def roman2arabic(strval):
"""
Roman to arabic converter for 0 < num < 4000.
Always returns an integer.
On an invalid input zero is returned.
"""
# Return zero if the type is not str
try:
strval = str(strval).upper()
if not strval:
return 0
except:
return 0
# Return None if there are chars outside of valid roman numerals
if not all(char in 'MDCLXVI' for char in strval):
return 0
vals2 = ['CM', 'CD', 'XC', 'XL', 'IX', 'IV']
nums2 = ( 900, 400, 90, 40, 9, 4)
vals1 = [ 'M', 'D', 'C', 'L', 'X', 'V', 'I']
nums1 = (1000, 500, 100, 50, 10, 5, 1)
ret = 0
max_num = 1000
# Start unrolling strval from left to right,
# up to the penultimate char
i = 0
while i < len(strval):
first_index = vals1.index(strval[i])
if i+1 < len(strval) and strval[i:i+2] in vals2:
this_num = nums2[vals2.index(strval[i:i+2])]
if first_index+1 < len(nums1):
new_max_num = nums1[first_index+1]
else:
new_max_num = 0
i += 2
else:
this_num = nums1[first_index]
new_max_num = this_num
i += 1
# prohibit larger numbers following smaller ones,
# except for the above 2-char combinations
if this_num > max_num:
return 0
ret += this_num
max_num = new_max_num
return ret
#------------------------------------------------------------------------
#
# Paragraph Handling
#
#------------------------------------------------------------------------
class TexFont(object):
def __init__(self, style=None):
if style:
@ -309,6 +422,7 @@ class TexFont(object):
self.leftIndent = ""
self.firstLineIndent = ""
#------------------------------------------------------------------------
#
# LaTeXDoc
@ -318,9 +432,252 @@ class TexFont(object):
class LaTeXDoc(BaseDoc, TextDoc):
"""LaTeX document interface class. Derived from BaseDoc"""
# ---------------------------------------------------------------------
# some additional variables
# -------------------------------------------------------------------------
in_table = False
spec_var_col = 0
textmem = []
in_title = True
# ---------------------------------------------------------------------
# begin of table special treatment
# -------------------------------------------------------------------------
def emit(self, text, tab_state=CELL_TEXT, span=1):
"""
Hand over all text but tables to self._backend.write(), (line 1-2).
In case of tables pass to specal treatment below.
"""
if not self.in_table: # all stuff but table
self._backend.write(text)
else:
self.handle_table(text, tab_state, span)
def handle_table(self, text, tab_state, span):
"""
Collect tables elements in an adequate cell/row/table structure and
call for LaTeX width calculations and writing out
"""
if tab_state == CELL_BEG:
# here text is head
self.textmem = []
self.curcol_char = get_charform(self.curcol-1)
if span > 1: # phantom columns prior to multicolumns
for col in range(self.curcol - span, self.curcol - 1):
col_char = get_charform(col)
phantom = Tab_Cell(col_char, 0, '')
self.tabrow.cells.append(phantom)
if not self.tabrow.cells and text.rfind('{|l') != -1:
self.leftmost_vrule = True
if (self.leftmost_vrule and
self.curcol == self.numcols and
text.endswith('l}')):
text = text.replace('l}', 'l|}')
self.tabcell = Tab_Cell(self.curcol_char, span, text)
elif tab_state == CELL_TEXT:
self.textmem.append(text)
elif tab_state == CELL_END: # text == ''
self.tabcell.content = ''.join(self.textmem)
if self.tabcell.content.find('\\centering') != -1:
self.tabcell.content = self.tabcell.content.replace(
'\\centering', '')
self.tabcell.head = re.sub(
TBLFMT_PAT, '\\1c\\2', self.tabcell.head)
self.tabcell.content = self.tabcell.content.replace('\n\n}', '}')
self.tabrow.cells.append(self.tabcell)
self.textmem = []
elif tab_state == ROW_BEG:
self.tabrow = Tab_Row()
self.leftmost_vrule = False
elif tab_state == ROW_END:
self.tabrow.tail = ''.join(self.textmem)
self.tabrow.addit = text # text: \\hline, \\cline{}
self.tabmem.rows.append(self.tabrow)
elif tab_state == TAB_BEG: # text: \\begin{longtable}[l]{<tblfmt>}
self.tabmem = Tab_Mem(text)
elif tab_state == NESTED_MINPG:
self.tabcell.nested_minpg = True
self.tabcell.content.append(text)
elif tab_state == TAB_END: # text: \\end{longtable}
self.tabmem.tail = text
# table completed, calc widths and write out
self.calc_latex_widths()
self.write_table()
def calc_latex_widths(self):
"""
In tab-cells LaTeX needs fixed width for minipages.
Evaluations are set up here and passed to LaTeX:
Calculate needed/usable widths (lengths),
adjust rows to equal widths (lengths), thus prepared to put something
like hints as '→ ...' in the FamilySheet at rightmost position.
??? Can all this be done exclusively in TeX? Don't know how.
"""
self._backend.write('\\inittabvars{\\grwidthused}{\\tabcolsep}%\n')
width_used = ['\\tabcolsep']
for col_num in range(self.numcols):
col_char = get_charform(col_num)
if MINIPAGE_COL < 0:
self.spec_var_col = self.numcols + MINIPAGE_COL
else:
self.spec_var_col = MINIPAGE_COL
# for all columns but spec_var_col: calculate needed col width
if col_num == self.spec_var_col:
continue
self._backend.write(''.join(('\\inittabvars{\\grcolwidth',
col_char, '}{0.0em}%\n')))
for row in self.tabmem.rows:
cell = row.cells[col_num]
if cell.span:
for part in cell.content.split(PAT_FOR_LINE_BREAK):
self._backend.write(''.join(('\\grcalctextwidth{',
part, '}{\\grcolwidth', col_char, '}%\n')))
row.cells[col_num].content = cell.content.replace(
PAT_FOR_LINE_BREAK, '~\\newline %\n')
self._backend.write(''.join(('\\addtolength{\\grwidthused}{',
'\\grcolwidth', col_char, '+\\tabcolsep}%\n')))
self._backend.write(''.join(('\\inittabvars{',
'\\grwidthusedtill', col_char, '}{ \\grwidthused}%\n')))
width_used.append('\\grwidthusedtill' + col_char)
self._backend.write(''.join(('\\inittabvars{\\grwidthavailable}{',
'\\textwidth-\\grwidthused}%\n')))
# spec_var_col
self._backend.write('\\inittabvars{\\grmaxrowwidth}{0.0em}%\n')
row_alph_counter = str_incr(ROW_COUNT_BASE)
for row in self.tabmem.rows:
row_alph_id = row_alph_counter.next()
row.spec_var_cell_width = ''.join(('\\grcellwidth', row_alph_id))
cell = row.cells[self.spec_var_col]
self._backend.write(''.join(('\\inittabvars{',
row.spec_var_cell_width, '}{0.0em}%\n')))
lines = cell.content.split(PAT_FOR_LINE_BREAK)
for part in lines:
self._backend.write(''.join(('\\grcalctextwidth{', part, '}{',
row.spec_var_cell_width, '}%\n')))
row.cells[self.spec_var_col].content = '~\\newline %\n'.join(lines)
# shorten cells too long, calc row-length, search max row width
self._backend.write('\\inittabvars{\\grspangain}{0.0em}')
if cell.span > 1:
self._backend.write(''.join(('\\inittabvars{\\grspangain}{',
width_used[self.spec_var_col], '-',
width_used[self.spec_var_col - cell.span + 1], '}%\n')))
self._backend.write(''.join(('\\inittabvars{\\grspecavailable}{',
'\\grwidthavailable+\\grspangain}%\n')))
self._backend.write(''.join(('\\grminvaltofirst{',
row.spec_var_cell_width, '}{\\grspecavailable}%\n')))
row.total_width = ''.join(('\\grrowwidth', row_alph_id))
self._backend.write(''.join(('\\inittabvars{', row.total_width,
'}{\\grwidthused +', row.spec_var_cell_width,
'-\\grspangain}%\n')))
self._backend.write(''.join(('\\grmaxvaltofirst{\\grmaxrowwidth}{',
row.total_width, '}%\n')))
# widen spec_var cell:
# (special feature; allows text to be placed at the right border.
# for later use, doesn't matter here)
for row in self.tabmem.rows:
self._backend.write(''.join(('\\grmaxvaltofirst{',
row.spec_var_cell_width, '}{', row.spec_var_cell_width,
'+\\grmaxrowwidth-\\grspangain-', row.total_width, '}%\n')))
def write_table(self):
# open table with '\\begin{longtable}':
self._backend.write(''.join(self.tabmem.head))
# special treatment at begin of longtable for heading and
# closing at top and bottom of table
# and parts of it at pagebreak separating
(separate,
complete_row) = self.mk_first_row(self.tabmem.rows[FIRST_ROW])
self._backend.write(separate)
self._backend.write('\\endhead%\n')
self._backend.write(separate.replace('raisebox{+1ex}',
'raisebox{-2ex}'))
self._backend.write('\\endfoot%\n')
if self.head_line:
self._backend.write('\\hline%\n')
self.head_line= False
else:
self._backend.write('%\n')
self._backend.write(complete_row)
self._backend.write('\\endfirsthead%\n')
self._backend.write('\\endlastfoot%\n')
# now hand over subsequent rows
for row in self.tabmem.rows[SUBSEQ_ROW:]:
complete_row = self.mk_subseq_rows(row)
self._backend.write(complete_row)
# close table with '\\end{longtable}':
self._backend.write(''.join(self.tabmem.tail))
def mk_first_row(self, first_row):
complete =[]
separate =[]
add_vdots = ''
for cell in first_row.cells:
if cell.span == 0: # phantom columns prior to multicolumns
continue
leading = '{'
closing = '} & %\n'
if get_numform(cell.colchar) == self.spec_var_col:
leading = ''.join(('{\\tabheadstrutceil\\begin{minipage}[t]{',
first_row.spec_var_cell_width, '}{'))
closing = '\\tabheadstrutfloor}\\end{minipage}} & %\n'
if (not separate and
get_numform(cell.colchar) == self.numcols - 1):
add_vdots = ''.join(('\\hspace*{\\fill}\\hspace*{\\fill}',
'\\raisebox{+1ex}{\\vdots}'))
complete.append(''.join((cell.head, leading,
cell.content, closing)))
separate.append(''.join((cell.head, leading,
'\\hspace*{\\fill}\\raisebox{+1ex}{\\vdots}',
add_vdots, '\\hspace*{\\fill}', closing)))
return (''.join((''.join(separate)[:-4], '%\n', first_row.tail)),
''.join((''.join(complete)[:-4], '%\n', first_row.tail,
first_row.addit)))
def mk_subseq_rows(self, row):
complete =[]
for cell in row.cells:
if cell.span == 0: # phantom columns prior to multicolumns
continue
leading = '{'
closing = '} & %\n'
if get_numform(cell.colchar) == self.spec_var_col: # last col
if cell.nested_minpg:
cell.content.replace('\\begin{minipage}{\\grminpgwidth}',
''.join(('\\begin{minipage}{0.8',
row.spec_var_cell_width, '}')))
leading = ''.join(('{\\tabrowstrutceil\\begin{minipage}[t]{',
row.spec_var_cell_width, '}{'))
closing = '}\\end{minipage}} & %\n'
complete.append(''.join((cell.head, leading,
cell.content, closing)))
return ''.join((''.join(complete)[:-4], '%\n', row.tail, row.addit))
# ---------------------------------------------------------------------
# end of special table treatment
# ---------------------------------------------------------------------
def page_break(self):
"Forces a page break, creating a new page"
self._backend.write('\\newpage ')
self.emit('\\newpage%\n')
def open(self, filename):
"""Opens the specified file, making sure that it has the
@ -345,11 +702,12 @@ class LaTeXDoc(BaseDoc, TextDoc):
# Use the article template, T1 font encodings, and specify
# that we should use Latin1 and unicode character encodings.
self._backend.write(_LATEX_TEMPLATE_1 % options)
self._backend.write(_LATEX_TEMPLATE)
self.emit(_LATEX_TEMPLATE_1 % options)
self.emit(_LATEX_TEMPLATE)
self.in_list = 0
self.in_table = 0
self.in_list = False
self.in_table = False
self.head_line = False
self.imagenum = 0
#Establish some local styles for the report
@ -370,8 +728,8 @@ class LaTeXDoc(BaseDoc, TextDoc):
# Is there special alignment? (default is left)
align = style.get_alignment_text()
if align == "center":
thisstyle.font_beg += "\\centerline{"
thisstyle.font_end = "}" + thisstyle.font_end
thisstyle.font_beg += "{\\centering"
thisstyle.font_end = ''.join(("\n\n}", thisstyle.font_end))
elif align == "right":
thisstyle.font_beg += "\\hfill"
@ -404,13 +762,13 @@ class LaTeXDoc(BaseDoc, TextDoc):
def close(self):
"""Clean up and close the document"""
if self.in_list:
self._backend.write('\\end{enumerate}\n')
self._backend.write('\n\\end{document}\n')
self.emit('\\end{list}\n')
self.emit('\\end{document}\n')
self._backend.close()
def end_page(self):
"""Issue a new page command"""
self._backend.write('\\newpage')
self.emit('\\newpage')
def start_paragraph(self, style_name, leader=None):
"""Paragraphs handling - A Gramps paragraph is any
@ -424,78 +782,86 @@ class LaTeXDoc(BaseDoc, TextDoc):
self.fbeg = ltxstyle.font_beg
self.fend = ltxstyle.font_end
self.indent = ltxstyle.leftIndent
self.FLindent = ltxstyle.firstLineIndent
#firstLineIndent is not used, subtract from indent.
self.indent -= self.FLindent
if self.indent == 0:
self.indent = self.FLindent
# For additional vertical space beneath title line(s)
# i.e. when the first centering ended:
if self.in_title and ltxstyle.font_beg.find('centering') == -1:
self.in_title = False
self._backend.write('\\vspace{5ex}%\n')
self._backend.write('\\inittabvars{\\grleadinglabelwidth}{0.0em}%\n')
if leader:
self._backend.write(''.join((
'\\grcalctextwidth{', leader, '}{\\grleadinglabelwidth}%\n')))
self._backend.write(
'\\inittabvars{\\grlistbacksp}{\\grleadinglabelwidth +1em}%\n')
else:
self._backend.write(
'\\inittabvars{\\grlistbacksp}{0em}%\n')
# -------------------------------------------------------------------
# Gramps presumes 'cm' as units; here '\\grbaseindent' is used
# as equivalent, set in '_LATEX_TEMPLATE' above to '3em';
# there another value might be choosen.
# -------------------------------------------------------------------
if self.indent is not None and not self.in_table:
myspace = '%scm' % str(self.indent)
self._backend.write('\\grampsindent{%s}\n' % myspace)
self.fix_indent = 1
self.emit(''.join(('\\inittabvars{'
'\\grminpgindent}{%s\\grbaseindent-\\grlistbacksp}' %
repr(self.indent), '%\n\\hspace*{\\grminpgindent}%\n',
'\\begin{minipage}[t]{\\textwidth-\\grminpgindent}',
'%\n')))
self.fix_indent = True
if leader is not None and not self.in_list:
self._backend.write('\\begin{enumerate}\n')
self.in_list = 1
if leader is not None:
# try obtaining integer
leader_1 = leader[:-1]
num = roman2arabic(leader_1)
if num == 0:
# Not roman, try arabic or fallback to 1
try:
num = int(leader_1)
except ValueError:
num = 1
self._backend.write(' \\renewcommand\\theenumi{\\arabic{enumi}}')
else:
# roman, set the case correctly
self._backend.write(' \\renewcommand\\theenumi{\\%soman{enumi}}'
% ('r' if leader_1.islower() else 'R'))
self.in_list = True
self._backend.write(''.join(('\\begin{list}{%\n', leader,
'}{%\n', '\\labelsep0.5em %\n',
'\\setlength{\\labelwidth}{\\grleadinglabelwidth}%\n',
'\\setlength{\\leftmargin}{\\grlistbacksp}}%\n',
'\\item%\n')))
self._backend.write(' \\setcounter{enumi}{%d} ' % num)
self._backend.write(' \\addtocounter{enumi}{-1}\n')
self._backend.write(' \\item ')
if leader is None and not self.in_list and not self.in_table:
self._backend.write('\n')
self._backend.write('%s ' % self.fbeg)
if leader is None:
self.emit('\n')
self.emit('%s ' % self.fbeg)
def end_paragraph(self):
"""End the current paragraph"""
newline = '\ \\newline\n'
newline = '%\n\n'
if self.in_list:
self.in_list = 0
self._backend.write('\n\\end{enumerate}\n')
self.in_list = False
self.emit('\n\\end{list}\n')
newline = ''
elif self.in_table:
newline = ''
elif self.in_table:
newline = ('')
self._backend.write('%s%s' % (self.fend, newline))
if self.fix_indent == 1:
self.fix_indent = 0
self._backend.write('\\grampsindent{0cm}\n')
self.emit('%s%s' % (self.fend, newline))
if self.fix_indent:
self.emit('\\end{minipage}\\parindent0em%\n\n')
self.fix_indent = False
def start_bold(self):
"""Bold face"""
self._backend.write('\\textbf{')
self.emit('\\textbf{')
def end_bold(self):
"""End bold face"""
self._backend.write('}')
self.emit('}')
def start_superscript(self):
self._backend.write('\\textsuperscript{')
self.emit('\\textsuperscript{')
def end_superscript(self):
self._backend.write('}')
self.emit('}')
def start_table(self, name,style_name):
"""Begin new table"""
self.in_table = 1
self.in_table = True
self.currow = 0
# We need to know a priori how many columns are in this table
@ -503,33 +869,41 @@ class LaTeXDoc(BaseDoc, TextDoc):
self.tblstyle = styles.get_table_style(style_name)
self.numcols = self.tblstyle.get_columns()
# early test of column count:
z = get_charform(self.numcols-1)
tblfmt = '*{%d}{l}' % self.numcols
self._backend.write('\n\n\\begin{longtable}[l]{%s}\n' % tblfmt)
self.emit(
'\\begin{longtable}[l]{%s}\n' % (tblfmt), TAB_BEG)
def end_table(self):
"""Close the table environment"""
self.in_table = 0
# Create a paragraph separation below the table.
self._backend.write('\\end{longtable}\n\\par\n')
self.emit(
'%\n\\end{longtable}%\n\\par%\n', TAB_END)
self.in_table = False
def start_row(self):
"""Begin a new row"""
self.emit('', ROW_BEG)
# doline/skipfirst are flags for adding hor. rules
self.doline = 0
self.skipfirst = 0
self.doline = False
self.skipfirst = False
self.curcol = 0
self.currow = self.currow + 1
def end_row(self):
"""End the row (new line)"""
self._backend.write('\\\\ ')
if self.doline == 1:
if self.skipfirst == 1:
self._backend.write('\\cline{2-%d}\n' % self.numcols)
self.emit('\\\\ ')
if self.doline:
if self.skipfirst:
self.emit(''.join((('\\cline{2-%d}' %
self.numcols), '%\n')), ROW_END)
else:
self._backend.write('\\hline \\\\ \n')
self.emit('\\hline %\n', ROW_END)
else:
self._backend.write('\n')
self.emit('%\n', ROW_END)
self.emit('%\n')
def start_cell(self,style_name,span=1):
"""Add an entry to the table.
@ -540,40 +914,51 @@ class LaTeXDoc(BaseDoc, TextDoc):
styles = self.get_style_sheet()
self.cstyle = styles.get_cell_style(style_name)
self.lborder = self.cstyle.get_left_border()
self.rborder = self.cstyle.get_right_border()
self.bborder = self.cstyle.get_bottom_border()
self.tborder = self.cstyle.get_top_border()
self.llist = self.cstyle.get_longlist()
if self.llist == 1:
cellfmt = "p{\linewidth-3cm}"
else:
# ------------------------------------------------------------------
# begin special modification for boolean values
# values imported here are used for test '==1' and '!=0'. To get
# local boolean values the tests are now transfered to the import lines
# ------------------------------------------------------------------
self.lborder = 1 == self.cstyle.get_left_border()
self.rborder = 1 == self.cstyle.get_right_border()
self.bborder = 1 == self.cstyle.get_bottom_border()
self.tborder = 0 != self.cstyle.get_top_border()
# self.llist not needed, LaTeX has to decide on its own
# wether a line fits or not. See comment in calc_latex_widths() above.
# self.llist = 1 == self.cstyle.get_longlist()
cellfmt = "l"
# Account for vertical rules
if self.lborder == 1:
if self.lborder:
cellfmt = '|' + cellfmt
if self.rborder == 1:
if self.rborder:
cellfmt = cellfmt + '|'
# and Horizontal rules
if self.bborder == 1:
self.doline = 1
if self.bborder:
self.doline = True
elif self.curcol == 1:
self.skipfirst = 1
self.skipfirst = True
if self.tborder:
self.head_line = True
# ------------------------------------------------------------------
# end special modification for boolean values
# ------------------------------------------------------------------
self.emit(
''.join(('%\n', '\\multicolumn{%d}{%s}' % (span, cellfmt))),
CELL_BEG, span)
if self.tborder != 0:
self._backend.write('\\hline\n')
self._backend.write ('\\multicolumn{%d}{%s}{' % (span,cellfmt))
def end_cell(self):
"""Prepares for next cell"""
self._backend.write('} ')
if self.curcol < self.numcols:
self._backend.write('& ')
self.emit('', CELL_END)
def add_media_object(self, name, pos, x, y, alt='', style_name=None, crop=None):
def add_media_object(self, name, pos, x, y, alt='',
style_name=None, crop=None):
"""Add photo to report"""
return
@ -586,27 +971,34 @@ class LaTeXDoc(BaseDoc, TextDoc):
picf = self.filename[:-4] + '_img' + str(self.imagenum) + '.eps'
pic.eps_convert(picf)
# -------------------------------------------------------------------
# x and y will be maximum width OR height in units of cm
mysize = 'width=%dcm, height=%dcm,keepaspectratio' % (x,y)
# here '\\grbaseindent' is used as equivalent, see above
# -------------------------------------------------------------------
mysize = ''.join(('width=%d\\grbaseindent, ',
'height=%d\\grbaseindent,keepaspectratio' % (x,y)))
if pos == "right":
self._backend.write('\\hfill\\includegraphics[%s]{%s}\n' % (mysize,picf))
self.emit((
'\\hfill\\includegraphics[%s]{%s}\n' % (mysize,picf)))
elif pos == "left":
self._backend.write('\\includegraphics[%s]{%s}\\hfill\n' % (mysize,picf))
self.emit((
'\\includegraphics[%s]{%s}\\hfill\n' % (mysize,picf)))
else:
self._backend.write('\\centerline{\\includegraphics[%s]{%s}}\n' % (mysize,picf))
self.emit((
'\\centering{\\includegraphics[%s]{%s}}\n' % (mysize,picf)))
def write_text(self,text,mark=None,links=False):
"""Write the text to the file"""
if text == '\n':
text = '\\newline\n'
text = ''
text = latexescape(text)
if links == True:
text = re.sub(URL_PATTERN, _CLICKABLE, text)
#hard coded replace of the underline used for missing names/data
text = text.replace('\\_'*13, '\\underline{\hspace{3cm}}')
self._backend.write(text)
text = text.replace('\\_'*13, '\\underline{\hspace{3\\grbaseindent}}')
self.emit(text + ' ')
def write_styled_note(self, styledtext, format, style_name,
contains_html=False, links=False):
@ -618,7 +1010,7 @@ class LaTeXDoc(BaseDoc, TextDoc):
contains_html: bool, the backend should not check if html is present.
If contains_html=True, then the textdoc is free to handle that in
some way. Eg, a textdoc could remove all tags, or could make sure
a link is clickable. LatexDoc ignores notes that contain html
a link is clickable. self ignores notes that contain html
links: bool, make URLs clickable if True
"""
if contains_html:
@ -640,18 +1032,21 @@ class LaTeXDoc(BaseDoc, TextDoc):
# A good solution for this ???
# A quick solution: create a minipage for the note and add that always
# hoping that the user will have left sufficient room for the page
self._backend.write("\\begin{minipage}{{0.8\\linewidth}}\n")
#
# now solved by postprocessing in emit()
# by explicitely calculating column widths
#
if format:
self.start_paragraph(style_name)
self._backend.write(markuptext)
self.emit(markuptext)
self.end_paragraph()
#preformatted finished, go back to normal escape function
self._backend.setescape(False)
else:
for line in markuptext.split('\n\n'):
for line in markuptext.split('%\n%\n'):
self.start_paragraph(style_name)
for realline in line.split('\n'):
self._backend.write(realline)
self._backend.write("\\newline\n")
self.emit(realline)
self.emit("~\\newline%\n")
self.end_paragraph()
self._backend.write("\n\\vspace*{0.5cm} \n\\end{minipage}\n\n")