0004605: Multiple improvements for LaTeX output (patch from Harald Rosemann)
svn: r18035
This commit is contained in:
parent
fe4df17781
commit
603238aeb0
@ -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
|
||||
@ -38,7 +41,7 @@ from gen.ggettext import gettext as _
|
||||
from bisect import bisect
|
||||
import re
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#----------------------------------------------------------------------- -
|
||||
#
|
||||
# gramps modules
|
||||
#
|
||||
@ -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,15 +762,15 @@ 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):
|
||||
def start_paragraph(self, style_name, leader=None):
|
||||
"""Paragraphs handling - A Gramps paragraph is any
|
||||
single body of text from a single word to several sentences.
|
||||
We assume a linebreak at the end of each paragraph."""
|
||||
@ -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:
|
||||
cellfmt = "l"
|
||||
# ------------------------------------------------------------------
|
||||
# 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")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user