# -*- 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 copy
import random
import warnings
from mathmakerlib.LaTeX import KNOWN_AMSSYMB_SYMBOLS, KNOWN_TEXTCOMP_SYMBOLS
from mathmakerlib.LaTeX import KNOWN_AMSMATH_SYMBOLS
from mathmaker.lib import shared
from mathmaker.lib.constants import SLIDE_CONTENT_SEP
from mathmaker.lib.tools import rotate
from mathmaker.lib.tools.frameworks import load_sheet, read_layout
from mathmaker.lib.tools.frameworks import build_exercises_list
from mathmaker.lib.document.frames import Exercise
DEFAULT_SHEET_LAYOUT = {'type': 'default', 'unit': 'cm',
'font_size_offset': '0',
'wordings': '', 'answers': ''}
[docs]class Sheet(object):
# --------------------------------------------------------------------------
##
# @brief Constructor
# @param **options Any options
# @return One instance of sheet.Generic
def __init__(self, theme, subtheme, sheet_name, **options):
from mathmaker.lib.tools.xml import get_sheet_config
from mathmaker.lib.tools.xml import get_exercises_list
# from mathmaker.lib.tools.frameworks import load_sheet
filename = options.get('filename', None)
self.preset = 'default'
# default presets
presets = {'default':
{'write_ex_titles': True,
'header': '',
'title': '',
'subtitle': '',
'text': '',
'answers_title': _('Answers')},
'mental calculation':
{'write_ex_titles': False,
'header': '',
'title': '',
'subtitle': '',
'text': '',
'answers_title': _('Answers')},
'mental calculation slideshow':
{'write_ex_titles': False,
'header': '',
'title': _('Ready,{sep}Steady,{sep}Go!')
.format(sep=SLIDE_CONTENT_SEP),
'subtitle': '',
'text': '',
'answers_title': _('Answers!')}}
if filename is None:
data = load_sheet(theme, subtheme, sheet_name)
self.preset = data.get('preset', 'default')
shared.enable_js_form = (options.get('enable_js_form', False)
and self.preset == 'mental calculation')
header = data.get('header', presets[self.preset]['header'])
title = data.get('title', presets[self.preset]['title'])
subtitle = data.get('subtitle', presets[self.preset]['subtitle'])
text = data.get('text', presets[self.preset]['text'])
answers_title = data.get('answers_title',
presets[self.preset]['answers_title'])
layout_data = copy.deepcopy(DEFAULT_SHEET_LAYOUT)
if self.preset == 'mental calculation':
layout_data['font_size_offset'] = '-1'
loaded_layout_data = data.get('layout', layout_data)
if self.preset == 'mental calculation slideshow':
layout_data['type'] = 'slideshow'
if not isinstance(loaded_layout_data, list):
loaded_layout_data = [loaded_layout_data]
for d in loaded_layout_data:
layout_data.update(d)
self.layout_type = layout_data['type']
if (self.preset == 'mental calculation slideshow'
and self.layout_type != 'slideshow'):
self.layout_type = 'slideshow'
warnings.warn('For mental calculation slideshows, the '
'layout type cannot be redefined to another '
'value than \'slideshow\'.')
self.sheet_layout_unit = layout_data['unit']
font_size_offset = layout_data['font_size_offset']
sheet_layout = read_layout(layout_data)
try:
font_size_offset = int(font_size_offset)
except ValueError:
raise ValueError('YAML file format error: expected an integer'
'as font_size_offset value, got {} instead.'
.format(str(type(font_size_offset))))
else:
(header,
title,
subtitle,
text,
answers_title,
self.layout_type,
font_size_offset,
self.sheet_layout_unit,
sheet_layout,
self.preset) = get_sheet_config(filename)
self.write_ex_titles = presets[self.preset].get('write_ex_titles')
self.exercises_list = list()
shared.machine.set_font_size_offset(font_size_offset)
self.write_texts_twice = False
if 'write_texts_twice' in options and options['write_texts_twice']:
self.write_texts_twice = True
# Some tests on sheet_layout before using it ;
# but it's a bit complicated to write a complete set of tests on it ;
# e.g. if the user doesn't use the same number of exercises in the
# 'exc' key as in 'ans' key (which would be stupid) this
# won't be checked here and so it won't work.
if type(sheet_layout) != dict:
raise TypeError('Got: ' + str(type(type(sheet_layout)))
+ ' instead of a dict')
# if len(sheet_layout) != 4:
# raise ValueError('SHEET_LAYOUT should have four keys but '
# 'it has ' + str(len(sheet_layout)) + ' keys')
for k in ['exc', 'ans']:
if k not in sheet_layout:
raise ValueError('SHEET_LAYOUT should have a key '
+ k + ' but it has no such key')
if type(sheet_layout[k]) != list:
raise ValueError('SHEET_LAYOUT[' + k + '] should be'
+ ' a list, but it is '
+ str(type(sheet_layout[k])))
if len(sheet_layout[k]) % 2:
raise ValueError('SHEET_LAYOUT[' + k + '] should have'
+ ' an even number of elements but it has '
+ str(len(sheet_layout[k]))
+ ' elements')
for i in range(int(len(sheet_layout[k]) // 2)):
if (not (sheet_layout[k][2 * i] is None
or type(sheet_layout[k][2 * i]) == list
or sheet_layout[k][2 * i] == 'jump')):
# __
raise ValueError('SHEET_LAYOUT[' + k + ']['
+ str(2 * i) + '] should be '
'either a list '
'or None or "jump", but it is a '
+ str(type(sheet_layout[k][2 * i])))
elif sheet_layout[k][2 * i] is None:
if (not (type(sheet_layout[k][2 * i + 1]) == int
or sheet_layout[k][2 * i + 1]
in ['all', 'all_left', 'jump'])):
# __
raise ValueError(
'SHEET_LAYOUT[' + k + ']['
+ str(2 * i + 1) + '] should be an '
+ 'int since it follows the None'
+ 'keyword, but it is '
+ str(type(sheet_layout[k][2 * i + 1])))
elif sheet_layout[k][2 * i] == 'jump':
if not sheet_layout[k][2 * i + 1] == 'next_page':
raise ValueError(
'SHEET_LAYOUT[' + k + ']['
+ str(2 * i + 1) + '] should be: '
+ 'next_page since it follows '
+ 'the jump keyword, but it is '
+ str(type(sheet_layout[k][2 * i + 1])))
elif type(sheet_layout[k][2 * i]) == list:
if not type(sheet_layout[k][2 * i + 1]) == tuple:
raise ValueError(
'SHEET_LAYOUT[' + k + ']['
+ str(2 * i + 1) + '] should be a tuple, '
'but it is '
+ str(type(sheet_layout[k][2 * i + 1])))
if (not len(sheet_layout[k][2 * i + 1])
== (len(sheet_layout[k][2 * i]) - 1)
* sheet_layout[k][2 * i][0]):
# __
raise ValueError(
'SHEET_LAYOUT[' + k + ']['
+ str(2 * i + 1) + '] should have '
+ ' as many elements as the '
+ 'number of cols described in '
+ 'SHEET_LAYOUT[' + k + ']['
+ str(2 * i) + '], but found '
+ str(len(sheet_layout[k][2 * i + 1]))
+ ' instead of '
+ str(len(sheet_layout[k][2 * i]) - 1))
else:
raise ValueError(
'SHEET_LAYOUT[' + k + ']['
+ str(2 * i) + '] is not of any '
+ ' of the expected types or '
+ 'values, it is instead: '
+ str(len(sheet_layout[k][2 * i])))
self.sheet_layout = sheet_layout
self.header = _(header) if header != "" else ""
self.title = _(title) if title != "" else ""
self.subtitle = _(subtitle) if subtitle != "" else ""
self.text = _(text) if text != "" else ""
self.answers_title = _(answers_title) if answers_title != "" else ""
self.shift = options.get('shift', False)
if filename is None:
for e_data in build_exercises_list(data):
if self.preset != 'default' and 'preset' not in e_data:
if self.preset == 'mental calculation slideshow':
e_preset = 'mental calculation'
else:
e_preset = self.preset
e_data.update({'preset': e_preset})
if self.preset == 'mental calculation slideshow':
e_data.update({'layout_variant': 'slideshow'})
exc = Exercise(data=e_data)
self.exercises_list.append(exc)
if self.shift:
excbis = copy.deepcopy(exc)
offset = random.choice([i - 9
for i in range(19)
if abs(i - 9) >= 5])
excbis.questions_list = rotate(excbis.questions_list,
offset)
self.exercises_list.append(excbis)
else:
for ex in get_exercises_list(filename):
ex_kwargs = ex[2]
if self.preset != 'default':
ex_kwargs.update({'preset': self.preset})
self.exercises_list.append(Exercise(q_list=ex[0],
x_layout=ex[1][1],
x_config=ex[1][0],
**ex_kwargs))
# @brief Writes the whole sheet's content to the output.
def __str__(self):
result = ''
if self.layout_type in ['default', 'equations', 'slideshow']:
result += shared.machine.write_document_begins(
variant=self.layout_type)
result += self.sheet_header_to_str()
result += self.sheet_title_to_str(variant=self.layout_type)
result += self.sheet_text_to_str()
result += self.texts_to_str('exc', 0)
if self.layout_type != 'slideshow':
result += shared.machine.write_jump_to_next_page()
result += self.answers_title_to_str(
variant=self.layout_type)
result += self.texts_to_str('ans', 0)
result += shared.machine.write_document_ends()
pkg = []
if any([s in result for s in KNOWN_AMSSYMB_SYMBOLS]):
pkg.append('amssymb')
if any([s in result for s in KNOWN_AMSMATH_SYMBOLS]):
pkg.append('amsmath')
if any([s in result for s in KNOWN_TEXTCOMP_SYMBOLS]):
pkg.append('textcomp')
result = shared.machine.write_preamble(variant=self.layout_type,
required_pkg=pkg)\
+ result
return result
# --------------------------------------------------------------------------
##
# @brief Return as str exercises' or answers'texts
[docs] def texts_to_str(self, ex_or_answers, n_of_first_ex):
M = shared.machine
result = ''
if self.layout_type == 'slideshow':
result += self.exercises_list[0].to_str(ex_or_answers)
return result
result += M.reset_exercises_counter()
result += M.write_set_font_size_to('large')
layout = self.sheet_layout[ex_or_answers]
ex_n = n_of_first_ex
for k in range(int(len(layout) // 2)):
if layout[2 * k] is None:
how_many = layout[2 * k + 1]
if layout[2 * k + 1] in ['all_left', 'all']:
how_many = len(self.exercises_list) - ex_n
if (self.layout_type in ['short_test', 'mini_test']
and ex_n < len(self.exercises_list) // 2):
# __
how_many = len(self.exercises_list) // 2 - ex_n
# elif self.layout_type == 'mini_test':
# if ex_n < len(self.exercises_list) / 4:
# how_many = len(self.exercises_list) / 4 - ex_n
# elif ex_n < len(self.exercises_list) / 2:
# how_many = len(self.exercises_list) / 2 - ex_n
# elif ex_n < 3*len(self.exercises_list) / 4:
# how_many = 3*len(self.exercises_list) / 4 - ex_n
for i in range(how_many):
if self.shift and i != 0:
result += shared.machine.write_jump_to_next_page()
if ex_or_answers == 'exc':
result += self.sheet_title_to_str(
variant=self.layout_type)
else:
result += self.answers_title_to_str()
result += \
shared.machine.write_set_font_size_to(
'large')
if self.write_ex_titles:
result += M.write_exercise_number()
result += self.exercises_list[ex_n].to_str(ex_or_answers)
if (self.layout_type == 'default'
and ex_or_answers == 'ans'):
if i < how_many - 1:
result += M.addvspace(height='29.0pt')
else:
vspace = '' if len(result) <= 25 else result[-25:]
newpage = '' if len(result) <= 9 else result[-9:]
result += M.write_new_line(check=result[-2:],
check2=vspace,
check3=newpage)
# if not (ex_or_answers == 'ans' \
# and self.layout_type == 'equations'):
# __
# result += M.write_new_line()
ex_n += 1
elif layout[2 * k] == 'jump' and layout[2 * k + 1] == 'next_page':
result += M.write_jump_to_next_page()
else:
nb_of_lines = layout[2 * k][0]
nb_of_cols = len(layout[2 * k]) - 1
col_widths = layout[2 * k][1:]
content = []
for i in range(nb_of_lines):
for j in range(nb_of_cols):
nb_of_ex_in_this_cell = \
layout[2 * k + 1][i * nb_of_cols + j]
cell_content = ""
for n in range(nb_of_ex_in_this_cell):
if self.write_ex_titles:
cell_content += M.write_exercise_number()
cell_content += \
self.exercises_list[ex_n].to_str(ex_or_answers)
ex_n += 1
content += [cell_content]
result += M.write_layout((nb_of_lines, nb_of_cols),
col_widths,
content,
unit=self.sheet_layout_unit)
if ex_n < len(self.exercises_list):
result += M.write_new_line()
return result
# --------------------------------------------------------------------------
##
# @brief Writes to the output the header of the sheet to be generated
# This header is written in a large size. A new line follow it.
# It's useful to write headers for test sheets, for example.
# --------------------------------------------------------------------------
##
# @brief Writes to the output the title of the sheet to be generated
[docs] def sheet_title_to_str(self, variant='default'):
result = ''
if variant == 'slideshow' and self.title != '':
result += shared.machine.write_frame(self.title,
uncovered=True,
duration=0.75)
else:
result += shared.machine.write_set_font_size_to('large')
if shared.enable_js_form:
result += r'\begin{Form}' + '\n'
result += shared.machine.write(self.title, emphasize='bold')
if shared.enable_js_form:
result += r"""\hfill
\TextField[name=mark,width=3cm,height=0.2cm,value=Score :,
format={var f = this.getField('mark');
f.strokeColor = ['T'];
f.fillColor = ['T'];
f.textFont = 'Ubuntu'},
width=10em]{}"""
if self.subtitle != '':
result += shared.machine.write_new_line()
result += shared.machine.write_set_font_size_to('normal')
result += shared.machine.write(self.subtitle, emphasize='bold')
result += shared.machine.write_new_line_twice()
return result
# --------------------------------------------------------------------------
##
# @brief Writes to the output the sheet's text
[docs] def sheet_text_to_str(self):
result = ""
if self.text != "":
result += shared.machine.write(self.text)
result += shared.machine.write_new_line_twice()
return result
# --------------------------------------------------------------------------
##
# @brief Writes to the output title of the answers' sheet to be generated
[docs] def answers_title_to_str(self, variant='default'):
result = ''
if variant == 'slideshow' and self.answers_title != '':
result += shared.machine.write_frame(self.answers_title)
else:
result += shared.machine.write_set_font_size_to('Large')
result += shared.machine.write(self.answers_title,
emphasize='bold')
result += shared.machine.write_new_line_twice()
return result