Source code for mathmaker.lib.machine.LaTeX

# -*- coding: utf-8 -*-

# Mathmaker creates automatically maths exercises sheets
# with their answers
# Copyright 2006-2017 Nicolas Hainaux <nh.techn@gmail.com>

# This file is part of Mathmaker.

# Mathmaker 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 3 of the License, or
# any later version.

# Mathmaker 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 Mathmaker; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import os
import sys
import time
import glob
import subprocess
from distutils.version import LooseVersion
from tempfile import NamedTemporaryFile

from mathmakerlib import required
from mathmakerlib.calculus import is_integer, is_number
from mathmakerlib.LaTeX import TIKZSET
from mathmakerlib.LaTeX import AttrList, Command, DocumentClass, UsePackage
from mathmakerlib.LaTeX import OptionsList, UseTikzLibrary

from mathmaker import settings
from mathmaker.lib.constants import latex, SLIDE_CONTENT_SEP
from mathmaker.lib.constants.latex import TEXT_SCALES, TEXT_RANKS
from mathmaker.lib.tools import generate_preamble_comment
from mathmaker.lib.core.base import Printable, Drawable
from . import Structure


# ------------------------------------------------------------------------------
# --------------------------------------------------------------------------
# ------------------------------------------------------------------------------
##
# @class LaTeX
# @brief This machine knows how to write LaTeX commands & math expressions
# @todo When creating another machine, some things might have to change here
[docs]class LaTeX(Structure.Structure): # -------------------------------------------------------------------------- ## # @brief Constructor # The created machine is set to the beginning of an expression, # its language is the default one (from cfg file or in case of any # problem, from text.DEFAULT_LANGUAGE) # its encoding is set to the default one (from cfg file or in case of any # problem, from latex.DEFAULT_ENCODING) # @param expression_begins True if machine's at an expression's beginning # @param **options Any options # @return One instance of machine.LaTeX def __init__(self, language, create_pic_files=True, **options): self.text_sizes = latex.TEXT_SIZES self.font_size_offset = 0 self.create_pic_files = create_pic_files self.language_code = language self.language = latex.LANGUAGE_PACKAGE_NAME[language] self.markup = latex.MARKUP self.out = sys.stdout self.redirect_output_to_str = True # -------------------------------------------------------------------------- ## # @brief Write the complete LaTeX preamble of the sheet to the output.
[docs] def write_preamble(self, variant='default', required_pkg=None): if required_pkg is None: required_pkg = [] from mathmaker.lib import shared luatex85patch = str(Command('RequirePackage', 'luatex85')) + '\n' \ if LooseVersion(settings.luatex_version) >= LooseVersion('0.85') \ else '' # xcolor is handled first, because it may require to be loaded as an # option (if using 'beamer' document class) xcolor = '' xcolor_attr = [] if required.package['xcolor']: xcolor_attr = [o for o in required.options['xcolor']] if variant != 'slideshow': xcolor = '% {}\n{}\n\n'.format( _('To be able to color the documents'), str(UsePackage('xcolor', options=xcolor_attr))) # \documentclass if variant == 'slideshow': dc = DocumentClass('beamer', options='20pt') if required.package['xcolor']: if xcolor_attr: dc.options.append({'xcolor': AttrList(*xcolor_attr)}) else: dc.options.append('xcolor') else: dc = DocumentClass('article', options=['a4paper', 'fleqn', '12pt']) dc = str(dc) # various fonts, symbols and maths packages lxfonts = str(UsePackage('lxfonts')) \ if settings.round_letters_in_math_expr else '' amssymb = '' if 'amssymb' in required_pkg: amssymb = str(UsePackage('amssymb')) amsmath = '' if required.package['amsmath'] or 'amsmath' in required_pkg: amsmath = str(UsePackage('amsmath')) eurosym = str(UsePackage('eurosym')) \ if required.package['eurosym'] else '' stackengine = str(UsePackage('stackengine')) \ if required.package['stackengine'] else '' scalerel = str(UsePackage('scalerel')) \ if required.package['scalerel'] else '' variouspkg = [p for p in [lxfonts, amssymb, amsmath, eurosym, stackengine, scalerel] if p != ''] variouspkg = '\n'.join(variouspkg) if variouspkg != '': variouspkg = '\n\n' + variouspkg # font patch font_patch = '' if settings.font is not None: font_patch = r"""\usepackage[no-math]{{fontspec}} \AtEndPreamble{{\setmainfont{font_name}[NFSSFamily=fontid]}} \DeclareSymbolFont{{mynumbers}} {{TU}}{{fontid}}{{m}}{{n}} \SetSymbolFont {{mynumbers}}{{bold}}{{TU}}{{fontid}}{{bx}}{{n}} \AtBeginDocument{{ \DeclareMathSymbol{{0}}{{\mathalpha}}{{mynumbers}}{{`0}} \DeclareMathSymbol{{1}}{{\mathalpha}}{{mynumbers}}{{`1}} \DeclareMathSymbol{{2}}{{\mathalpha}}{{mynumbers}}{{`2}} \DeclareMathSymbol{{3}}{{\mathalpha}}{{mynumbers}}{{`3}} \DeclareMathSymbol{{4}}{{\mathalpha}}{{mynumbers}}{{`4}} \DeclareMathSymbol{{5}}{{\mathalpha}}{{mynumbers}}{{`5}} \DeclareMathSymbol{{6}}{{\mathalpha}}{{mynumbers}}{{`6}} \DeclareMathSymbol{{7}}{{\mathalpha}}{{mynumbers}}{{`7}} \DeclareMathSymbol{{8}}{{\mathalpha}}{{mynumbers}}{{`8}} \DeclareMathSymbol{{9}}{{\mathalpha}}{{mynumbers}}{{`9}} \DeclareMathSymbol{{.}}{{\mathalpha}}{{mynumbers}}{{`.}} \DeclareMathSymbol{{,}}{{\mathalpha}}{{mynumbers}}{{`,}} }}""".format(font_name='{' + settings.font + '}') # language polyglossia = str(UsePackage('polyglossia')) language_options = None if self.language_code in latex.LANGUAGE_OPTIONS: lod = latex.LANGUAGE_OPTIONS[self.language_code] language_options = OptionsList(lod) language_setup = Command('setmainlanguage', content=self.language, options=language_options) language_setup = str(language_setup) # siunitx siunitx = '' if required.package['siunitx']: siunitx = '% {}\n{}'.format(_('To display units correctly'), str(UsePackage('siunitx'))) sisetup_attr = {'mode': 'text'} if settings.language.startswith('fr'): sisetup_attr.update({'locale': 'FR'}) if settings.font is not None: siunitx += r""" \newfontfamily\configfont{{{font_name}}} """.format(font_name=settings.font) sisetup_attr.update({'text-rm': r'\configfont'}) siunitx += r""" \AtBeginDocument{{ {sisetup} }}""".format(sisetup=str(Command('sisetup', content=sisetup_attr))) siunitx += '\n\n' # TikZ setup tikz_setup = '' if required.package['tikz']: tikz = '% {}\n{}'.format(_('To draw TikZ pictures'), str(UsePackage('tikz'))) tikzlibraries = '\n'.join([str(UseTikzLibrary(k)) for k in required.tikz_library if required.tikz_library[k]]) tikzset = '\n'.join([TIKZSET[k] for k in required.tikzset if required.tikzset[k]]) tikz_setup = '\n'.join(_ for _ in [tikz, tikzlibraries, tikzset]) tikz_setup += '\n' # hyperref hyperref = '' if shared.enable_js_form: hyperref = '\n' + '% {}\n{}\n\n'\ .format(_('To insert pdf formular and javascript'), str(UsePackage('hyperref'))) # cancel cancel = '' if required.package['cancel']: cancel = '\n' + '% {}\n{}\n\n'\ .format(_('To strike out numbers '), str(UsePackage('cancel'))) # multicol multicol = '' if required.package['multicol']: multicol = '\n' + '% {}\n{}\n\n'\ .format(_('To use multicol environment'), str(UsePackage('multicol'))) # placeins placeins = '' if required.package['placeins']: placeins = '\n' + '% {}\n{}\n\n'\ .format(_('To get a better control on floats positioning ' '(e.g. tabulars)'), str(UsePackage('placeins'))) # textcomp textcomp = '' if 'textcomp' in required_pkg: textcomp = '\n' + '% {}\n{}\n\n'\ .format(_('To use some symbols like currency units'), str(UsePackage('textcomp'))) # ulem ulem = '' if required.package['ulem']: ulem = '\n' + '% {}\n{}\n\n'\ .format(_('For pretty underlining'), str(UsePackage('ulem'))) # array array = '' if required.package['array']: array = '\n' + '% {}\n{}\n\n'\ .format(_('To use extra commands to handle tabulars'), str(UsePackage('array'))) # graphicx graphicx = '' if required.package['graphicx']: graphicx = '\n' + '% {}\n{}\n\n'\ .format(_('To include .eps pictures'), str(UsePackage('graphicx'))) # epstopdf epstopdf = '' if required.package['epstopdf']: epstopdf = '\n' + '% {}\n{}\n\epstopdfsetup{{outdir=./}}\n\n'\ .format(_('To make .eps pictures includable by pdflatex'), str(UsePackage('epstopdf'))) # textpos textpos = '' if required.package['textpos']: textpos_options = [o for o in required.options['textpos']] textpos = '\n' + '% {}\n{}\n\n'\ .format(_('Absolute positioning of text on the page'), str(UsePackage('textpos', options=textpos_options))) # Specific packages if variant == 'slideshow': specificpkg = r"""% Useless? \usefonttheme{professionalfonts} \usepackage{parskip} """ else: specificpkg = r""" % {geometry_comment} \usepackage{{geometry}}""".format(geometry_comment=_('To define the layout ' 'of the page')) # Specific commands if variant == 'slideshow': specificcmd = r""" \addtolength{\headsep}{-1cm} """ else: specificcmd = r"""% {layout_comment} \geometry{{hmargin=0.75cm, vmargin=0.75cm}} \setlength{{\parindent}}{{0cm}} \setlength{{\arrayrulewidth}}{{0.02pt}} \pagestyle{{empty}}% {counter_comment} \newcounter{{n}} % {exercisecmd_comment} \newcommand{{\exercise}}{{\noindent \hspace{{-.25cm}} """\ r"""\stepcounter{{n}} \normalsize \textbf{{Exercice \arabic{{n}}}} """\ r"""\newline \normalsize }} % {resetcounter_comment} \newcommand{{\razcompteur}}{{\setcounter{{n}}{{0}}}} """.format(layout_comment=_('General layout of the page'), counter_comment=_('Exercises counter'), exercisecmd_comment=_('Definition of the "exercise" command, which ' 'will insert the word "Exercise" in bold, ' 'with its number and automatically ' 'increments the counter'), resetcounter_comment=_('Definition of the command resetting the ' 'exercises counter (which is useful when ' 'begining to write the answers sheet)')) # noqa # Common commands commoncmd = '' if settings.language.startswith('fr'): commoncmd = r''' % {french_parallel_sign_comment} \renewcommand{{\parallel}}{{\mathbin{{/\negthickspace/}}}} '''.format(french_parallel_sign_comment=_('Redefinition of "\parallel" ' 'command to draw the parallel ' 'symbol slanted as usual in french ' 'notations')) # Complete preamble generation preamble = r""" {luatex85patch}{documentclass}{symbols} {font_patch} {polyglossia} {language_setup} {siunitx}{xcolor}{tikz_setup}{hyperref}{cancel}{multicol}{placeins}{ulem}"""\ r"""{textcomp}{array}{graphicx}{epstopdf}{textpos}{specificpackages} {specificcommands}{commoncommands} """.format(luatex85patch=luatex85patch, documentclass=dc, symbols=variouspkg, polyglossia=polyglossia, language_setup=language_setup, font_patch=font_patch, siunitx=siunitx, xcolor=xcolor, tikz_setup=tikz_setup, hyperref=hyperref, cancel=cancel, multicol=multicol, placeins=placeins, ulem=ulem, textcomp=textcomp, array=array, graphicx=graphicx, epstopdf=epstopdf, textpos=textpos, specificpackages=specificpkg, specificcommands=specificcmd, commoncommands=commoncmd) result = generate_preamble_comment(latex.FORMAT_NAME_PRINT) + preamble if self.redirect_output_to_str: return result else: self.out.write(result)
# -------------------------------------------------------------------------- ## # @brief Writes to the output the command to begin the document
[docs] def write_document_begins(self, variant='default'): output_str = "\\begin{document}\n" # if settings.font is not None and variant != 'slideshow': # output_str += "\\fontspec{" + settings.font + "}\n" if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief Writes to the output the end of document command
[docs] def write_document_ends(self): from mathmaker.lib import shared output_str = '\end{document}\n' if shared.enable_js_form: output_str = '\n\end{Form}\n' + output_str if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief Writes to the output the command displaying an exercise's title
[docs] def write_exercise_number(self): output_str = "\exercise" + "\n" if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief Writes to the output the jump to next page command
[docs] def write_jump_to_next_page(self): output_str = "\\newpage" + "\n" if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief Writes to the output the exercises counter reinitialize command
[docs] def reset_exercises_counter(self): output_str = "\\razcompteur " + "\n" if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
[docs] def addvspace(self, height='30.0pt', **options): """ Add a vertical space. """ output_str = "\n\n\n\\addvspace{{{height}}}\n\n\n"\ .format(height=height) if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
[docs] def write_frame(self, content, uncovered=False, only=False, duration=None, numbering=''): """ Write a slideshow's frame to the output :param content: the frame's content :type content: str :param uncovered: whether to split the content in several slides that will show one after the other. Mostly useful for title. The content's parts must be delimited by SLIDE_CONTENT_SEP (from lib.constants). :type uncovered: bool :param only: whether to split the content in several slides that will show one after the other. Mostly useful for answers. The content's parts must be delimited by SLIDE_CONTENT_SEP (from lib.constants). Difference with uncovered is the text will be replaced, not only made invisible. :type only: bool :param duration: the duration of the frame. If it's None, then no duration will be set. :type duration: number (int or float) :rtype: str """ result = '\n' + r'\begin{frame}' + '\n' if duration is not None: result += r'\transduration{t}'\ .format(t='{' + str(duration) + '}') + '\n' result += r'\begin{center}' + '\n' if settings.font is not None: result += r'\fontspec{font}'\ .format(font='{' + settings.font + '}') + '\n' if numbering != '': result += (r'\begin{{textblock}}{{1}}(0.5,0)' + r'\textcolor{{Silver!90!Black}}{{\small{displ_nb}}}' + r'\end{{textblock}}') \ .format(displ_nb='{' + numbering + '}') + '\n' required.options['xcolor'].add('svgnames') required.package['textpos'] = True required.options['textpos'].add('overlay') required.options['textpos'].add('absolute') if uncovered: for i, chunk in enumerate(content.split(sep=SLIDE_CONTENT_SEP)): result += r'\uncover<{n}>{c}'\ .format(n=i + 1, c='{' + chunk + '}') + '\n' elif only: for i, chunk in enumerate(content.split(sep=SLIDE_CONTENT_SEP)): result += r'\only<{n}>{c}'\ .format(n=i + 1, c='{' + chunk + '}') + '\n' else: result += content + '\n' result += r'\end{center}' + '\n' result += r'\end{frame}' + '\n' + '\n' return result
## # @brief Writes to the output the new line command
[docs] def write_new_line(self, check='', check2='', check3='', check4=''): output_str = '\\newline \n' if (check == '\]' or 'addvspace' in check2 or 'newpage' in check3 or 'FloatBarrier' in check4): output_str = '' if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief Writes to the output two commands writing two new lines
[docs] def write_new_line_twice(self, **options): output_str = "\\newline " + "\n" + " \\newline " + "\n" if 'check' in options: if options['check'] == '\]': output_str = "" if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief Prints the given string as a mathematical expression
[docs] def write_math_style2(self, given_string, **kwargs): spacing = " " if 'extra_spacing' in kwargs and not kwargs['extra_spacing']: spacing = "" output_str = self.markup['opening_math_style2'] + spacing \ + given_string \ + spacing + self.markup['closing_math_style2'] if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief Prints the given string as a mathematical expression
[docs] def write_math_style1(self, given_string): output_str = self.markup['opening_math_style1'] + " " \ + given_string \ + " " + self.markup['closing_math_style1'] if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
[docs] def write_out(self, latex_document: str, pdf_output=False): """ Writes the given document to the output. If pdf_output is set to True then the document will be compiled into a pdf and the pdf content will be written to output. :param latex_document: contains the entire LaTeX document :param pdf_output: if True, output will be written in pdf format """ document = latex_document if pdf_output: with NamedTemporaryFile(mode='r+t') as tmp_file: tmp_filename = os.path.basename(tmp_file.name) tmp_file.write(latex_document) tmp_file.seek(0) p = subprocess.Popen(['lualatex', '-interaction', 'nonstopmode', tmp_file.name], cwd=settings.outputdir, stdout=sys.stderr) errorcode = p.wait() if errorcode: saved_log_name = os.path.join( settings.outputdir, 'lualatex_' + time.strftime("%Y%m%d-%H%M%S") + '.log') tmp_log_name = os.path.join(settings.outputdir, tmp_filename + '.log') with open(tmp_log_name, mode='rt') as tmplog,\ open(saved_log_name, mode='wt') as savedlog: # noqa savedlog.write(tmplog.read()) saved_tex_name = os.path.join( settings.outputdir, 'lualatex_' + time.strftime("%Y%m%d-%H%M%S") + '.tex') with open(saved_tex_name, mode='wt') as savedtex: savedtex.write(document) raise RuntimeError('lualatex had a problem while ' 'compiling. See {} and {}.' .format(saved_tex_name, saved_log_name)) pdf_filename = os.path.join(settings.outputdir, tmp_filename + '.pdf') with open(pdf_filename, mode='rb') as pdf_file: document = pdf_file.read() self.out = sys.stdout.buffer for f in glob.glob(os.path.join(settings.outputdir, tmp_filename + '.*')): os.remove(f) self.out.write(document)
## # @brief Writes to the output the given string # @option emphasize='bold'|'italics'|'underlined'
[docs] def write(self, given_string, **options): output_str = "" if 'emphasize' in options: if options['emphasize'] == 'bold': output_str = "\\textbf{" + given_string + "}" + "\n" elif options['emphasize'] == 'italics': output_str = "\\textit{" + given_string + "}" + "\n" elif options['emphasize'] == 'underlined': output_str = r"\uline{" + given_string + "}" + "\n" required.package['ulem'] = True else: output_str = given_string else: output_str = given_string if 'multicolumns' in options: keyword = 'multicols' required.package['multicol'] = True if options.get('unbalanced', False): keyword = 'multicols*' if (type(options['multicolumns']) == int and options['multicolumns'] >= 1): # __ output_str = '\\begin{' + keyword + '}{' \ + str(options['multicolumns']) + "} " + "\n" \ + output_str \ + '\end{' + keyword + '}\n' else: raise ValueError('This: {} should be an int >=1\n' .format(str(options['multicolumns']))) if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief turn the size keyword in LaTeX matching keyword # @warning if you chose a too low or too high value as font_size_offset, # @warning then all the text will be either tiny or Huge.
[docs] def translate_font_size(self, arg): if not type(arg) == str: raise TypeError('Got: ' + str(type(arg)) + ' instead of str') elif arg not in TEXT_SCALES: raise ValueError('expected a text size (see TEXT_SCALES) ' 'instead of ' + arg) arg_num = TEXT_RANKS[arg] size_to_use = self.font_size_offset + arg_num if size_to_use < 0: size_to_use = 0 elif size_to_use > len(self.text_sizes) - 1: size_to_use = len(self.text_sizes) - 1 return self.text_sizes[size_to_use]
## # @brief Writes to the output the command setting the text size
[docs] def write_set_font_size_to(self, arg): output_str = self.translate_font_size(arg) + "\n" if self.redirect_output_to_str: return output_str else: self.out.write(output_str)
## # @brief Writes content arranged like in a table. # @brief In the case of latex, it will just be the same. # @param size: (nb of lines, nb of columns) # @param col_widths: [int] # @param content: [strings] # @options: borders=0|1|2|3... (not implemented yet) # @options: unit='inch' etc. (check the possibilities...)
[docs] def write_layout(self, size, col_widths, content, **options): if self.redirect_output_to_str: return self.create_table(size, content, col_fmt=col_widths, **options) else: self.out.write(self.create_table(size, content, col_fmt=col_widths, **options))
# -------------------------------------------------------------------------- ## # @brief Writes a table filled with the given [strings] # @param size: (nb of lines, nb of columns) # @param chosen_markup # @param content: [strings] # @options col_fmt: [int|<'l'|'c'|'r'>] # @options: borders='all'|'v_internal' # @options: unit='inch' etc. (check the possibilities...) # @return
[docs] def create_table(self, size, content, **options): # 'array' package is loaded whenever a tabular will be used. # It would be complicated to find out which commands exactly require # it, and anyway it is recommended to use it when drawing a tabular. required.package['array'] = True n_col = size[1] n_lin = size[0] result = "" length_unit = 'cm' if 'unit' in options: length_unit = options['unit'] tabular_format = "" v_border = "" h_border = "" justify = ["" for _ in range(n_col)] new_line_sep = "\\\\" + "\n" min_row_height = "" # The last column is not centered vertically (LaTeX bug?) # As a workaround it's possible to add an extra empty column... extra_last_column = "" extra_col_sep = "" if 'justify' in options and type(options['justify']) == list: if not len(options['justify']) == n_col: raise ValueError("The number of elements of this list should " "be equal to the number of columns of the " "tabular.") new_line_sep = "\\tabularnewline" + "\n" extra_last_column = "@{}m{0pt}@{}" extra_col_sep = " & " justify = [] for i in range(n_col): if options['justify'][i] == 'center': justify.append(">{\centering}") elif options['justify'][i] == 'left': justify.append(">{}") else: raise ValueError("Expecting 'left' or 'center' as values " "of this list.") elif 'center' in options: new_line_sep = "\\tabularnewline" + "\n" extra_last_column = "@{}m{0pt}@{}" extra_col_sep = " & " justify = [">{\centering}" for _ in range(n_col)] if 'min_row_height' in options: min_row_height = " [" + str(options['min_row_height']) \ + length_unit + "] " cell_fmt = "p{" if 'center_vertically' in options and options['center_vertically']: cell_fmt = "m{" if 'borders' in options and options['borders'] in ['all', 'v_internal', 'penultimate']: v_border = "|" h_border = "\\hline \n" col_fmt = ['c' for i in range(n_col)] if ('col_fmt' in options and type(options['col_fmt']) == list and len(options['col_fmt']) == n_col): # __ for i in range(len(col_fmt)): col_fmt[i] = options['col_fmt'][i] for i in range(len(col_fmt)): t = col_fmt[i] if is_number(col_fmt[i]): t = cell_fmt + str(col_fmt[i]) + " " + str(length_unit) + "}" vb = v_border if 'borders' in options and options['borders'] == "penultimate": if i == n_col - 1: vb = "|" else: vb = "" tabular_format += vb + justify[i] + t if 'borders' in options and options['borders'] == "penultimate": v_border = "" tabular_format += extra_last_column + v_border if 'borders' in options and options['borders'] in ['v_internal']: tabular_format = tabular_format[1:-1] result += "\\begin{tabular}{" + tabular_format + "}" + "\n" result += h_border for i in range(int(n_lin)): for j in range(n_col): result += str(content[i * n_col + j]) if j != n_col - 1: result += "&" + "\n" if i != n_lin - 1: result += extra_col_sep + new_line_sep + min_row_height \ + h_border result += extra_col_sep + new_line_sep + min_row_height + h_border result += "\end{tabular}" + "\n" return result.replace(" $~", "$~").replace("~$~", "$~")
# -------------------------------------------------------------------------- ## # @brief Creates a LaTeX string of the given object
[docs] def type_string(self, objct, **options): if isinstance(objct, Printable): options.update({'force_expression_begins': True}) return objct.into_str(**options) elif is_number(objct) or type(objct) is str: return str(objct) else: raise TypeError('Got: ' + str(type(objct)) + ' instead of String|Number|Printable')
# -------------------------------------------------------------------------- ## # @brief Draws a horizontal dashed line
[docs] def insert_dashed_hline(self, **options): required.package['tikz'] = True return "\\begin{tikzpicture}[x=2cm]" \ + "\draw[black,line width=0.5pt,dashed] (0,0)--(9,0);" \ + "\end{tikzpicture}" + "\n"
# -------------------------------------------------------------------------- ## # @brief Puts a vertical space (default 1 cm)
[docs] def insert_vspace(self, **options): return "\\vspace{1 cm}"
# -------------------------------------------------------------------------- ## # @brief Returns a non-breaking space
[docs] def insert_nonbreaking_space(self, **options): return "~"
# -------------------------------------------------------------------------- ## # @brief Draws and inserts the picture of the drawable_arg
[docs] def insert_picture(self, drawable_arg, **options): required.package['graphicx'] = True required.package['epstopdf'] = True if not isinstance(drawable_arg, Drawable): raise ValueError('Got: ' + str(drawable_arg) + ' instead of a Drawable') drawable_arg.into_pic(create_pic_file=self.create_pic_files) s = "1" if 'scale' in options: s = str(options['scale']) if 'vertical_alignment_in_a_tabular' in options: return "\\raisebox{-.5\height}{" \ + "\includegraphics[scale=" + s + "]{" \ + drawable_arg.eps_filename \ + "}" + "}" elif 'top_aligned_in_a_tabular' in options: # return "\includegraphics[align=t, scale=" + s + "]{" \ # + drawable_arg.eps_filename \ # + "}" + "\\newline" + "\n" return "\\raisebox{-0.9\height}{" \ + "\includegraphics[scale=" + s + "]{" \ + drawable_arg.eps_filename \ + "}" + "}" else: return "\includegraphics[scale=" + s + "]{" \ + drawable_arg.eps_filename \ + "}" + "\n"
# -------------------------------------------------------------------------- ## # @brief Sets the font_size_offset field
[docs] def set_font_size_offset(self, arg): if not (is_number(arg) and is_integer(arg)): raise TypeError('Got: ' + str(type(arg)) + ' instead of an integer') else: self.font_size_offset = int(arg)
# -------------------------------------------------------------------------- ## # @brief Sets the redirect_output_to_str field to True or False
[docs] def set_redirect_output_to_str(self, arg): if type(arg) == bool: self.redirect_output_to_str = arg else: raise TypeError('Got: ' + str(type(arg)) + ' instead of boolean ')