Files
gramps/src/plugins/docgen/LaTeXDoc.py
2012-11-18 03:31:41 +00:00

1289 lines
47 KiB
Python

#
# -*- coding: utf-8 -*-
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2007-2009 Brian G. Matherly
# Copyright (C) 2008 Raphael Ackermann
# 2002-2003 Donald A. Peterson
# 2003 Alex Roitman
# 2009 Benny Malengier
# 2010 Peter Landgren
# Copyright (C) 2011 Adam Stein <adam@csh.rit.edu>
# 2011-2012 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
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"""LaTeX document generator"""
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gen.ggettext import gettext as _
from bisect import bisect
import re, os, sys
#----------------------------------------------------------------------- -
#
# gramps modules
#
#------------------------------------------------------------------------
from gen.plug.docgen import BaseDoc, TextDoc, PAPER_LANDSCAPE, FONT_SANS_SERIF, URL_PATTERN
from gen.plug.docbackend import DocBackend
import Image
import Errors
import Utils
_CLICKABLE = r'''\url{\1}'''
#------------------------------------------------------------------------
#
# Special settings for LaTeX output
#
#------------------------------------------------------------------------
# For an interim mark e.g. for an intended linebreak I use a special pattern.
# It shouldn't interfere with normal text. In LaTeX charackter '&' is used
# 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:
SEPARATION_PAT = '&&'
#------------------------------------------------------------------------
#
# Latex Article Template
#
#------------------------------------------------------------------------
_LATEX_TEMPLATE_1 = '\\documentclass[%s]{article}\n'
_LATEX_TEMPLATE = '''%
%
\\usepackage[T1]{fontenc}%
%
% We use latin1 encoding at a minimum by default.
% GRAMPS uses unicode UTF-8 encoding for its
% international support. LaTeX can deal gracefully
% with unicode encoding by using the ucs style invoked
% when utf8 is specified as an option to the inputenc
% package. This package is included by default in some
% installations, but not in others, so we do not make it
% the default. Uncomment the first line if you wish to use it
% (If you do not have ucs.sty, you may obtain it from
% http://www.tug.org/tex-archive/macros/latex/contrib/supported/unicode/)
%
%\\usepackage[latin1]{inputenc}%
\\usepackage[latin1,utf8]{inputenc}%
\\usepackage{graphicx}% Extended graphics support
\\usepackage{longtable}% For multi-page tables
\\usepackage{calc}% For some calculations
\\usepackage{ifthen}% For table width calculations
\\usepackage{ragged2e}% For left aligning with hyphenation
\\usepackage{wrapfig}% wrap pictures in text
%
% Depending on your LaTeX installation, the margins may be too
% narrow. This can be corrected by uncommenting the following
% two lines and adjusting the width appropriately. The example
% removes 0.5in from each margin. (Adds 1 inch to the text)
%\\addtolength{\\oddsidemargin}{-0.5in}%
%\\addtolength{\\textwidth}{1.0in}%
%
% 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, 2.5em is right for 12pt
% take one of three possibilities or modify to your taste:
\\newlength{\\grbaseindent}%
%\\setlength{\\grbaseindent}{3.0em}%
\\setlength{\\grbaseindent}{2.5em}%
%\\setlength{\\grbaseindent}{2.0em}%
%
%
% -------------------------------------------------------------
% New lengths, counters and commands for calculations in tables
% -------------------------------------------------------------
%
\\newlength{\\grtabwidth}%
\\newlength{\\grtabprepos}%
\\newlength{\\grreqwidth}%
\\newlength{\\grtempwd}%
\\newlength{\\grmaxwidth}%
\\newlength{\\grprorated}%
\\newlength{\\grxwd}%
\\newlength{\\grwidthused}%
\\newlength{\\grreduce}%
\\newlength{\\grcurcolend}%
\\newlength{\\grspanwidth}%
\\newlength{\\grleadlabelwidth}%
\\newlength{\\grminpgindent}%
\\newlength{\\grlistbacksp}%
\\newlength{\\grpictsize}%
\\newlength{\\grmaxpictsize}%
\\newlength{\\grtextsize}%
\\newlength{\\grmaxtextsize}%
\\newcounter{grtofixcnt}%
\\newcounter{grxwdcolcnt}%
%
%
\\newcommand{\\grinitlength}[2]{%
\\ifthenelse{\\isundefined{#1}}%
{\\newlength{#1}}{}%
\\setlength{#1}{#2}%
}%
%
\\newcommand{\\grinittab}[2]{% #1: tabwidth, #2 = 1.0/anz-cols
\\setlength{\\grtabwidth}{#1}%
\\setlength{\\grprorated}{#2\\grtabwidth}%
\\setlength{\\grwidthused}{0em}%
\\setlength{\\grreqwidth}{0em}%
\\setlength{\\grmaxwidth }{0em}%
\\setlength{\\grxwd}{0em}%
\\setlength{\\grtempwd}{0em}%
\\setlength{\\grpictsize}{0em}%
\\setlength{\\grmaxpictsize}{0em}%
\\setlength{\\grtextsize}{0em}%
\\setlength{\\grmaxtextsize}{0em}%
\\setlength{\\grcurcolend}{0em}%
\\setcounter{grxwdcolcnt}{0}%
\\setcounter{grtofixcnt}{0}% number of wide cols%
\\grinitlength{\\grcolbega}{0em}% beg of first col
}%
%
\\newcommand{\\grmaxvaltofirst}[2]{%
\\ifthenelse{\\lengthtest{#1 < #2}}%
{\\setlength{#1}{#2}}{}%
}%
%
\\newcommand{\\grsetreqfull}{%
\\grmaxvaltofirst{\\grmaxpictsize}{\\grpictsize}%
\\grmaxvaltofirst{\\grmaxtextsize}{\\grtextsize}%
}%
%
\\newcommand{\\grsetreqpart}[1]{%
\\addtolength{\\grtextsize}{#1 - \\grcurcolend}%
\\addtolength{\\grpictsize}{#1 - \\grcurcolend}%
\\grsetreqfull%
}%
%
\\newcommand{\\grdividelength}{%
\\setlength{\\grtempwd}{\\grtabwidth - \\grwidthused}%
% rough division of lengths:
% if 0 < #1 <= 10: \\grxwd = ~\\grtempwd / grtofixcnt
% otherwise: \\grxwd = \\grprorated
\\ifthenelse{\\value{grtofixcnt} > 0}%
{\\ifthenelse{\\value{grtofixcnt}=1}%
{\\setlength{\\grxwd}{\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=2}
{\\setlength{\\grxwd}{0.5\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=3}
{\\setlength{\\grxwd}{0.333\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=4}
{\\setlength{\\grxwd}{0.25\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=5}
{\\setlength{\\grxwd}{0.2\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=6}
{\\setlength{\\grxwd}{0.166\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=7}
{\\setlength{\\grxwd}{0.143\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=8}
{\\setlength{\\grxwd}{0.125\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=9}
{\\setlength{\\grxwd}{0.111\\grtempwd}}{%
\\ifthenelse{\\value{grtofixcnt}=10}
{\\setlength{\\grxwd}{0.1\\grtempwd}}{%
\\setlength{\\grxwd}{\\grprorated}% give up, take \\grprorated%
}}}}}}}}}}%
\\setlength{\\grreduce}{0em}%
}{\\setlength{\\grxwd}{0em}}%
}%
%
\\newcommand{\\grtextneedwidth}[1]{%
\\settowidth{\\grtempwd}{#1}%
\\grmaxvaltofirst{\\grtextsize}{\\grtempwd}%
}%
%
\\newcommand{\\grcolsfirstfix}[5]{%
\\grinitlength{#1}{\\grcurcolend}%
\\grinitlength{#3}{0em}%
\\grinitlength{#4}{\\grmaxpictsize}%
\\grinitlength{#5}{\\grmaxtextsize}%
\\grinitlength{#2}{#5}%
\\grmaxvaltofirst{#2}{#4}%
\\addtolength{#2}{2\\tabcolsep}%
\\grmaxvaltofirst{\\grmaxwidth}{#2}%
\\ifthenelse{\\lengthtest{#2 < #4} \\or \\lengthtest{#2 < \\grprorated}}%
{ \\setlength{#3}{#2}%
\\addtolength{\\grwidthused}{#2} }%
{ \\stepcounter{grtofixcnt} }%
\\addtolength{\\grcurcolend}{#2}%
}%
%
\\newcommand{\\grcolssecondfix}[4]{%
\\ifthenelse{\\lengthtest{\\grcurcolend < \\grtabwidth}}%
{ \\setlength{#3}{#2} }%
{ \\addtolength{#1}{-\\grreduce}%
\\ifthenelse{\\lengthtest{#2 = \\grmaxwidth}}%
{ \\stepcounter{grxwdcolcnt}}%
{ \\ifthenelse{\\lengthtest{#3 = 0em} \\and %
\\lengthtest{#4 > 0em}}%
{ \\setlength{\\grtempwd}{#4}%
\\grmaxvaltofirst{\\grtempwd}{\\grxwd}%
\\addtolength{\\grreduce}{#2 - \\grtempwd}%
\\setlength{#2}{\\grtempwd}%
\\addtolength{\\grwidthused}{#2}%
\\addtocounter{grtofixcnt}{-1}%
\\setlength{#3}{#2}%
}{}%
}%
}%
}%
%
\\newcommand{\\grcolsthirdfix}[3]{%
\\ifthenelse{\\lengthtest{\\grcurcolend < \\grtabwidth}}%
{}{ \\addtolength{#1}{-\\grreduce}%
\\ifthenelse{\\lengthtest{#3 = 0em} \\and %
\\lengthtest{#2 < \\grmaxwidth}}%
{ \\ifthenelse{\\lengthtest{#2 < 0.5\\grmaxwidth}}%
{ \\setlength{\\grtempwd}{0.5\\grxwd}%
\\grmaxvaltofirst{\\grtempwd}{0.7\\grprorated}}%
{ \\setlength{\\grtempwd}{\\grxwd}}%
\\addtolength{\\grreduce}{#2 - \\grtempwd}%
\\setlength{#2}{\\grtempwd}%
\\addtolength{\\grwidthused}{#2}%
\\addtocounter{grtofixcnt}{-1}%
\\setlength{#3}{#2}%
}{}%
}%
}%
%
\\newcommand{\\grcolsfourthfix}[3]{%
\\ifthenelse{\\lengthtest{\\grcurcolend < \\grtabwidth}}%
{}{ \\addtolength{#1}{-\\grreduce}%
\\ifthenelse{\\lengthtest{#3 = 0em}}%
{ \\addtolength{\\grreduce}{#2 - \\grxwd}%
\\setlength{#2}{\\grxwd}%
\\setlength{#3}{#2}%
}{}%
}%
}%
%
\\newcommand{\\grgetspanwidth}[4]{%
\\grinitlength{#1}{#3 - #2 + #4}%
}%
%
\\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}{2.9ex}}%
\\newcommand{\\tabrowstrutfloor}{%
\\rule[-0.1ex]{0.00em}{2.0ex}}%
%
\\newcommand{\\grempty}[1]{}%
%
\\newcommand{\\graddvdots}[1]{%
\\hspace*{\\fill}\\hspace*{\\fill}\\raisebox{#1}{\\vdots}%
}%
%
\\newcommand{\\grtabpgbreak}[4]{%
#1 { \\parbox[t]{ #2 - 2\\tabcolsep}{\\tabheadstrutceil\\hspace*{\\fill}%
\\raisebox{#4}{\\vdots} #3{#4} \\hspace*{\\fill}\\tabheadstrutfloor}}%
}%
%
\\newcommand{\\grcolpart}[3]{%
#1 { \\parbox[t]{ #2 - 2\\tabcolsep}%
{\\tabrowstrutceil #3~\\\\[-1.6ex]\\tabrowstrutfloor}}%
}%
%
\\newcommand{\\grminpghead}[2]{%
\\setlength{\\grminpgindent}{#1\\grbaseindent-\\grlistbacksp}%
\\hspace*{\\grminpgindent}%
\\ifthenelse{\\not \\lengthtest{#2em > 0em}}%
{\\begin{minipage}[t]{\\textwidth -\\grminpgindent}}%
{\\begin{minipage}[t]{\\textwidth -\\grminpgindent%
-#2\\grbaseindent -4\\tabcolsep}}%
}%
%
\\newcommand{\\grminpgtail}{%
\\end{minipage}\\parindent0em%
}%
%
\\newcommand{\\grlisthead}[1]{%
\\begin{list}{#1}%
{ \\setlength{\\labelsep}{0.5em}%
\\setlength{\\labelwidth}{\\grleadlabelwidth}%
\\setlength{\\leftmargin}{\\grlistbacksp}%
}\\item%
}%
%
\\newcommand{\\grlisttail}{%
\\end{list}%
}%
%
\\newcommand{\\grprepleader}[1]{%
\\settowidth{\\grtempwd}{#1}%
\\ifthenelse{\\lengthtest{\\grtempwd > \\grleadlabelwidth}}%
{ \\setlength{\\grleadlabelwidth}{\\grtempwd}}{}%
\\setlength{\\grlistbacksp}{\\grleadlabelwidth + 1.0em}%
}%
%
\\newcommand{\\grprepnoleader}{%
\\setlength{\\grleadlabelwidth}{0em}%
\\setlength{\\grlistbacksp}{0em}%
}%
%
\\newcommand{\\grmkpicture}[4]{%
\\begin{wrapfigure}{r}{#2\\grbaseindent}%
\\vspace{-6ex}%
\\begin{center}%
\\includegraphics[%
width= #2\\grbaseindent,%
height= #3\\grbaseindent,%
keepaspectratio]%
{#1}\\\\%
{\\RaggedRight\\footnotesize#4}%
\\end{center}%
\\end{wrapfigure}%
\\settowidth{\\grtempwd}{\\footnotesize#4}%
\\setlength{\\grxwd}{#2\\grbaseindent}%
\\ifthenelse{\\lengthtest{\\grtempwd < 0.7\\grxwd}}%
{\\setlength{\\grxwd}{1ex}}{%
\\ifthenelse{\\lengthtest{\\grtempwd < 1.2\\grxwd}}%
{\\setlength{\\grxwd}{2ex}}{%
\\ifthenelse{\\lengthtest{\\grtempwd < 1.8\\grxwd}}%
{\\setlength{\\grxwd}{6ex}}{%
\\ifthenelse{\\lengthtest{\\grtempwd < 2.0\\grxwd}}%
{\\setlength{\\grxwd}{10ex}}{%
\\setlength{\\grxwd}{12ex}}%
}}}%
\\setlength{\\grtempwd}{#3\\grbaseindent + \\grxwd}%
\\rule[-\\grtempwd]{0pt}{\\grtempwd}%
\\setlength{\\grtabprepos}{-\\grtempwd}%
}%
%
%
\\begin{document}%
'''
#------------------------------------------------------------------------
#
# Font size table and function
#
#------------------------------------------------------------------------
# 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
# from 6-7 points are 'script', etc. and fonts greater than or equal to 22
# are considered 'Huge'. Note that fonts from 12-13 points are not given a
# Latex font size name but are considered "normal."
_FONT_SIZES = [6, 8, 10, 12, 14, 16, 18, 20, 22]
_FONT_NAMES = ['tiny', 'scriptsize', 'footnotesize', 'small', '',
'large', 'Large', 'LARGE', 'huge', 'Huge']
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) = range(7)
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')
#------------------------------------------
# row_alph_counter = str_incr(MULTCOL_COUNT_BASE)
#
# 'aaa' is sufficient for up to 17576 multicolumns in each table;
# do you need more?
# uncomment one of the two lines
MULTCOL_COUNT_BASE = 'aaa'
# MULTCOL_COUNT_BASE = 'aaaa'
#------------------------------------------
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, content):
self.colchar = colchar
self.span = span
self.head = head
self.content = content
class Tab_Row():
def __init__(self):
self.cells =[]
self.tail = ''
self.addit = '' # for: \\hline, \\cline{}
class Tab_Mem():
def __init__(self, head):
self.head = head
self.tail =''
self.rows =[]
#------------------------------------------------------------------------
#
# Functions for docbackend
#
#------------------------------------------------------------------------
def latexescape(text):
"""
change text in text that latex shows correctly
special characters: \& \$ \% \# \_ \{ \}
"""
text = text.replace('&','\\&')
text = text.replace('$','\\$')
text = text.replace('%','\\%')
text = text.replace('#','\\#')
text = text.replace('_','\\_')
text = text.replace('{','\\{')
text = text.replace('}','\\}')
# replace character unknown to LaTeX
text = text.replace('','$\\longrightarrow$')
return text
def latexescapeverbatim(text):
"""
change text in text that latex shows correctly respecting whitespace
special characters: \& \$ \% \# \_ \{ \}
Now also make sure space and newline is respected
"""
text = text.replace('&', '\\&')
text = text.replace('$', '\\$')
text = text.replace('%', '\\%')
text = text.replace('#', '\\#')
text = text.replace('_', '\\_')
text = text.replace('{', '\\{')
text = text.replace('}', '\\}')
text = text.replace(' ', '\\ ')
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.1\\grbaseindent}\\ ')
# replace character unknown to LaTeX
text = text.replace('','$\\longrightarrow$')
return text
#------------------------------------------------------------------------
#
# Document Backend class for cairo docs
#
#------------------------------------------------------------------------
class LateXBackend(DocBackend):
"""
Implementation of docbackend for latex docs.
File and File format management for latex docs
"""
# overwrite base class attributes, they become static var of LaTeXDoc
SUPPORTED_MARKUP = [
DocBackend.BOLD,
DocBackend.ITALIC,
DocBackend.UNDERLINE,
DocBackend.FONTSIZE,
DocBackend.FONTFACE,
DocBackend.SUPERSCRIPT ]
STYLETAG_MARKUP = {
DocBackend.BOLD : ("\\textbf{", "}"),
DocBackend.ITALIC : ("\\textit{", "}"),
DocBackend.UNDERLINE : ("\\underline{", "}"),
DocBackend.SUPERSCRIPT : ("\\textsuperscript{", "}"),
}
ESCAPE_FUNC = lambda x: latexescape
def setescape(self, preformatted=False):
"""
Latex needs two different escape functions depending on the type.
This function allows to switch the escape function
"""
if not preformatted:
LateXBackend.ESCAPE_FUNC = lambda x: latexescape
else:
LateXBackend.ESCAPE_FUNC = lambda x: latexescapeverbatim
def _create_xmltag(self, type, value):
"""
overwrites the method in DocBackend.
creates the latex tags needed for non bool style types we support:
FONTSIZE : use different \large denomination based
on size
: very basic, in mono in the font face
then we use {\ttfamily }
"""
if type not in self.SUPPORTED_MARKUP:
return None
elif type == DocBackend.FONTSIZE:
#translate size in point to something LaTeX can work with
fontsize = map_font_size(value)
if fontsize:
return ("{\\" + fontsize + ' ', "}")
else:
return ("", "")
elif type == DocBackend.FONTFACE:
if 'MONO' in value.upper():
return ("{\\ttfamily ", "}")
elif 'ROMAN' in value.upper():
return ("{\\rmfamily ", "}")
return None
def _checkfilename(self):
"""
Check to make sure filename satisfies the standards for this filetype
"""
if not self._filename.endswith(".tex"):
self._filename = self._filename + ".tex"
#------------------------------------------------------------------------
#
# Paragraph Handling
#
#------------------------------------------------------------------------
class TexFont(object):
def __init__(self, style=None):
if style:
self.font_beg = style.font_beg
self.font_end = style.font_end
self.leftIndent = style.left_indent
self.firstLineIndent = style.firstLineIndent
else:
self.font_beg = ""
self.font_end = ""
self.leftIndent = ""
self.firstLineIndent = ""
#------------------------------------------------------------------
#
# LaTeXDoc
#
#------------------------------------------------------------------
class LaTeXDoc(BaseDoc, TextDoc):
"""LaTeX document interface class. Derived from BaseDoc"""
# ---------------------------------------------------------------
# some additional variables
# ---------------------------------------------------------------
in_table = False
in_multrow_cell = False # for tab-strukt: cols of rows
pict = ''
pict_in_table = False
pict_width = 0
pict_height = 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)
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).strip()
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.tabrow.cells.append(self.tabcell)
self.textmem = []
elif tab_state == ROW_BEG:
self.tabrow = Tab_Row()
elif tab_state == ROW_END:
self.tabrow.addit = text # text: \\hline, \\cline{}
self.tabrow.tail = ''.join(self.textmem) # \\\\ row-termination
if self.in_multrow_cell: # cols of rows: convert to rows of cols
self.repack_row()
else:
self.tabmem.rows.append(self.tabrow)
elif tab_state == TAB_BEG: # text: \\begin{longtable}[l]{
self._backend.write(''.join(('\\grinittab{\\textwidth}{',
repr(1.0/self.numcols), '}%\n')))
self.tabmem = Tab_Mem(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 repack_row(self):
"""
Transpose contents contained in a row of cols of cells
to rows of cells with corresponding contents.
Cols of the mult-row-cell are ended by SEPARATION_PAT
"""
# if last col empty: delete
if self.tabrow.cells[-1].content == '':
del self.tabrow.cells[-1]
self.numcols -= 1
# extract cell.contents
bare_contents = [cell.content.strip(SEPARATION_PAT).replace(
'\n', '').split(SEPARATION_PAT)
for cell in self.tabrow.cells]
# mk equal length & transpose
num_new_rows = max([len(mult_row_cont)
for mult_row_cont in bare_contents])
cols_equ_len = []
for mrc in bare_contents:
for i in range(num_new_rows - len(mrc)):
mrc.append('')
cols_equ_len.append(mrc)
transp_cont = zip(*cols_equ_len)
# picts? extract
first_cell, last_cell = (0, self.numcols)
if self.pict_in_table:
if transp_cont[0][-1].startswith('\\grmkpicture'):
self.pict = transp_cont[0][-1]
last_cell -= 1
self.numcols -= 1
self._backend.write(''.join(('\\addtolength{\\grtabwidth}{-',
repr(self.pict_width), '\\grbaseindent -2\\tabcolsep}%\n')))
self.pict_in_table = False
# new row-col structure
for row in range(num_new_rows):
new_row = Tab_Row()
for i in range(first_cell, last_cell):
new_cell = Tab_Cell(get_charform(i + first_cell),
self.tabrow.cells[i].span, self.tabrow.cells[i].head,
transp_cont[row][i + first_cell])
new_row.cells.append(new_cell)
new_row.tail = self.tabrow.tail
new_row.addit = ''
self.tabmem.rows.append(new_row)
self.tabmem.rows[-1].addit = self.tabrow.addit
self.in_multrow_cell = False
return
def calc_latex_widths(self):
"""
Control width settings in latex table construction
Evaluations are set up here and passed to LaTeX
to calculate required and to fix suitable widths.
??? Can all this be done exclusively in TeX? Don't know how.
"""
tabcol_chars = []
for col_num in range(self.numcols):
col_char = get_charform(col_num)
tabcol_chars.append(col_char)
for row in self.tabmem.rows:
cell = row.cells[col_num]
if cell.span == 0:
continue
if cell.content.startswith('\\grmkpicture'):
self._backend.write(''.join(('\\setlength{\\grpictsize}{',
self.pict_width, '\\grbaseindent}%\n')))
else:
for part in cell.content.split(SEPARATION_PAT):
self._backend.write(''.join(('\\grtextneedwidth{',
part, '}%\n')))
row.cells[col_num].content = cell.content.replace(
SEPARATION_PAT, '~\\newline \n')
if cell.span == 1:
self._backend.write(''.join(('\\grsetreqfull%\n')))
elif cell.span > 1:
self._backend.write(''.join(('\\grsetreqpart{\\grcolbeg',
get_charform(get_numform(cell.colchar) - cell.span +1),
'}%\n')))
self._backend.write(''.join(('\\grcolsfirstfix',
' {\\grcolbeg', col_char, '}{\\grtempwidth', col_char,
'}{\\grfinalwidth', col_char, '}{\\grpictreq', col_char,
'}{\\grtextreq', col_char, '}%\n')))
self._backend.write(''.join(('\\grdividelength%\n')))
for col_char in tabcol_chars:
self._backend.write(''.join(('\\grcolssecondfix',
' {\\grcolbeg', col_char, '}{\\grtempwidth', col_char,
'}{\\grfinalwidth', col_char, '}{\\grpictreq', col_char,
'}%\n')))
self._backend.write(''.join(('\\grdividelength%\n')))
for col_char in tabcol_chars:
self._backend.write(''.join(('\\grcolsthirdfix',
' {\\grcolbeg', col_char, '}{\\grtempwidth', col_char,
'}{\\grfinalwidth', col_char, '}%\n')))
self._backend.write(''.join(('\\grdividelength%\n')))
for col_char in tabcol_chars:
self._backend.write(''.join(('\\grcolsfourthfix',
' {\\grcolbeg', col_char, '}{\\grtempwidth', col_char,
'}{\\grfinalwidth', col_char, '}%\n')))
self.multcol_alph_counter = str_incr(MULTCOL_COUNT_BASE)
for row in self.tabmem.rows:
for cell in row.cells:
if cell.span > 1:
multcol_alph_id = self.multcol_alph_counter.next()
self._backend.write(''.join(('\\grgetspanwidth{',
'\\grspanwidth', multcol_alph_id,
'}{\\grcolbeg', get_charform(get_numform(cell.colchar)-
cell.span + 1),
'}{\\grcolbeg', cell.colchar,
'}{\\grtempwidth', cell.colchar,
'}%\n')))
def write_table(self):
# Choosing RaggedRight (with hyphenation) in table and
# provide manually adjusting of column widths
self._backend.write(''.join((
'%\n', self.pict,
'%\n%\n',
'% ==> Comment out one of the two lines ',
'by a leading "%" (first position)\n',
'{ \\RaggedRight% left align with hyphenation in table \n',
'%{% no left align in table \n%\n',
'% ==> You may add pos or neg values ',
'to the following ', repr(self.numcols), ' column widths %\n')))
for col_num in range(self.numcols):
self._backend.write(''.join(('\\addtolength{\\grtempwidth',
get_charform(col_num), '}{+0.0cm}%\n')))
self._backend.write('% === %\n')
# adjust & open table':
if self.pict:
self._backend.write(''.join(('%\n\\vspace{\\grtabprepos}%\n',
'\\setlength{\\grtabprepos}{0ex}%\n')))
self.pict = ''
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
self.multcol_alph_counter = str_incr(MULTCOL_COUNT_BASE)
splitting_row = self.mk_splitting_row(self.tabmem.rows[FIRST_ROW])
self.multcol_alph_counter = str_incr(MULTCOL_COUNT_BASE)
complete_row = self.mk_complete_row(self.tabmem.rows[FIRST_ROW])
self._backend.write(splitting_row)
self._backend.write('\\endhead%\n')
self._backend.write(splitting_row.replace('{+2ex}', '{-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')
# hand over subsequent rows
for row in self.tabmem.rows[SUBSEQ_ROW:]:
self._backend.write(self.mk_complete_row(row))
# close table by '\\end{longtable}', end '{\\RaggedRight' or '{' by '}'
self._backend.write(''.join((''.join(self.tabmem.tail), '}%\n\n')))
def mk_splitting_row(self, row):
splitting =[]
add_vdots = '\\grempty'
for cell in row.cells:
if cell.span == 0:
continue
if (not splitting and
get_numform(cell.colchar) == self.numcols - 1):
add_vdots = '\\graddvdots'
if cell.span == 1:
cell_width = ''.join(('\\grtempwidth', cell.colchar))
else:
cell_width = ''.join(('\\grspanwidth',
self.multcol_alph_counter.next()))
splitting.append(''.join(('\\grtabpgbreak{', cell.head, '}{',
cell_width, '}{', add_vdots, '}{+2ex}%\n')))
return ''.join((' & '.join(splitting), '%\n', row.tail))
def mk_complete_row(self, row):
complete =[]
for cell in row.cells:
if cell.span == 0:
continue
elif cell.span == 1:
cell_width = ''.join(('\\grtempwidth', cell.colchar))
else:
cell_width = ''.join(('\\grspanwidth',
self.multcol_alph_counter.next()))
complete.append(''.join(('\\grcolpart{%\n ', cell.head,
'}{%\n', cell_width, '}{%\n ', cell.content, '%\n}%\n')))
return ''.join((' & '.join(complete), '%\n', row.tail, row.addit))
# ---------------------------------------------------------------------
# end of special table treatment
# ---------------------------------------------------------------------
def page_break(self):
"Forces a page break, creating a new page"
self.emit('\\newpage%\n')
def open(self, filename):
"""Opens the specified file, making sure that it has the
extension of .tex"""
self._backend = LateXBackend(filename)
self._backend.open()
# Font size control seems to be limited. For now, ignore
# any style constraints, and use 12pt has the default
options = "12pt"
if self.paper.get_orientation() == PAPER_LANDSCAPE:
options = options + ",landscape"
# Paper selections are somewhat limited on a stock installation.
# If the user picks something not listed here, we'll just accept
# the default of the user's LaTeX installation (usually letter).
paper_name = self.paper.get_size().get_name().lower()
if paper_name in ["a4", "a5", "legal", "letter"]:
options += ',' + paper_name + 'paper'
# Use the article template, T1 font encodings, and specify
# that we should use Latin1 and unicode character encodings.
self.emit(_LATEX_TEMPLATE_1 % options)
self.emit(_LATEX_TEMPLATE)
self.in_list = False
self.in_table = False
self.head_line = False
#Establish some local styles for the report
self.latexstyle = {}
self.latex_font = {}
style_sheet = self.get_style_sheet()
for style_name in style_sheet.get_paragraph_style_names():
style = style_sheet.get_paragraph_style(style_name)
font = style.get_font()
size = font.get_size()
self.latex_font[style_name] = TexFont()
thisstyle = self.latex_font[style_name]
thisstyle.font_beg = ""
thisstyle.font_end = ""
# Is there special alignment? (default is left)
align = style.get_alignment_text()
if align == "center":
thisstyle.font_beg += "{\\centering"
thisstyle.font_end = ''.join(("\n\n}", thisstyle.font_end))
elif align == "right":
thisstyle.font_beg += "\\hfill"
# Establish font face and shape
if font.get_type_face() == FONT_SANS_SERIF:
thisstyle.font_beg += "\\sffamily"
thisstyle.font_end = "\\rmfamily" + thisstyle.font_end
if font.get_bold():
thisstyle.font_beg += "\\bfseries"
thisstyle.font_end = "\\mdseries" + thisstyle.font_end
if font.get_italic() or font.get_underline():
thisstyle.font_beg += "\\itshape"
thisstyle.font_end = "\\upshape" + thisstyle.font_end
# Now determine font size
fontsize = map_font_size(size)
if fontsize:
thisstyle.font_beg += "\\" + fontsize
thisstyle.font_end += "\\normalsize"
thisstyle.font_beg += " "
thisstyle.font_end += " "
left = style.get_left_margin()
first = style.get_first_indent() + left
thisstyle.leftIndent = left
thisstyle.firstLineIndent = first
self.latexstyle[style_name] = thisstyle
def close(self):
"""Clean up and close the document"""
if self.in_list:
self.emit('\\end{list}\n')
self.emit('\\end{document}\n')
self._backend.close()
def end_page(self):
"""Issue a new page command"""
self.emit('\\newpage')
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."""
style_sheet = self.get_style_sheet()
style = style_sheet.get_paragraph_style(style_name)
ltxstyle = self.latexstyle[style_name]
self.level = style.get_header_level()
self.fbeg = ltxstyle.font_beg
self.fend = ltxstyle.font_end
self.indent = ltxstyle.leftIndent
self.FLindent = ltxstyle.firstLineIndent
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')
if self.in_table: # paragraph in table indicates: cols of rows
self.in_multrow_cell = True
else:
if leader:
self._backend.write(''.join(('\\grprepleader{', leader,
'}%\n')))
else:
self._backend.write('\\grprepnoleader%\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:
self._backend.write(''.join(('\\grminpghead{',
repr(self.indent), '}{', repr(self.pict_width), '}%\n')))
self.fix_indent = True
if leader is not None and not self.in_list:
self.in_list = True
self._backend.write(''.join(('\\grlisthead{', leader,
'}%\n')))
if leader is None:
self.emit('\n')
self.emit('%s ' % self.fbeg)
def end_paragraph(self):
"""End the current paragraph"""
newline = '%\n\n'
if self.in_list:
self.in_list = False
self.emit('\n\\grlisttail%\n')
newline = ''
elif self.in_table:
newline = SEPARATION_PAT
self.emit('%s%s' % (self.fend, newline))
if self.fix_indent:
self.emit('\\grminpgtail%\n\n')
self.fix_indent = False
if self.pict_width:
self.pict_width = 0
self.pict_height = 0
def start_bold(self):
"""Bold face"""
self.emit('\\textbf{')
def end_bold(self):
"""End bold face"""
self.emit('}')
def start_superscript(self):
self.emit('\\textsuperscript{')
def end_superscript(self):
self.emit('}')
def start_table(self, name,style_name):
"""Begin new table"""
self.in_table = True
self.currow = 0
# We need to know a priori how many columns are in this table
styles = self.get_style_sheet()
self.tblstyle = styles.get_table_style(style_name)
self.numcols = self.tblstyle.get_columns()
tblfmt = '*{%d}{l}' % self.numcols
self.emit('\\begin{longtable}[l]{%s}\n' % (tblfmt), TAB_BEG)
def end_table(self):
"""Close the table environment"""
self.emit('%\n\\end{longtable}%\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 = False
self.skipfirst = False
self.curcol = 0
self.currow = self.currow + 1
def end_row(self):
"""End the row (new line)"""
self.emit('\\\\ ')
if self.doline:
if self.skipfirst:
self.emit(''.join((('\\cline{2-%d}' %
self.numcols), '%\n')), ROW_END)
else:
self.emit('\\hline %\n', ROW_END)
else:
self.emit('%\n', ROW_END)
self.emit('%\n')
def start_cell(self, style_name, span=1):
"""Add an entry to the table.
We always place our data inside braces
for safety of formatting."""
self.colspan = span
self.curcol = self.curcol + self.colspan
styles = self.get_style_sheet()
self.cstyle = styles.get_cell_style(style_name)
# ------------------------------------------------------------------
# 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 any longer.
# now column widths are arranged in self.calc_latex_widths()
# serving for fitting of cell contents at any column position.
# self.llist = 1 == self.cstyle.get_longlist()
cellfmt = "l"
# Account for vertical rules
if self.lborder:
cellfmt = '|' + cellfmt
if self.rborder:
cellfmt = cellfmt + '|'
# and Horizontal rules
if self.bborder:
self.doline = True
elif self.curcol == 1:
self.skipfirst = True
if self.tborder:
self.head_line = True
# ------------------------------------------------------------------
# end special modification for boolean values
# ------------------------------------------------------------------
self.emit('\\multicolumn{%d}{%s}' % (span, cellfmt), CELL_BEG, span)
def end_cell(self):
"""Prepares for next cell"""
self.emit('', CELL_END)
def add_media_object(self, infile, pos, x, y, alt='',
style_name=None, crop=None):
"""Add photo to report"""
outfile = os.path.splitext(infile)[0]
pictname = latexescape(os.path.split(outfile)[1])
outfile = ''.join((outfile, '.jpg'))
if infile != outfile:
try:
curr_img = Image.open(infile)
curr_img.save(outfile)
px, py = curr_img.size
if py > px:
y = y*py/px
except IOError:
self.emit(''.join(('%\n *** Error: cannot convert ', infile,
'\n *** to ', outfile,
'%\n')))
if self.in_table:
self.pict_in_table = True
self.emit(''.join(('\\grmkpicture{', outfile, '}{', repr(x), '}{',
repr(y), '}{', pictname, '}%\n')))
self.pict_width = x
self.pict_height = y
def write_text(self,text,mark=None,links=False):
"""Write the text to the file"""
if text == '\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{3\\grbaseindent}}')
self.emit(text + ' ')
def write_styled_note(self, styledtext, format, style_name,
contains_html=False, links=False):
"""
Convenience function to write a styledtext to the latex doc.
styledtext : assumed a StyledText object to write
format : = 0 : Flowed, = 1 : Preformatted
style_name : name of the style to use for default presentation
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. self ignores notes that contain html
links: bool, make URLs clickable if True
"""
if contains_html:
return
text = str(styledtext)
s_tags = styledtext.get_tags()
if format:
#preformatted, use different escape function
self._backend.setescape(True)
markuptext = self._backend.add_markup_from_styled(text, s_tags)
if links == True:
markuptext = re.sub(URL_PATTERN, _CLICKABLE, markuptext)
markuptext = self._backend.add_markup_from_styled(text, s_tags)
#there is a problem if we write out a note in a table.
# ..................
# now solved by postprocessing in self.calc_latex_widths()
# by explicitely setting suitable width for all columns.
#
if format:
self.start_paragraph(style_name)
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 '):
self.start_paragraph(style_name)
for realline in line.split('\n'):
self.emit(realline)
self.emit("~\\newline \n")
self.end_paragraph()