# # -*- 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 # 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()